mirror of
https://github.com/bitwarden/mobile.git
synced 2024-11-27 12:26:31 +01:00
push notification services
This commit is contained in:
parent
faccb61a6b
commit
3f11fdaa82
BIN
src/Android/8bit.keystore.enc
Normal file
BIN
src/Android/8bit.keystore.enc
Normal file
Binary file not shown.
@ -30,6 +30,7 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AndroidLinkMode>None</AndroidLinkMode>
|
||||
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@ -59,6 +60,9 @@
|
||||
<PackageReference Include="Xamarin.Essentials">
|
||||
<Version>1.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Xamarin.Firebase.Messaging">
|
||||
<Version>60.1142.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Xamarin.Forms" Version="3.6.0.344457" />
|
||||
<PackageReference Include="Xamarin.Android.Support.Design" Version="28.0.0.1" />
|
||||
<PackageReference Include="Xamarin.Android.Support.v7.AppCompat" Version="28.0.0.1" />
|
||||
@ -80,6 +84,8 @@
|
||||
<Compile Include="Autofill\FilledItem.cs" />
|
||||
<Compile Include="Autofill\Parser.cs" />
|
||||
<Compile Include="Autofill\SavedItem.cs" />
|
||||
<Compile Include="Push\FirebaseInstanceIdService.cs" />
|
||||
<Compile Include="Push\FirebaseMessagingService.cs" />
|
||||
<Compile Include="Receivers\LockAlarmReceiver.cs" />
|
||||
<Compile Include="Receivers\PackageReplacedReceiver.cs" />
|
||||
<Compile Include="Renderers\ExtendedSliderRenderer.cs" />
|
||||
@ -89,6 +95,7 @@
|
||||
<Compile Include="Renderers\CustomSearchBarRenderer.cs" />
|
||||
<Compile Include="Renderers\ExtendedListViewRenderer.cs" />
|
||||
<Compile Include="Renderers\HybridWebViewRenderer.cs" />
|
||||
<Compile Include="Services\AndroidPushNotificationService.cs" />
|
||||
<Compile Include="SplashActivity.cs" />
|
||||
<Compile Include="Renderers\BoxedView\BoxedViewRecyclerAdapter.cs" />
|
||||
<Compile Include="Renderers\BoxedView\BoxedViewRenderer.cs" />
|
||||
@ -111,6 +118,10 @@
|
||||
<AndroidAsset Include="Assets\FontAwesome.ttf" />
|
||||
<AndroidAsset Include="Assets\RobotoMono_Regular.ttf" />
|
||||
<AndroidAsset Include="Assets\MaterialIcons_Regular.ttf" />
|
||||
<None Include="8bit.keystore.enc" />
|
||||
<None Include="ci-build-apks.ps1" />
|
||||
<GoogleServicesJson Include="google-services.json" />
|
||||
<GoogleServicesJson Include="google-services.json.enc" />
|
||||
<None Include="Properties\AndroidManifest.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -76,6 +76,20 @@ namespace Bit.Droid
|
||||
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
|
||||
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
||||
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
||||
|
||||
// Push
|
||||
#if FDROID
|
||||
container.RegisterSingleton<IPushNotificationListener, NoopPushNotificationListener>();
|
||||
container.RegisterSingleton<IPushNotificationService, NoopPushNotificationService>();
|
||||
#else
|
||||
var notificationListenerService = new PushNotificationListenerService();
|
||||
ServiceContainer.Register<IPushNotificationListenerService>(
|
||||
"pushNotificationListenerService", notificationListenerService);
|
||||
var androidPushNotificationService = new AndroidPushNotificationService(
|
||||
mobileStorageService, notificationListenerService);
|
||||
ServiceContainer.Register<IPushNotificationService>(
|
||||
"pushNotificationService", androidPushNotificationService);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void Bootstrap()
|
||||
|
25
src/Android/Push/FirebaseInstanceIdService.cs
Normal file
25
src/Android/Push/FirebaseInstanceIdService.cs
Normal file
@ -0,0 +1,25 @@
|
||||
#if !FDROID
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Firebase.Iid;
|
||||
|
||||
namespace Bit.Droid.Push
|
||||
{
|
||||
[Service]
|
||||
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
|
||||
public class FirebaseInstanceIdService : Firebase.Iid.FirebaseInstanceIdService
|
||||
{
|
||||
public override void OnTokenRefresh()
|
||||
{
|
||||
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
|
||||
storageService.SaveAsync(Constants.PushRegisteredTokenKey, FirebaseInstanceId.Instance.Token);
|
||||
pushNotificationService.RegisterAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
42
src/Android/Push/FirebaseMessagingService.cs
Normal file
42
src/Android/Push/FirebaseMessagingService.cs
Normal file
@ -0,0 +1,42 @@
|
||||
#if !FDROID
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Firebase.Messaging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.Droid.Push
|
||||
{
|
||||
[Service]
|
||||
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
|
||||
public class FirebaseMessagingService : Firebase.Messaging.FirebaseMessagingService
|
||||
{
|
||||
public override void OnMessageReceived(RemoteMessage message)
|
||||
{
|
||||
if(message?.Data == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var data = message.Data.ContainsKey("data") ? message.Data["data"] : null;
|
||||
if(data == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
var obj = JObject.Parse(data);
|
||||
var listener = ServiceContainer.Resolve<IPushNotificationListenerService>(
|
||||
"pushNotificationListenerService");
|
||||
listener.OnMessageAsync(obj, Device.Android);
|
||||
}
|
||||
catch(JsonReaderException ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
2538
src/Android/Resources/Resource.designer.cs
generated
2538
src/Android/Resources/Resource.designer.cs
generated
File diff suppressed because it is too large
Load Diff
50
src/Android/Services/AndroidPushNotificationService.cs
Normal file
50
src/Android/Services/AndroidPushNotificationService.cs
Normal file
@ -0,0 +1,50 @@
|
||||
#if !FDROID
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.Droid.Services
|
||||
{
|
||||
public class AndroidPushNotificationService : IPushNotificationService
|
||||
{
|
||||
private readonly IStorageService _storageService;
|
||||
private readonly IPushNotificationListenerService _pushNotificationListenerService;
|
||||
|
||||
public AndroidPushNotificationService(
|
||||
IStorageService storageService,
|
||||
IPushNotificationListenerService pushNotificationListenerService)
|
||||
{
|
||||
_storageService = storageService;
|
||||
_pushNotificationListenerService = pushNotificationListenerService;
|
||||
}
|
||||
|
||||
public async Task<string> GetTokenAsync()
|
||||
{
|
||||
return await _storageService.GetAsync<string>(Constants.PushCurrentTokenKey);
|
||||
}
|
||||
|
||||
public async Task RegisterAsync()
|
||||
{
|
||||
var registeredToken = await _storageService.GetAsync<string>(Constants.PushRegisteredTokenKey);
|
||||
var currentToken = await GetTokenAsync();
|
||||
if(!string.IsNullOrWhiteSpace(registeredToken) && registeredToken != currentToken)
|
||||
{
|
||||
await _pushNotificationListenerService.OnRegisteredAsync(registeredToken, Device.Android);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _storageService.SaveAsync(Constants.PushLastRegistrationDateKey, DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
|
||||
public Task UnregisterAsync()
|
||||
{
|
||||
// Do we ever need to unregister?
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
87
src/Android/ci-build-apks.ps1
Normal file
87
src/Android/ci-build-apks.ps1
Normal file
@ -0,0 +1,87 @@
|
||||
$rootPath = $env:APPVEYOR_BUILD_FOLDER;
|
||||
|
||||
$androidPath = $($rootPath + "\src\Android\Android.csproj");
|
||||
$appPath = $($rootPath + "\src\App\App.csproj");
|
||||
|
||||
echo "##### Increment Version"
|
||||
|
||||
$androidManifest = $($rootPath + "\src\Android\Properties\AndroidManifest.xml");
|
||||
|
||||
$xml=New-Object XML;
|
||||
$xml.Load($androidManifest);
|
||||
|
||||
$node=$xml.SelectNodes("/manifest");
|
||||
$node.SetAttribute("android:versionCode", $env:APPVEYOR_BUILD_NUMBER);
|
||||
|
||||
$xml.Save($androidManifest);
|
||||
|
||||
echo "##### Decrypt Keystore"
|
||||
|
||||
$encKeystorePath = $($rootPath + "\src\Android\8bit.keystore.enc");
|
||||
$secureFilePath = $($rootPath + "\secure-file\tools\secure-file.exe");
|
||||
|
||||
Invoke-Expression "& `"$secureFilePath`" -decrypt $($encKeystorePath) -secret $($env:keystore_dec_secret)"
|
||||
|
||||
echo "##### Sign Release Configuration"
|
||||
|
||||
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" "/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:keystore_password)" "/p:AndroidSigningKeyStore=8bit.keystore" "/p:AndroidSigningStorePass=$($env:keystore_password)" "/v:quiet"
|
||||
|
||||
echo "##### Copy Release apk to project root"
|
||||
|
||||
$signedApkPath = $($rootPath + "\src\Android\bin\Release\com.x8bit.bitwarden-Signed.apk");
|
||||
$signedApkDestPath = $($rootPath + "\com.x8bit.bitwarden-" + $env:APPVEYOR_BUILD_NUMBER + ".apk");
|
||||
|
||||
Copy-Item $signedApkPath $signedApkDestPath
|
||||
|
||||
echo "##### Clean Android and App"
|
||||
|
||||
msbuild "$($androidPath)" "/t:Clean" "/p:Configuration=FDroid"
|
||||
msbuild "$($appPath)" "/t:Clean" "/p:Configuration=FDroid"
|
||||
|
||||
echo "##### Backup project files"
|
||||
|
||||
Copy-Item $androidPath $($androidPath + ".original");
|
||||
Copy-Item $appPath $($appPath + ".original");
|
||||
|
||||
echo "##### Uninstall from Android.csproj"
|
||||
|
||||
$xml=New-Object XML;
|
||||
$xml.Load($androidPath);
|
||||
|
||||
$ns=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
|
||||
$ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI);
|
||||
|
||||
$firebaseNode=$xml.SelectSingleNode("/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Firebase.Messaging']", $ns);
|
||||
$firebaseNode.ParentNode.RemoveChild($firebaseNode);
|
||||
|
||||
$xml.Save($androidPath);
|
||||
|
||||
echo "##### Uninstall from App.csproj"
|
||||
|
||||
$xml=New-Object XML;
|
||||
$xml.Load($appPath);
|
||||
|
||||
$hockeyNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='HockeySDK.Xamarin']");
|
||||
$hockeyNode.ParentNode.RemoveChild($hockeyNode);
|
||||
|
||||
$xml.Save($appPath);
|
||||
|
||||
echo "##### Restore NuGet"
|
||||
|
||||
$nugetPath = $($rootPath + "\nuget.exe");
|
||||
|
||||
Invoke-Expression "& `"$nugetPath`" restore"
|
||||
|
||||
echo "##### Build and Sign FDroid Configuration"
|
||||
|
||||
msbuild "$($androidPath)" "/logger:C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" "/p:Configuration=FDroid"
|
||||
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=FDroid" "/p:AndroidKeyStore=true" "/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:keystore_password)" "/p:AndroidSigningKeyStore=8bit.keystore" "/p:AndroidSigningStorePass=$($env:keystore_password)" "/v:quiet"
|
||||
|
||||
echo "##### Copy FDroid apk to project root"
|
||||
|
||||
$signedApkPath = $($rootPath + "\src\Android\bin\FDroid\com.x8bit.bitwarden-Signed.apk");
|
||||
$signedApkDestPath = $($rootPath + "\com.x8bit.bitwarden-fdroid-" + $env:APPVEYOR_BUILD_NUMBER + ".apk");
|
||||
|
||||
Copy-Item $signedApkPath $signedApkDestPath
|
||||
|
||||
echo "##### Done"
|
42
src/Android/google-services.json
Normal file
42
src/Android/google-services.json
Normal file
@ -0,0 +1,42 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "1093287226212",
|
||||
"firebase_url": "https://bitwarden-dev.firebaseio.com",
|
||||
"project_id": "bitwarden-dev",
|
||||
"storage_bucket": "bitwarden-dev.appspot.com"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:1093287226212:android:f8d67b786db1b844",
|
||||
"android_client_info": {
|
||||
"package_name": "com.x8bit.bitwarden"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "1093287226212-m4mv8ho387tdgosc9lsltnmruul7ouo0.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyA4Xkn0do7Ky_OLff2L_7MXeNK6s-JVgXg"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"analytics_service": {
|
||||
"status": 1
|
||||
},
|
||||
"appinvite_service": {
|
||||
"status": 1,
|
||||
"other_platform_oauth_client": []
|
||||
},
|
||||
"ads_service": {
|
||||
"status": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
BIN
src/Android/google-services.json.enc
Normal file
BIN
src/Android/google-services.json.enc
Normal file
Binary file not shown.
14
src/App/Abstractions/IPushNotificationListenerService.cs
Normal file
14
src/App/Abstractions/IPushNotificationListenerService.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface IPushNotificationListenerService
|
||||
{
|
||||
Task OnMessageAsync(JObject values, string device);
|
||||
Task OnRegisteredAsync(string token, string device);
|
||||
void OnUnregistered(string device);
|
||||
void OnError(string message, string device);
|
||||
bool ShouldShowNotification();
|
||||
}
|
||||
}
|
11
src/App/Abstractions/IPushNotificationService.cs
Normal file
11
src/App/Abstractions/IPushNotificationService.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface IPushNotificationService
|
||||
{
|
||||
Task<string> GetTokenAsync();
|
||||
Task RegisterAsync();
|
||||
Task UnregisterAsync();
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
@ -12,6 +14,8 @@ namespace Bit.App.Pages
|
||||
{
|
||||
private readonly IBroadcasterService _broadcasterService;
|
||||
private readonly ISyncService _syncService;
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
private readonly IStorageService _storageService;
|
||||
private readonly GroupingsPageViewModel _vm;
|
||||
private readonly string _pageName;
|
||||
|
||||
@ -23,6 +27,8 @@ namespace Bit.App.Pages
|
||||
SetActivityIndicator(_mainContent);
|
||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
_vm = BindingContext as GroupingsPageViewModel;
|
||||
_vm.Page = this;
|
||||
_vm.MainPage = mainPage;
|
||||
@ -71,6 +77,30 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
}, _mainContent);
|
||||
|
||||
// Push registration
|
||||
var lastPushRegistration = await _storageService.GetAsync<DateTime?>(Constants.PushLastRegistrationDateKey);
|
||||
lastPushRegistration = lastPushRegistration.GetValueOrDefault(DateTime.MinValue);
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
var pushPromptShow = await _storageService.GetAsync<bool?>(Constants.PushInitialPromptShownKey);
|
||||
if(!pushPromptShow.GetValueOrDefault(false))
|
||||
{
|
||||
await _storageService.SaveAsync(Constants.PushInitialPromptShownKey, true);
|
||||
await DisplayAlert(AppResources.EnableAutomaticSyncing, AppResources.PushNotificationAlert,
|
||||
AppResources.OkGotIt);
|
||||
}
|
||||
if(!pushPromptShow.GetValueOrDefault(false) ||
|
||||
DateTime.UtcNow - lastPushRegistration > TimeSpan.FromDays(1))
|
||||
{
|
||||
await _pushNotificationService.RegisterAsync();
|
||||
}
|
||||
}
|
||||
else if(Device.RuntimePlatform == Device.Android &&
|
||||
DateTime.UtcNow - lastPushRegistration > TimeSpan.FromDays(1))
|
||||
{
|
||||
await _pushNotificationService.RegisterAsync();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
|
32
src/App/Services/NoopPushNotificationListenerService.cs
Normal file
32
src/App/Services/NoopPushNotificationListenerService.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Bit.App.Abstractions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.App.Services
|
||||
{
|
||||
public class NoopPushNotificationListenerService : IPushNotificationListenerService
|
||||
{
|
||||
public Task OnMessageAsync(JObject value, string deviceType)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task OnRegisteredAsync(string token, string deviceType)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void OnUnregistered(string deviceType)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnError(string message, string deviceType)
|
||||
{
|
||||
}
|
||||
|
||||
public bool ShouldShowNotification()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
23
src/App/Services/NoopPushNotificationService.cs
Normal file
23
src/App/Services/NoopPushNotificationService.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
|
||||
namespace Bit.App.Services
|
||||
{
|
||||
public class NoopPushNotificationService : IPushNotificationService
|
||||
{
|
||||
public Task<string> GetTokenAsync()
|
||||
{
|
||||
return Task.FromResult(null as string);
|
||||
}
|
||||
|
||||
public Task RegisterAsync()
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task UnregisterAsync()
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
}
|
187
src/App/Services/PushNotificationListenerService.cs
Normal file
187
src/App/Services/PushNotificationListenerService.cs
Normal file
@ -0,0 +1,187 @@
|
||||
#if !FDROID
|
||||
using System.Diagnostics;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Bit.App.Abstractions;
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Models.Response;
|
||||
using Bit.Core.Exceptions;
|
||||
|
||||
namespace Bit.App.Services
|
||||
{
|
||||
public class PushNotificationListenerService : IPushNotificationListenerService
|
||||
{
|
||||
private bool _showNotification;
|
||||
private bool _resolved;
|
||||
private IStorageService _storageService;
|
||||
private ISyncService _syncService;
|
||||
private IUserService _userService;
|
||||
private IAppIdService _appIdService;
|
||||
private IApiService _apiService;
|
||||
private IMessagingService _messagingService;
|
||||
|
||||
public async Task OnMessageAsync(JObject value, string deviceType)
|
||||
{
|
||||
Resolve();
|
||||
if(value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_showNotification = false;
|
||||
Debug.WriteLine("Message Arrived: {0}", JsonConvert.SerializeObject(value));
|
||||
|
||||
NotificationResponse notification = null;
|
||||
if(deviceType == Device.Android)
|
||||
{
|
||||
notification = value.ToObject<NotificationResponse>();
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!value.TryGetValue("data", StringComparison.OrdinalIgnoreCase, out JToken dataToken) ||
|
||||
dataToken == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
notification = dataToken.ToObject<NotificationResponse>();
|
||||
}
|
||||
|
||||
var appId = await _appIdService.GetAppIdAsync();
|
||||
if(notification?.Payload == null || notification.ContextId == appId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var myUserId = await _userService.GetUserIdAsync();
|
||||
var isAuthenticated = await _userService.IsAuthenticatedAsync();
|
||||
switch(notification.Type)
|
||||
{
|
||||
case NotificationType.SyncCipherUpdate:
|
||||
case NotificationType.SyncCipherCreate:
|
||||
var cipherCreateUpdateMessage = JsonConvert.DeserializeObject<SyncCipherNotification>(
|
||||
notification.Payload);
|
||||
if(isAuthenticated && cipherCreateUpdateMessage.UserId == myUserId)
|
||||
{
|
||||
await _syncService.SyncUpsertCipherAsync(cipherCreateUpdateMessage,
|
||||
notification.Type == NotificationType.SyncCipherUpdate);
|
||||
}
|
||||
break;
|
||||
case NotificationType.SyncFolderUpdate:
|
||||
case NotificationType.SyncFolderCreate:
|
||||
var folderCreateUpdateMessage = JsonConvert.DeserializeObject<SyncFolderNotification>(
|
||||
notification.Payload);
|
||||
if(isAuthenticated && folderCreateUpdateMessage.UserId == myUserId)
|
||||
{
|
||||
await _syncService.SyncUpsertFolderAsync(folderCreateUpdateMessage,
|
||||
notification.Type == NotificationType.SyncFolderUpdate);
|
||||
}
|
||||
break;
|
||||
case NotificationType.SyncLoginDelete:
|
||||
case NotificationType.SyncCipherDelete:
|
||||
var loginDeleteMessage = JsonConvert.DeserializeObject<SyncCipherNotification>(
|
||||
notification.Payload);
|
||||
if(isAuthenticated && loginDeleteMessage.UserId == myUserId)
|
||||
{
|
||||
await _syncService.SyncDeleteCipherAsync(loginDeleteMessage);
|
||||
}
|
||||
break;
|
||||
case NotificationType.SyncFolderDelete:
|
||||
var folderDeleteMessage = JsonConvert.DeserializeObject<SyncFolderNotification>(
|
||||
notification.Payload);
|
||||
if(isAuthenticated && folderDeleteMessage.UserId == myUserId)
|
||||
{
|
||||
await _syncService.SyncDeleteFolderAsync(folderDeleteMessage);
|
||||
}
|
||||
break;
|
||||
case NotificationType.SyncCiphers:
|
||||
case NotificationType.SyncVault:
|
||||
case NotificationType.SyncSettings:
|
||||
if(isAuthenticated)
|
||||
{
|
||||
await _syncService.FullSyncAsync(false);
|
||||
}
|
||||
break;
|
||||
case NotificationType.SyncOrgKeys:
|
||||
if(isAuthenticated)
|
||||
{
|
||||
await _apiService.RefreshIdentityTokenAsync();
|
||||
await _syncService.FullSyncAsync(true);
|
||||
}
|
||||
break;
|
||||
case NotificationType.LogOut:
|
||||
if(isAuthenticated)
|
||||
{
|
||||
_messagingService.Send("logout");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnRegisteredAsync(string token, string deviceType)
|
||||
{
|
||||
Resolve();
|
||||
Debug.WriteLine(string.Format("Push Notification - Device Registered - Token : {0}", token));
|
||||
var isAuthenticated = await _userService.IsAuthenticatedAsync();
|
||||
if(!isAuthenticated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var appId = await _appIdService.GetAppIdAsync();
|
||||
try
|
||||
{
|
||||
await _apiService.PutDeviceTokenAsync(appId,
|
||||
new Core.Models.Request.DeviceTokenRequest { PushToken = token });
|
||||
Debug.WriteLine("Registered device with server.");
|
||||
await _storageService.SaveAsync(Constants.PushLastRegistrationDateKey, DateTime.UtcNow);
|
||||
if(deviceType == Device.Android)
|
||||
{
|
||||
await _storageService.SaveAsync(Constants.PushCurrentTokenKey, token);
|
||||
}
|
||||
}
|
||||
catch(ApiException)
|
||||
{
|
||||
Debug.WriteLine("Failed to register device.");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnUnregistered(string deviceType)
|
||||
{
|
||||
Debug.WriteLine("Push Notification - Device Unnregistered");
|
||||
}
|
||||
|
||||
public void OnError(string message, string deviceType)
|
||||
{
|
||||
Debug.WriteLine(string.Format("Push notification error - {0}", message));
|
||||
}
|
||||
|
||||
public bool ShouldShowNotification()
|
||||
{
|
||||
return _showNotification;
|
||||
}
|
||||
|
||||
private void Resolve()
|
||||
{
|
||||
if(_resolved)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||
_userService = ServiceContainer.Resolve<IUserService>("userService");
|
||||
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
||||
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_resolved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -45,5 +45,6 @@ namespace Bit.Core.Abstractions
|
||||
string organizationId);
|
||||
Task<List<BreachAccountResponse>> GetHibpBreachAsync(string username);
|
||||
Task PostTwoFactorEmailAsync(TwoFactorEmailRequest request);
|
||||
Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request);
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,10 @@
|
||||
public static string AccessibilityAutofillPasswordFieldKey = "accessibilityAutofillPasswordField";
|
||||
public static string AccessibilityAutofillPersistNotificationKey = "accessibilityAutofillPersistNotification";
|
||||
public static string DisableFaviconKey = "disableFavicon";
|
||||
public static string PushRegisteredTokenKey = "pushRegisteredToken";
|
||||
public static string PushCurrentTokenKey = "pushCurrentToken";
|
||||
public static string PushLastRegistrationDateKey = "pushLastRegistrationDate";
|
||||
public static string PushInitialPromptShownKey = "pushInitialPromptShown";
|
||||
public const int SelectFileRequestCode = 42;
|
||||
public const int SelectFilePermissionRequestCode = 43;
|
||||
}
|
||||
|
7
src/Core/Models/Request/DeviceTokenRequest.cs
Normal file
7
src/Core/Models/Request/DeviceTokenRequest.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Bit.Core.Models.Request
|
||||
{
|
||||
public class DeviceTokenRequest
|
||||
{
|
||||
public string PushToken { get; set; }
|
||||
}
|
||||
}
|
@ -4,10 +4,12 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Bit.Core.Models.Response
|
||||
{
|
||||
public class PushNotificationResponse
|
||||
public class NotificationResponse
|
||||
{
|
||||
public string ContextId { get; set; }
|
||||
public NotificationType Type { get; set; }
|
||||
public string Payload { get; set; }
|
||||
public object PayloadObject { get; set; }
|
||||
}
|
||||
|
||||
public class SyncCipherNotification
|
||||
|
@ -281,6 +281,16 @@ namespace Bit.Core.Services
|
||||
|
||||
#endregion
|
||||
|
||||
#region Device APIs
|
||||
|
||||
public Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request)
|
||||
{
|
||||
return SendAsync<DeviceTokenRequest, object>(
|
||||
HttpMethod.Post, $"identifier/{identifier}/token", request, true, false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region HIBP APIs
|
||||
|
||||
public Task<List<BreachAccountResponse>> GetHibpBreachAsync(string username)
|
||||
|
66
src/iOS/Services/iOSPushNotificationHandler.cs
Normal file
66
src/iOS/Services/iOSPushNotificationHandler.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using Foundation;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.iOS.Services
|
||||
{
|
||||
public class iOSPushNotificationHandler
|
||||
{
|
||||
private const string TokenSetting = "token";
|
||||
private const string DomainName = "iOSPushNotificationService";
|
||||
|
||||
private readonly IPushNotificationListener _pushNotificationListener;
|
||||
|
||||
public iOSPushNotificationHandler(
|
||||
IPushNotificationListener pushNotificationListener)
|
||||
{
|
||||
_pushNotificationListener = pushNotificationListener;
|
||||
}
|
||||
|
||||
public void OnMessageReceived(NSDictionary userInfo)
|
||||
{
|
||||
var json = DictionaryToJson(userInfo);
|
||||
var values = JObject.Parse(json);
|
||||
var keyAps = new NSString("aps");
|
||||
if(userInfo.ContainsKey(keyAps) && userInfo.ValueForKey(keyAps) is NSDictionary aps)
|
||||
{
|
||||
foreach(var apsKey in aps)
|
||||
{
|
||||
if(!values.TryGetValue(apsKey.Key.ToString(), out JToken temp))
|
||||
{
|
||||
values.Add(apsKey.Key.ToString(), apsKey.Value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
_pushNotificationListener.OnMessage(values, Device.iOS);
|
||||
}
|
||||
|
||||
public void OnErrorReceived(NSError error)
|
||||
{
|
||||
Debug.WriteLine("{0} - Registration Failed.", DomainName);
|
||||
_pushNotificationListener.OnError(error.LocalizedDescription, Device.iOS);
|
||||
}
|
||||
|
||||
public void OnRegisteredSuccess(NSData token)
|
||||
{
|
||||
Debug.WriteLine("{0} - Successfully Registered.", DomainName);
|
||||
var trimmedDeviceToken = token.Description;
|
||||
if(!string.IsNullOrWhiteSpace(trimmedDeviceToken))
|
||||
{
|
||||
trimmedDeviceToken = trimmedDeviceToken.Trim('<').Trim('>').Trim().Replace(" ", string.Empty);
|
||||
}
|
||||
Console.WriteLine("{0} - Token: {1}", DomainName, trimmedDeviceToken);
|
||||
_pushNotificationListener.OnRegistered(trimmedDeviceToken, Device.iOS);
|
||||
NSUserDefaults.StandardUserDefaults.SetString(trimmedDeviceToken, TokenSetting);
|
||||
NSUserDefaults.StandardUserDefaults.Synchronize();
|
||||
}
|
||||
|
||||
private static string DictionaryToJson(NSDictionary dictionary)
|
||||
{
|
||||
var json = NSJsonSerialization.Serialize(dictionary, NSJsonWritingOptions.PrettyPrinted, out NSError error);
|
||||
return json.ToString(NSStringEncoding.UTF8);
|
||||
}
|
||||
}
|
||||
}
|
36
src/iOS/Services/iOSPushNotificationService.cs
Normal file
36
src/iOS/Services/iOSPushNotificationService.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
|
||||
namespace Bit.iOS.Services
|
||||
{
|
||||
public class iOSPushNotificationService : IPushNotificationService
|
||||
{
|
||||
private const string TokenSetting = "token";
|
||||
|
||||
public Task<string> GetTokenAsync()
|
||||
{
|
||||
return Task.FromResult(NSUserDefaults.StandardUserDefaults.StringForKey(TokenSetting));
|
||||
}
|
||||
|
||||
public Task RegisterAsync()
|
||||
{
|
||||
var userNotificationTypes = UIUserNotificationType.Alert | UIUserNotificationType.Badge |
|
||||
UIUserNotificationType.Sound;
|
||||
var settings = UIUserNotificationSettings.GetSettingsForTypes(userNotificationTypes, null);
|
||||
UIApplication.SharedApplication.RegisterUserNotificationSettings(settings);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task UnregisterAsync()
|
||||
{
|
||||
UIApplication.SharedApplication.UnregisterForRemoteNotifications();
|
||||
// TODO: unregister call
|
||||
// _pushNotificationListener.OnUnregistered(Device.iOS);
|
||||
NSUserDefaults.StandardUserDefaults.SetString(string.Empty, TokenSetting);
|
||||
NSUserDefaults.StandardUserDefaults.Synchronize();
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
}
|
@ -94,6 +94,8 @@
|
||||
<Compile Include="AppDelegate.cs" />
|
||||
<Compile Include="Renderers\HybridWebViewRenderer.cs" />
|
||||
<Compile Include="Services\DeviceActionService.cs" />
|
||||
<Compile Include="Services\iOSPushNotificationHandler.cs" />
|
||||
<Compile Include="Services\iOSPushNotificationService.cs" />
|
||||
<None Include="Entitlements.plist" />
|
||||
<None Include="Info.plist" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
|
Loading…
Reference in New Issue
Block a user