Init HttpClient with each Api Request. Guarg agsint connectivity issues when making API calls.

This commit is contained in:
Kyle Spearrin 2016-07-01 18:54:00 -04:00
parent a4e6f49959
commit 7d62a89a51
15 changed files with 402 additions and 172 deletions

View File

@ -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<IAuthService>(),
Resolver.Resolve<IConnectivity>(),
Resolver.Resolve<IUserDialogs>(),
Resolver.Resolve<IDatabaseService>(),
Resolver.Resolve<ISyncService>(),
Resolver.Resolve<IFingerprint>(),

View File

@ -14,6 +14,6 @@ namespace Bit.App.Abstractions
Task<ApiResult<ListResponse<TResponse>>> GetAsync();
Task<ApiResult<TResponse>> PostAsync(TRequest requestObj);
Task<ApiResult<TResponse>> PutAsync(TId id, TRequest requestObj);
Task<ApiResult<object>> DeleteAsync(TId id);
Task<ApiResult> DeleteAsync(TId id);
}
}

View File

@ -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<Application, bool>(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<Application, bool>(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;

View File

@ -157,6 +157,7 @@
<Compile Include="Pages\VaultListSitesPage.cs" />
<Compile Include="Utilities\Extentions.cs" />
<Compile Include="Utilities\ExtendedObservableCollection.cs" />
<Compile Include="Utilities\ApiHttpClient.cs" />
<Compile Include="Utilities\TokenHttpRequestMessage.cs" />
</ItemGroup>
<ItemGroup>

View File

@ -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<IFolderService>();
_siteService = Resolver.Resolve<ISiteService>();
_connectivity = Resolver.Resolve<IConnectivity>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_clipboardService = Resolver.Resolve<IClipboardService>();
_pushNotification = Resolver.Resolve<IPushNotification>();
@ -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<bool>(Constants.PushPromptShown))
{

View File

@ -3,22 +3,34 @@ 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<ApiResult> PostRegisterAsync(RegisterRequest requestObj)
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected();
}
using(var client = new ApiHttpClient())
{
var requestMessage = new TokenHttpRequestMessage(requestObj)
{
Method = HttpMethod.Post,
RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/register")),
RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/register")),
};
var response = await Client.SendAsync(requestMessage);
var response = await client.SendAsync(requestMessage);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync(response);
@ -28,3 +40,4 @@ namespace Bit.App.Repositories
}
}
}
}

View File

@ -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,18 +14,26 @@ namespace Bit.App.Repositories
where TRequest : class
where TResponse : class
{
public ApiRepository()
public ApiRepository(IConnectivity connectivity)
: base(connectivity)
{ }
public virtual async Task<ApiResult<TResponse>> GetByIdAsync(TId id)
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<TResponse>();
}
using(var client = new ApiHttpClient())
{
var requestMessage = new TokenHttpRequestMessage()
{
Method = HttpMethod.Get,
RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/", id)),
RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/", id)),
};
var response = await Client.SendAsync(requestMessage);
var response = await client.SendAsync(requestMessage);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync<TResponse>(response);
@ -34,16 +43,24 @@ namespace Bit.App.Repositories
var responseObj = JsonConvert.DeserializeObject<TResponse>(responseContent);
return ApiResult<TResponse>.Success(responseObj, response.StatusCode);
}
}
public virtual async Task<ApiResult<ListResponse<TResponse>>> GetAsync()
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<ListResponse<TResponse>>();
}
using(var client = new ApiHttpClient())
{
var requestMessage = new TokenHttpRequestMessage()
{
Method = HttpMethod.Get,
RequestUri = new Uri(Client.BaseAddress, ApiRoute),
RequestUri = new Uri(client.BaseAddress, ApiRoute),
};
var response = await Client.SendAsync(requestMessage);
var response = await client.SendAsync(requestMessage);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync<ListResponse<TResponse>>(response);
@ -53,16 +70,24 @@ namespace Bit.App.Repositories
var responseObj = JsonConvert.DeserializeObject<ListResponse<TResponse>>(responseContent);
return ApiResult<ListResponse<TResponse>>.Success(responseObj, response.StatusCode);
}
}
public virtual async Task<ApiResult<TResponse>> PostAsync(TRequest requestObj)
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<TResponse>();
}
using(var client = new ApiHttpClient())
{
var requestMessage = new TokenHttpRequestMessage(requestObj)
{
Method = HttpMethod.Post,
RequestUri = new Uri(Client.BaseAddress, ApiRoute),
RequestUri = new Uri(client.BaseAddress, ApiRoute),
};
var response = await Client.SendAsync(requestMessage);
var response = await client.SendAsync(requestMessage);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync<TResponse>(response);
@ -72,16 +97,24 @@ namespace Bit.App.Repositories
var responseObj = JsonConvert.DeserializeObject<TResponse>(responseContent);
return ApiResult<TResponse>.Success(responseObj, response.StatusCode);
}
}
public virtual async Task<ApiResult<TResponse>> PutAsync(TId id, TRequest requestObj)
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<TResponse>();
}
using(var client = new ApiHttpClient())
{
var requestMessage = new TokenHttpRequestMessage(requestObj)
{
Method = HttpMethod.Put,
RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/", id)),
RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/", id)),
};
var response = await Client.SendAsync(requestMessage);
var response = await client.SendAsync(requestMessage);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync<TResponse>(response);
@ -91,22 +124,31 @@ namespace Bit.App.Repositories
var responseObj = JsonConvert.DeserializeObject<TResponse>(responseContent);
return ApiResult<TResponse>.Success(responseObj, response.StatusCode);
}
}
public virtual async Task<ApiResult<object>> DeleteAsync(TId id)
public virtual async Task<ApiResult> DeleteAsync(TId id)
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected();
}
using(var client = new ApiHttpClient())
{
var requestMessage = new TokenHttpRequestMessage()
{
Method = HttpMethod.Delete,
RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/", id)),
RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/", id)),
};
var response = await Client.SendAsync(requestMessage);
var response = await client.SendAsync(requestMessage);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync<object>(response);
return await HandleErrorAsync(response);
}
return ApiResult<object>.Success(null, response.StatusCode);
return ApiResult.Success(response.StatusCode);
}
}
}
}

