From 7d62a89a51c695ab6f0f8cb048533d50ff924b91 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Fri, 1 Jul 2016 18:54:00 -0400 Subject: [PATCH] Init HttpClient with each Api Request. Guarg agsint connectivity issues when making API calls. --- src/Android/MainActivity.cs | 4 + .../Repositories/IApiRepository.cs | 2 +- src/App/App.cs | 73 +++++++- src/App/App.csproj | 1 + src/App/Pages/VaultListSitesPage.cs | 5 +- src/App/Repositories/AccountsApiRepository.cs | 33 ++-- src/App/Repositories/ApiRepository.cs | 162 +++++++++++------- src/App/Repositories/AuthApiRepository.cs | 69 +++++--- src/App/Repositories/BaseApiRepository.cs | 25 ++- src/App/Repositories/CipherApiRepository.cs | 101 +++++++---- src/App/Repositories/DeviceApiRepository.cs | 37 ++-- src/App/Repositories/FolderApiRepository.cs | 37 ++-- src/App/Repositories/SiteApiRepository.cs | 5 + src/App/Utilities/ApiHttpClient.cs | 17 ++ src/iOS/AppDelegate.cs | 3 + 15 files changed, 402 insertions(+), 172 deletions(-) create mode 100644 src/App/Utilities/ApiHttpClient.cs diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs index eaa2bfaad..56c90fb54 100644 --- a/src/Android/MainActivity.cs +++ b/src/Android/MainActivity.cs @@ -10,6 +10,8 @@ using Bit.App.Abstractions; using XLabs.Ioc; using Plugin.Fingerprint.Abstractions; using Plugin.Settings.Abstractions; +using Plugin.Connectivity.Abstractions; +using Acr.UserDialogs; namespace Bit.Android { @@ -25,6 +27,8 @@ namespace Bit.Android LoadApplication(new App.App( Resolver.Resolve(), + Resolver.Resolve(), + Resolver.Resolve(), Resolver.Resolve(), Resolver.Resolve(), Resolver.Resolve(), diff --git a/src/App/Abstractions/Repositories/IApiRepository.cs b/src/App/Abstractions/Repositories/IApiRepository.cs index 651e69701..51273a005 100644 --- a/src/App/Abstractions/Repositories/IApiRepository.cs +++ b/src/App/Abstractions/Repositories/IApiRepository.cs @@ -14,6 +14,6 @@ namespace Bit.App.Abstractions Task>> GetAsync(); Task> PostAsync(TRequest requestObj); Task> PutAsync(TId id, TRequest requestObj); - Task> DeleteAsync(TId id); + Task DeleteAsync(TId id); } } \ No newline at end of file diff --git a/src/App/App.cs b/src/App/App.cs index 8c1102e37..b6c98924b 100644 --- a/src/App/App.cs +++ b/src/App/App.cs @@ -10,12 +10,17 @@ using Plugin.Fingerprint.Abstractions; using System.Threading.Tasks; using Plugin.Settings.Abstractions; using Bit.App.Controls; +using Plugin.Connectivity.Abstractions; +using System.Net; +using Acr.UserDialogs; namespace Bit.App { public class App : Application { private readonly IDatabaseService _databaseService; + private readonly IConnectivity _connectivity; + private readonly IUserDialogs _userDialogs; private readonly ISyncService _syncService; private readonly IAuthService _authService; private readonly IFingerprint _fingerprint; @@ -23,12 +28,16 @@ namespace Bit.App public App( IAuthService authService, + IConnectivity connectivity, + IUserDialogs userDialogs, IDatabaseService databaseService, ISyncService syncService, IFingerprint fingerprint, ISettings settings) { _databaseService = databaseService; + _connectivity = connectivity; + _userDialogs = userDialogs; _syncService = syncService; _authService = authService; _fingerprint = fingerprint; @@ -47,9 +56,36 @@ namespace Bit.App MessagingCenter.Subscribe(Current, "Resumed", async (sender, args) => { - var syncTask = _syncService.IncrementalSyncAsync(); await CheckLockAsync(args); - await syncTask; + if(_connectivity.IsConnected) + { + var attempt = 0; + do + { + try + { + await _syncService.IncrementalSyncAsync(); + break; + } + catch(WebException) + { + Debug.WriteLine("Failed to sync."); + if(attempt >= 1) + { + await _userDialogs.AlertAsync("Unable to automatically sync."); + } + else + { + await Task.Delay(1000); + } + attempt++; + } + } while(attempt <= 1); + } + else + { + Debug.WriteLine("Not connected."); + } }); MessagingCenter.Subscribe(Current, "Lock", async (sender, args) => @@ -58,12 +94,35 @@ namespace Bit.App }); } - protected override void OnStart() + protected async override void OnStart() { // Handle when your app starts - var lockTask = CheckLockAsync(false); + await CheckLockAsync(false); _databaseService.CreateTables(); - var syncTask = _syncService.FullSyncAsync(); + if(_connectivity.IsConnected) + { + var attempt = 0; + do + { + try + { + await _syncService.FullSyncAsync(); + break; + } + catch(WebException) + { + if(attempt >= 1) + { + await _userDialogs.AlertAsync("Unable to automatically sync. Manual sync required."); + } + else + { + await Task.Delay(1000); + } + attempt++; + } + } while(attempt <= 1); + } Debug.WriteLine("OnStart"); } @@ -79,14 +138,14 @@ namespace Bit.App } } - protected override void OnResume() + protected async override void OnResume() { // Handle when your app resumes Debug.WriteLine("OnResume"); if(Device.OS == TargetPlatform.Android) { - var task = CheckLockAsync(false); + await CheckLockAsync(false); } var lockPinPage = Current.MainPage.Navigation.ModalStack.LastOrDefault() as LockPinPage; diff --git a/src/App/App.csproj b/src/App/App.csproj index f40b674b0..c12e76863 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -157,6 +157,7 @@ + diff --git a/src/App/Pages/VaultListSitesPage.cs b/src/App/Pages/VaultListSitesPage.cs index bea69d12a..5c1c51c30 100644 --- a/src/App/Pages/VaultListSitesPage.cs +++ b/src/App/Pages/VaultListSitesPage.cs @@ -12,6 +12,7 @@ using XLabs.Ioc; using Bit.App.Utilities; using PushNotification.Plugin.Abstractions; using Plugin.Settings.Abstractions; +using Plugin.Connectivity.Abstractions; namespace Bit.App.Pages { @@ -20,6 +21,7 @@ namespace Bit.App.Pages private readonly IFolderService _folderService; private readonly ISiteService _siteService; private readonly IUserDialogs _userDialogs; + private readonly IConnectivity _connectivity; private readonly IClipboardService _clipboardService; private readonly IPushNotification _pushNotification; private readonly ISettings _settings; @@ -30,6 +32,7 @@ namespace Bit.App.Pages _favorites = favorites; _folderService = Resolver.Resolve(); _siteService = Resolver.Resolve(); + _connectivity = Resolver.Resolve(); _userDialogs = Resolver.Resolve(); _clipboardService = Resolver.Resolve(); _pushNotification = Resolver.Resolve(); @@ -77,7 +80,7 @@ namespace Bit.App.Pages base.OnAppearing(); LoadFoldersAsync().Wait(); - if(Device.OS == TargetPlatform.iOS && !_favorites) + if(_connectivity.IsConnected && Device.OS == TargetPlatform.iOS && !_favorites) { if(!_settings.GetValueOrDefault(Constants.PushPromptShown)) { diff --git a/src/App/Repositories/AccountsApiRepository.cs b/src/App/Repositories/AccountsApiRepository.cs index 8dc78f50c..ab2d9c4a5 100644 --- a/src/App/Repositories/AccountsApiRepository.cs +++ b/src/App/Repositories/AccountsApiRepository.cs @@ -3,28 +3,41 @@ using System.Net.Http; using System.Threading.Tasks; using Bit.App.Abstractions; using Bit.App.Models.Api; +using Plugin.Connectivity.Abstractions; namespace Bit.App.Repositories { public class AccountsApiRepository : BaseApiRepository, IAccountsApiRepository { + public AccountsApiRepository(IConnectivity connectivity) + : base(connectivity) + { } + protected override string ApiRoute => "accounts"; public virtual async Task PostRegisterAsync(RegisterRequest requestObj) { - var requestMessage = new TokenHttpRequestMessage(requestObj) + if(!Connectivity.IsConnected) { - Method = HttpMethod.Post, - RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/register")), - }; - - var response = await Client.SendAsync(requestMessage); - if(!response.IsSuccessStatusCode) - { - return await HandleErrorAsync(response); + return HandledNotConnected(); } - return ApiResult.Success(response.StatusCode); + using(var client = new ApiHttpClient()) + { + var requestMessage = new TokenHttpRequestMessage(requestObj) + { + Method = HttpMethod.Post, + RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/register")), + }; + + var response = await client.SendAsync(requestMessage); + if(!response.IsSuccessStatusCode) + { + return await HandleErrorAsync(response); + } + + return ApiResult.Success(response.StatusCode); + } } } } diff --git a/src/App/Repositories/ApiRepository.cs b/src/App/Repositories/ApiRepository.cs index 080055d33..111706034 100644 --- a/src/App/Repositories/ApiRepository.cs +++ b/src/App/Repositories/ApiRepository.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Bit.App.Abstractions; using Bit.App.Models.Api; using Newtonsoft.Json; +using Plugin.Connectivity.Abstractions; namespace Bit.App.Repositories { @@ -13,100 +14,141 @@ namespace Bit.App.Repositories where TRequest : class where TResponse : class { - public ApiRepository() + public ApiRepository(IConnectivity connectivity) + : base(connectivity) { } public virtual async Task> GetByIdAsync(TId id) { - var requestMessage = new TokenHttpRequestMessage() + if(!Connectivity.IsConnected) { - Method = HttpMethod.Get, - RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/", id)), - }; - - var response = await Client.SendAsync(requestMessage); - if(!response.IsSuccessStatusCode) - { - return await HandleErrorAsync(response); + return HandledNotConnected(); } - var responseContent = await response.Content.ReadAsStringAsync(); - var responseObj = JsonConvert.DeserializeObject(responseContent); - return ApiResult.Success(responseObj, response.StatusCode); + using(var client = new ApiHttpClient()) + { + var requestMessage = new TokenHttpRequestMessage() + { + Method = HttpMethod.Get, + RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/", id)), + }; + + var response = await client.SendAsync(requestMessage); + if(!response.IsSuccessStatusCode) + { + return await HandleErrorAsync(response); + } + + var responseContent = await response.Content.ReadAsStringAsync(); + var responseObj = JsonConvert.DeserializeObject(responseContent); + return ApiResult.Success(responseObj, response.StatusCode); + } } public virtual async Task>> GetAsync() { - var requestMessage = new TokenHttpRequestMessage() + if(!Connectivity.IsConnected) { - Method = HttpMethod.Get, - RequestUri = new Uri(Client.BaseAddress, ApiRoute), - }; - - var response = await Client.SendAsync(requestMessage); - if(!response.IsSuccessStatusCode) - { - return await HandleErrorAsync>(response); + return HandledNotConnected>(); } - var responseContent = await response.Content.ReadAsStringAsync(); - var responseObj = JsonConvert.DeserializeObject>(responseContent); - return ApiResult>.Success(responseObj, response.StatusCode); + using(var client = new ApiHttpClient()) + { + var requestMessage = new TokenHttpRequestMessage() + { + Method = HttpMethod.Get, + RequestUri = new Uri(client.BaseAddress, ApiRoute), + }; + + var response = await client.SendAsync(requestMessage); + if(!response.IsSuccessStatusCode) + { + return await HandleErrorAsync>(response); + } + + var responseContent = await response.Content.ReadAsStringAsync(); + var responseObj = JsonConvert.DeserializeObject>(responseContent); + return ApiResult>.Success(responseObj, response.StatusCode); + } } public virtual async Task> PostAsync(TRequest requestObj) { - var requestMessage = new TokenHttpRequestMessage(requestObj) + if(!Connectivity.IsConnected) { - Method = HttpMethod.Post, - RequestUri = new Uri(Client.BaseAddress, ApiRoute), - }; - - var response = await Client.SendAsync(requestMessage); - if(!response.IsSuccessStatusCode) - { - return await HandleErrorAsync(response); + return HandledNotConnected(); } - var responseContent = await response.Content.ReadAsStringAsync(); - var responseObj = JsonConvert.DeserializeObject(responseContent); - return ApiResult.Success(responseObj, response.StatusCode); + using(var client = new ApiHttpClient()) + { + var requestMessage = new TokenHttpRequestMessage(requestObj) + { + Method = HttpMethod.Post, + RequestUri = new Uri(client.BaseAddress, ApiRoute), + }; + + var response = await client.SendAsync(requestMessage); + if(!response.IsSuccessStatusCode) + { + return await HandleErrorAsync(response); + } + + var responseContent = await response.Content.ReadAsStringAsync(); + var responseObj = JsonConvert.DeserializeObject(responseContent); + return ApiResult.Success(responseObj, response.StatusCode); + } } public virtual async Task> PutAsync(TId id, TRequest requestObj) { - var requestMessage = new TokenHttpRequestMessage(requestObj) + if(!Connectivity.IsConnected) { - Method = HttpMethod.Put, - RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/", id)), - }; - - var response = await Client.SendAsync(requestMessage); - if(!response.IsSuccessStatusCode) - { - return await HandleErrorAsync(response); + return HandledNotConnected(); } - var responseContent = await response.Content.ReadAsStringAsync(); - var responseObj = JsonConvert.DeserializeObject(responseContent); - return ApiResult.Success(responseObj, response.StatusCode); + using(var client = new ApiHttpClient()) + { + var requestMessage = new TokenHttpRequestMessage(requestObj) + { + Method = HttpMethod.Put, + RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/", id)), + }; + + var response = await client.SendAsync(requestMessage); + if(!response.IsSuccessStatusCode) + { + return await HandleErrorAsync(response); + } + + var responseContent = await response.Content.ReadAsStringAsync(); + var responseObj = JsonConvert.DeserializeObject(responseContent); + return ApiResult.Success(responseObj, response.StatusCode); + } } - public virtual async Task> DeleteAsync(TId id) + public virtual async Task DeleteAsync(TId id) { - var requestMessage = new TokenHttpRequestMessage() + if(!Connectivity.IsConnected) { - Method = HttpMethod.Delete, - RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/", id)), - }; - - var response = await Client.SendAsync(requestMessage); - if(!response.IsSuccessStatusCode) - { - return await HandleErrorAsync(response); + return HandledNotConnected(); } - return ApiResult.Success(null, response.StatusCode); + using(var client = new ApiHttpClient()) + { + var requestMessage = new TokenHttpRequestMessage() + { + Method = HttpMethod.Delete, + RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/", id)), + }; + + var response = await client.SendAsync(requestMessage); + if(!response.IsSuccessStatusCode) + { + return await HandleErrorAsync(response); + } + + return ApiResult.Success(response.StatusCode); + } } } } diff --git a/src/App/Repositories/AuthApiRepository.cs b/src/App/Repositories/AuthApiRepository.cs index a149d10b9..777611cc8 100644 --- a/src/App/Repositories/AuthApiRepository.cs +++ b/src/App/Repositories/AuthApiRepository.cs @@ -4,49 +4,70 @@ using System.Threading.Tasks; using Bit.App.Abstractions; using Bit.App.Models.Api; using Newtonsoft.Json; +using Plugin.Connectivity.Abstractions; namespace Bit.App.Repositories { public class AuthApiRepository : BaseApiRepository, IAuthApiRepository { + public AuthApiRepository(IConnectivity connectivity) + : base(connectivity) + { } + protected override string ApiRoute => "auth"; public virtual async Task> PostTokenAsync(TokenRequest requestObj) { - var requestMessage = new TokenHttpRequestMessage(requestObj) + if(!Connectivity.IsConnected) { - Method = HttpMethod.Post, - RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/token")), - }; - - var response = await Client.SendAsync(requestMessage); - if(!response.IsSuccessStatusCode) - { - return await HandleErrorAsync(response); + return HandledNotConnected(); } - var responseContent = await response.Content.ReadAsStringAsync(); - var responseObj = JsonConvert.DeserializeObject(responseContent); - return ApiResult.Success(responseObj, response.StatusCode); + using(var client = new ApiHttpClient()) + { + var requestMessage = new TokenHttpRequestMessage(requestObj) + { + Method = HttpMethod.Post, + RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/token")), + }; + + var response = await client.SendAsync(requestMessage); + if(!response.IsSuccessStatusCode) + { + return await HandleErrorAsync(response); + } + + var responseContent = await response.Content.ReadAsStringAsync(); + var responseObj = JsonConvert.DeserializeObject(responseContent); + return ApiResult.Success(responseObj, response.StatusCode); + } } public virtual async Task> PostTokenTwoFactorAsync(TokenTwoFactorRequest requestObj) { - var requestMessage = new TokenHttpRequestMessage(requestObj) + if(!Connectivity.IsConnected) { - Method = HttpMethod.Post, - RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/token/two-factor")), - }; - - var response = await Client.SendAsync(requestMessage); - if(!response.IsSuccessStatusCode) - { - return await HandleErrorAsync(response); + return HandledNotConnected(); } - var responseContent = await response.Content.ReadAsStringAsync(); - var responseObj = JsonConvert.DeserializeObject(responseContent); - return ApiResult.Success(responseObj, response.StatusCode); + using(var client = new ApiHttpClient()) + { + var requestMessage = new TokenHttpRequestMessage(requestObj) + { + Method = HttpMethod.Post, + RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/token/two-factor")), + }; + + var response = await client.SendAsync(requestMessage); + if(!response.IsSuccessStatusCode) + { + return await HandleErrorAsync(response); + } + + var responseContent = await response.Content.ReadAsStringAsync(); + var responseObj = JsonConvert.DeserializeObject(responseContent); + return ApiResult.Success(responseObj, response.StatusCode); + } } } } diff --git a/src/App/Repositories/BaseApiRepository.cs b/src/App/Repositories/BaseApiRepository.cs index 18d5ee0d5..885919c25 100644 --- a/src/App/Repositories/BaseApiRepository.cs +++ b/src/App/Repositories/BaseApiRepository.cs @@ -1,27 +1,34 @@ using System; using System.Collections.Generic; using System.Net.Http; -using System.Net.Http.Headers; using System.Threading.Tasks; using Bit.App.Models.Api; -using ModernHttpClient; using Newtonsoft.Json; +using Plugin.Connectivity.Abstractions; namespace Bit.App.Repositories { public abstract class BaseApiRepository { - public BaseApiRepository() + public BaseApiRepository(IConnectivity connectivity) { - Client = new HttpClient(new NativeMessageHandler()); - Client.BaseAddress = new Uri("https://api.bitwarden.com"); - Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + Connectivity = connectivity; } - protected virtual HttpClient Client { get; private set; } + protected IConnectivity Connectivity { get; private set; } protected abstract string ApiRoute { get; } - public async Task> HandleErrorAsync(HttpResponseMessage response) + protected ApiResult HandledNotConnected() + { + return ApiResult.Failed(System.Net.HttpStatusCode.RequestTimeout, new ApiError { Message = "Not connected to the internet." }); + } + + protected ApiResult HandledNotConnected() + { + return ApiResult.Failed(System.Net.HttpStatusCode.RequestTimeout, new ApiError { Message = "Not connected to the internet." }); + } + + protected async Task> HandleErrorAsync(HttpResponseMessage response) { try { @@ -34,7 +41,7 @@ namespace Bit.App.Repositories return ApiResult.Failed(response.StatusCode, new ApiError { Message = "An unknown error has occured." }); } - public async Task HandleErrorAsync(HttpResponseMessage response) + protected async Task HandleErrorAsync(HttpResponseMessage response) { try { diff --git a/src/App/Repositories/CipherApiRepository.cs b/src/App/Repositories/CipherApiRepository.cs index b47be0fda..c7c0af91e 100644 --- a/src/App/Repositories/CipherApiRepository.cs +++ b/src/App/Repositories/CipherApiRepository.cs @@ -5,68 +5,97 @@ using System.Threading.Tasks; using Bit.App.Abstractions; using Bit.App.Models.Api; using Newtonsoft.Json; +using Plugin.Connectivity.Abstractions; namespace Bit.App.Repositories { public class CipherApiRepository : BaseApiRepository, ICipherApiRepository { + public CipherApiRepository(IConnectivity connectivity) + : base(connectivity) + { } + protected override string ApiRoute => "ciphers"; public virtual async Task> GetByIdAsync(string id) { - var requestMessage = new TokenHttpRequestMessage() + if(!Connectivity.IsConnected) { - Method = HttpMethod.Get, - RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/", id)), - }; - - var response = await Client.SendAsync(requestMessage); - if(!response.IsSuccessStatusCode) - { - return await HandleErrorAsync(response); + return HandledNotConnected(); } - var responseContent = await response.Content.ReadAsStringAsync(); - var responseObj = JsonConvert.DeserializeObject(responseContent); - return ApiResult.Success(responseObj, response.StatusCode); + using(var client = new ApiHttpClient()) + { + var requestMessage = new TokenHttpRequestMessage() + { + Method = HttpMethod.Get, + RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/", id)), + }; + + var response = await client.SendAsync(requestMessage); + if(!response.IsSuccessStatusCode) + { + return await HandleErrorAsync(response); + } + + var responseContent = await response.Content.ReadAsStringAsync(); + var responseObj = JsonConvert.DeserializeObject(responseContent); + return ApiResult.Success(responseObj, response.StatusCode); + } } public virtual async Task>> GetAsync() { - var requestMessage = new TokenHttpRequestMessage() + if(!Connectivity.IsConnected) { - Method = HttpMethod.Get, - RequestUri = new Uri(Client.BaseAddress, ApiRoute), - }; - - var response = await Client.SendAsync(requestMessage); - if(!response.IsSuccessStatusCode) - { - return await HandleErrorAsync>(response); + return HandledNotConnected>(); } - var responseContent = await response.Content.ReadAsStringAsync(); - var responseObj = JsonConvert.DeserializeObject>(responseContent); - return ApiResult>.Success(responseObj, response.StatusCode); + using(var client = new ApiHttpClient()) + { + var requestMessage = new TokenHttpRequestMessage() + { + Method = HttpMethod.Get, + RequestUri = new Uri(client.BaseAddress, ApiRoute), + }; + + var response = await client.SendAsync(requestMessage); + if(!response.IsSuccessStatusCode) + { + return await HandleErrorAsync>(response); + } + + var responseContent = await response.Content.ReadAsStringAsync(); + var responseObj = JsonConvert.DeserializeObject>(responseContent); + return ApiResult>.Success(responseObj, response.StatusCode); + } } public virtual async Task> GetByRevisionDateWithHistoryAsync(DateTime since) { - var requestMessage = new TokenHttpRequestMessage() + if(!Connectivity.IsConnected) { - Method = HttpMethod.Get, - RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/history", "?since=", since)), - }; - - var response = await Client.SendAsync(requestMessage); - if(!response.IsSuccessStatusCode) - { - return await HandleErrorAsync(response); + return HandledNotConnected(); } - var responseContent = await response.Content.ReadAsStringAsync(); - var responseObj = JsonConvert.DeserializeObject(responseContent); - return ApiResult.Success(responseObj, response.StatusCode); + using(var client = new ApiHttpClient()) + { + var requestMessage = new TokenHttpRequestMessage() + { + Method = HttpMethod.Get, + RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/history", "?since=", since)), + }; + + var response = await client.SendAsync(requestMessage); + if(!response.IsSuccessStatusCode) + { + return await HandleErrorAsync(response); + } + + var responseContent = await response.Content.ReadAsStringAsync(); + var responseObj = JsonConvert.DeserializeObject(responseContent); + return ApiResult.Success(responseObj, response.StatusCode); + } } } } diff --git a/src/App/Repositories/DeviceApiRepository.cs b/src/App/Repositories/DeviceApiRepository.cs index c08c7b54f..0d4811547 100644 --- a/src/App/Repositories/DeviceApiRepository.cs +++ b/src/App/Repositories/DeviceApiRepository.cs @@ -4,30 +4,43 @@ using System.Threading.Tasks; using Bit.App.Abstractions; using Bit.App.Models.Api; using Newtonsoft.Json; +using Plugin.Connectivity.Abstractions; namespace Bit.App.Repositories { public class DeviceApiRepository : ApiRepository, IDeviceApiRepository { + public DeviceApiRepository(IConnectivity connectivity) + : base(connectivity) + { } + protected override string ApiRoute => "devices"; public virtual async Task> PutTokenAsync(string identifier, DeviceTokenRequest request) { - var requestMessage = new TokenHttpRequestMessage(request) + if(!Connectivity.IsConnected) { - Method = HttpMethod.Put, - RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/identifier/", identifier, "/token")), - }; - - var response = await Client.SendAsync(requestMessage); - if(!response.IsSuccessStatusCode) - { - return await HandleErrorAsync(response); + return HandledNotConnected(); } - var responseContent = await response.Content.ReadAsStringAsync(); - var responseObj = JsonConvert.DeserializeObject(responseContent); - return ApiResult.Success(responseObj, response.StatusCode); + using(var client = new ApiHttpClient()) + { + var requestMessage = new TokenHttpRequestMessage(request) + { + Method = HttpMethod.Put, + RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/identifier/", identifier, "/token")), + }; + + var response = await client.SendAsync(requestMessage); + if(!response.IsSuccessStatusCode) + { + return await HandleErrorAsync(response); + } + + var responseContent = await response.Content.ReadAsStringAsync(); + var responseObj = JsonConvert.DeserializeObject(responseContent); + return ApiResult.Success(responseObj, response.StatusCode); + } } } } diff --git a/src/App/Repositories/FolderApiRepository.cs b/src/App/Repositories/FolderApiRepository.cs index 7e116833f..1e1e0bc19 100644 --- a/src/App/Repositories/FolderApiRepository.cs +++ b/src/App/Repositories/FolderApiRepository.cs @@ -5,30 +5,43 @@ using System.Threading.Tasks; using Bit.App.Abstractions; using Bit.App.Models.Api; using Newtonsoft.Json; +using Plugin.Connectivity.Abstractions; namespace Bit.App.Repositories { public class FolderApiRepository : ApiRepository, IFolderApiRepository { + public FolderApiRepository(IConnectivity connectivity) + : base(connectivity) + { } + protected override string ApiRoute => "folders"; public virtual async Task>> GetByRevisionDateAsync(DateTime since) { - var requestMessage = new TokenHttpRequestMessage() + if(!Connectivity.IsConnected) { - Method = HttpMethod.Get, - RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "?since=", since)), - }; - - var response = await Client.SendAsync(requestMessage); - if(!response.IsSuccessStatusCode) - { - return await HandleErrorAsync>(response); + return HandledNotConnected>(); } - var responseContent = await response.Content.ReadAsStringAsync(); - var responseObj = JsonConvert.DeserializeObject>(responseContent); - return ApiResult>.Success(responseObj, response.StatusCode); + using(var client = new ApiHttpClient()) + { + var requestMessage = new TokenHttpRequestMessage() + { + Method = HttpMethod.Get, + RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "?since=", since)), + }; + + var response = await client.SendAsync(requestMessage); + if(!response.IsSuccessStatusCode) + { + return await HandleErrorAsync>(response); + } + + var responseContent = await response.Content.ReadAsStringAsync(); + var responseObj = JsonConvert.DeserializeObject>(responseContent); + return ApiResult>.Success(responseObj, response.StatusCode); + } } } } diff --git a/src/App/Repositories/SiteApiRepository.cs b/src/App/Repositories/SiteApiRepository.cs index a90ca3e6d..735191a0f 100644 --- a/src/App/Repositories/SiteApiRepository.cs +++ b/src/App/Repositories/SiteApiRepository.cs @@ -5,11 +5,16 @@ using System.Threading.Tasks; using Bit.App.Abstractions; using Bit.App.Models.Api; using Newtonsoft.Json; +using Plugin.Connectivity.Abstractions; namespace Bit.App.Repositories { public class SiteApiRepository : ApiRepository, ISiteApiRepository { + public SiteApiRepository(IConnectivity connectivity) + : base(connectivity) + { } + protected override string ApiRoute => "sites"; } } diff --git a/src/App/Utilities/ApiHttpClient.cs b/src/App/Utilities/ApiHttpClient.cs new file mode 100644 index 000000000..87d5cb84c --- /dev/null +++ b/src/App/Utilities/ApiHttpClient.cs @@ -0,0 +1,17 @@ +using System.Net.Http; +using ModernHttpClient; +using System; +using System.Net.Http.Headers; + +namespace Bit.App +{ + public class ApiHttpClient : HttpClient + { + public ApiHttpClient() + : base(new NativeMessageHandler()) + { + BaseAddress = new Uri("https://api.bitwarden.com"); + DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + } + } +} diff --git a/src/iOS/AppDelegate.cs b/src/iOS/AppDelegate.cs index 3374f8cf9..56041b27f 100644 --- a/src/iOS/AppDelegate.cs +++ b/src/iOS/AppDelegate.cs @@ -22,6 +22,7 @@ using Bit.App; using Bit.iOS.Core.Services; using PushNotification.Plugin; using Plugin.DeviceInfo; +using Plugin.Connectivity.Abstractions; namespace Bit.iOS { @@ -43,6 +44,8 @@ namespace Bit.iOS LoadApplication(new App.App( Resolver.Resolve(), + Resolver.Resolve(), + Resolver.Resolve(), Resolver.Resolve(), Resolver.Resolve(), Resolver.Resolve(),