push notification services

This commit is contained in:
Kyle Spearrin 2019-05-28 12:01:55 -04:00
parent faccb61a6b
commit 3f11fdaa82
24 changed files with 1982 additions and 1256 deletions

Binary file not shown.

View file

@ -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>

View file

@ -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()

View 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

View 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

File diff suppressed because it is too large Load diff

View 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

View 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"

View 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"
}

Binary file not shown.

View 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();
}
}

View file

@ -0,0 +1,11 @@
using System.Threading.Tasks;
namespace Bit.App.Abstractions
{
public interface IPushNotificationService
{
Task<string> GetTokenAsync();
Task RegisterAsync();
Task UnregisterAsync();
}
}

View file

@ -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()

View 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;
}
}
}

View 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);
}
}
}

View 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

View file

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

View file

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

View file

@ -0,0 +1,7 @@
namespace Bit.Core.Models.Request
{
public class DeviceTokenRequest
{
public string PushToken { get; set; }
}
}

View file

@ -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

View file

@ -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)

View 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);
}
}
}

View 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);
}
}
}

View file

@ -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" />