View File

@ -4,22 +4,34 @@ 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<ApiResult<TokenResponse>> PostTokenAsync(TokenRequest requestObj)
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<TokenResponse>();
}
using(var client = new ApiHttpClient())
{
var requestMessage = new TokenHttpRequestMessage(requestObj)
{
Method = HttpMethod.Post,
RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/token")),
RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/token")),
};
var response = await Client.SendAsync(requestMessage);
var response = await client.SendAsync(requestMessage);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync<TokenResponse>(response);
@ -29,16 +41,24 @@ namespace Bit.App.Repositories
var responseObj = JsonConvert.DeserializeObject<TokenResponse>(responseContent);
return ApiResult<TokenResponse>.Success(responseObj, response.StatusCode);
}
}
public virtual async Task<ApiResult<TokenResponse>> PostTokenTwoFactorAsync(TokenTwoFactorRequest requestObj)
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<TokenResponse>();
}
using(var client = new ApiHttpClient())
{
var requestMessage = new TokenHttpRequestMessage(requestObj)
{
Method = HttpMethod.Post,
RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/token/two-factor")),
RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/token/two-factor")),
};
var response = await Client.SendAsync(requestMessage);
var response = await client.SendAsync(requestMessage);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync<TokenResponse>(response);
@ -50,3 +70,4 @@ namespace Bit.App.Repositories
}
}
}
}

View File

@ -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<ApiResult<T>> HandleErrorAsync<T>(HttpResponseMessage response)
protected ApiResult HandledNotConnected()
{
return ApiResult.Failed(System.Net.HttpStatusCode.RequestTimeout, new ApiError { Message = "Not connected to the internet." });
}
protected ApiResult<T> HandledNotConnected<T>()
{
return ApiResult<T>.Failed(System.Net.HttpStatusCode.RequestTimeout, new ApiError { Message = "Not connected to the internet." });
}
protected async Task<ApiResult<T>> HandleErrorAsync<T>(HttpResponseMessage response)
{
try
{
@ -34,7 +41,7 @@ namespace Bit.App.Repositories
return ApiResult<T>.Failed(response.StatusCode, new ApiError { Message = "An unknown error has occured." });
}
public async Task<ApiResult> HandleErrorAsync(HttpResponseMessage response)
protected async Task<ApiResult> HandleErrorAsync(HttpResponseMessage response)
{
try
{

View File

@ -5,22 +5,34 @@ 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<ApiResult<CipherResponse>> GetByIdAsync(string id)
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<CipherResponse>();
}
using(var client = new ApiHttpClient())
{
var requestMessage = new TokenHttpRequestMessage()
{
Method = HttpMethod.Get,
RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/", id)),
RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/", id)),
};
var response = await Client.SendAsync(requestMessage);
var response = await client.SendAsync(requestMessage);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync<CipherResponse>(response);
@ -30,16 +42,24 @@ namespace Bit.App.Repositories
var responseObj = JsonConvert.DeserializeObject<CipherResponse>(responseContent);
return ApiResult<CipherResponse>.Success(responseObj, response.StatusCode);
}
}
public virtual async Task<ApiResult<ListResponse<CipherResponse>>> GetAsync()
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<ListResponse<CipherResponse>>();
}
using(var client = new ApiHttpClient())
{
var requestMessage = new TokenHttpRequestMessage()
{
Method = HttpMethod.Get,
RequestUri = new Uri(Client.BaseAddress, ApiRoute),
RequestUri = new Uri(client.BaseAddress, ApiRoute),
};
var response = await Client.SendAsync(requestMessage);
var response = await client.SendAsync(requestMessage);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync<ListResponse<CipherResponse>>(response);
@ -49,16 +69,24 @@ namespace Bit.App.Repositories
var responseObj = JsonConvert.DeserializeObject<ListResponse<CipherResponse>>(responseContent);
return ApiResult<ListResponse<CipherResponse>>.Success(responseObj, response.StatusCode);
}
}
public virtual async Task<ApiResult<CipherHistoryResponse>> GetByRevisionDateWithHistoryAsync(DateTime since)
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<CipherHistoryResponse>();
}
using(var client = new ApiHttpClient())
{
var requestMessage = new TokenHttpRequestMessage()
{
Method = HttpMethod.Get,
RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/history", "?since=", since)),
RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/history", "?since=", since)),
};
var response = await Client.SendAsync(requestMessage);
var response = await client.SendAsync(requestMessage);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync<CipherHistoryResponse>(response);
@ -70,3 +98,4 @@ namespace Bit.App.Repositories
}
}
}
}

View File

@ -4,22 +4,34 @@ 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<DeviceRequest, DeviceResponse, string>, IDeviceApiRepository
{
public DeviceApiRepository(IConnectivity connectivity)
: base(connectivity)
{ }
protected override string ApiRoute => "devices";
public virtual async Task<ApiResult<DeviceResponse>> PutTokenAsync(string identifier, DeviceTokenRequest request)
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<DeviceResponse>();
}
using(var client = new ApiHttpClient())
{
var requestMessage = new TokenHttpRequestMessage(request)
{
Method = HttpMethod.Put,
RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/identifier/", identifier, "/token")),
RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/identifier/", identifier, "/token")),
};
var response = await Client.SendAsync(requestMessage);
var response = await client.SendAsync(requestMessage);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync<DeviceResponse>(response);
@ -31,3 +43,4 @@ namespace Bit.App.Repositories
}
}
}
}

View File

@ -5,22 +5,34 @@ 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<FolderRequest, FolderResponse, string>, IFolderApiRepository
{
public FolderApiRepository(IConnectivity connectivity)
: base(connectivity)
{ }
protected override string ApiRoute => "folders";
public virtual async Task<ApiResult<ListResponse<FolderResponse>>> GetByRevisionDateAsync(DateTime since)
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<ListResponse<FolderResponse>>();
}
using(var client = new ApiHttpClient())
{
var requestMessage = new TokenHttpRequestMessage()
{
Method = HttpMethod.Get,
RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "?since=", since)),
RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "?since=", since)),
};
var response = await Client.SendAsync(requestMessage);
var response = await client.SendAsync(requestMessage);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync<ListResponse<FolderResponse>>(response);
@ -32,3 +44,4 @@ namespace Bit.App.Repositories
}
}
}
}

View File

@ -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<SiteRequest, SiteResponse, string>, ISiteApiRepository
{
public SiteApiRepository(IConnectivity connectivity)
: base(connectivity)
{ }
protected override string ApiRoute => "sites";
}
}

View File

@ -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"));
}
}
}

View File

@ -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<IAuthService>(),
Resolver.Resolve<IConnectivity>(),
Resolver.Resolve<IUserDialogs>(),
Resolver.Resolve<IDatabaseService>(),
Resolver.Resolve<ISyncService>(),
Resolver.Resolve<IFingerprint>(),