diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7650fadca..20128af67 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -397,6 +397,15 @@ jobs: perl -0777 -pi.bak -e 's/CFBundleVersion<\/key>\s*1<\/string>/CFBundleVersion<\/key>\n\t'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Autofill/Info.plist shell: bash + - name: Update Entitlements + run: | + echo "########################################" + echo "##### Updating Entitlements" + echo "########################################" + + perl -0777 -pi.bak -e 's/aps-environment<\/key>\s*development<\/string>/aps-environment<\/key>\n\tproduction<\/string>/' ./src/iOS/Entitlements.plist + shell: bash + - name: Set up Keychain env: KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }} diff --git a/src/Android/Services/AndroidPushNotificationService.cs b/src/Android/Services/AndroidPushNotificationService.cs index 975cbd2db..097c5f708 100644 --- a/src/Android/Services/AndroidPushNotificationService.cs +++ b/src/Android/Services/AndroidPushNotificationService.cs @@ -1,6 +1,7 @@ #if !FDROID using System; using System.Threading.Tasks; +using AndroidX.Core.App; using Bit.App.Abstractions; using Bit.Core; using Bit.Core.Abstractions; @@ -21,6 +22,8 @@ namespace Bit.Droid.Services _pushNotificationListenerService = pushNotificationListenerService; } + public bool IsRegisteredForPush => NotificationManagerCompat.From(Android.App.Application.Context)?.AreNotificationsEnabled() ?? false; + public async Task GetTokenAsync() { return await _storageService.GetAsync(Constants.PushCurrentTokenKey); diff --git a/src/App/Abstractions/IPushNotificationService.cs b/src/App/Abstractions/IPushNotificationService.cs index ae227f292..c4e3827cb 100644 --- a/src/App/Abstractions/IPushNotificationService.cs +++ b/src/App/Abstractions/IPushNotificationService.cs @@ -4,6 +4,7 @@ namespace Bit.App.Abstractions { public interface IPushNotificationService { + bool IsRegisteredForPush { get; } Task GetTokenAsync(); Task RegisterAsync(); Task UnregisterAsync(); diff --git a/src/App/Pages/Accounts/LockPageViewModel.cs b/src/App/Pages/Accounts/LockPageViewModel.cs index 0cc3408e6..42ba9d54a 100644 --- a/src/App/Pages/Accounts/LockPageViewModel.cs +++ b/src/App/Pages/Accounts/LockPageViewModel.cs @@ -1,15 +1,14 @@ -using Bit.App.Abstractions; -using Bit.App.Models; +using System; +using System.Threading.Tasks; +using Bit.App.Abstractions; using Bit.App.Resources; +using Bit.App.Utilities; using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.Domain; -using Bit.Core.Utilities; -using System; -using System.Threading.Tasks; -using Bit.App.Utilities; using Bit.Core.Models.Request; +using Bit.Core.Utilities; using Xamarin.Forms; namespace Bit.App.Pages @@ -135,7 +134,7 @@ namespace Bit.App.Pages // Users with key connector and without biometric or pin has no MP to unlock with _usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector(); - if ( _usingKeyConnector && !(BiometricLock || PinLock)) + if (_usingKeyConnector && !(BiometricLock || PinLock)) { await _vaultTimeoutService.LogOutAsync(); } diff --git a/src/App/Services/NoopPushNotificationService.cs b/src/App/Services/NoopPushNotificationService.cs index edf501a73..61dfc8c26 100644 --- a/src/App/Services/NoopPushNotificationService.cs +++ b/src/App/Services/NoopPushNotificationService.cs @@ -5,6 +5,8 @@ namespace Bit.App.Services { public class NoopPushNotificationService : IPushNotificationService { + public bool IsRegisteredForPush => false; + public Task GetTokenAsync() { return Task.FromResult(null as string); diff --git a/src/iOS/Entitlements.plist b/src/iOS/Entitlements.plist index 6e6f519c6..fcf986d4b 100644 --- a/src/iOS/Entitlements.plist +++ b/src/iOS/Entitlements.plist @@ -1,4 +1,4 @@ - + @@ -25,5 +25,7 @@ webcredentials:bitwarden.com + aps-environment + development diff --git a/src/iOS/Services/iOSPushNotificationHandler.cs b/src/iOS/Services/iOSPushNotificationHandler.cs index c2cd5d6be..8d8c5a022 100644 --- a/src/iOS/Services/iOSPushNotificationHandler.cs +++ b/src/iOS/Services/iOSPushNotificationHandler.cs @@ -1,13 +1,15 @@ -using Bit.App.Abstractions; -using Foundation; -using Newtonsoft.Json.Linq; -using System; +using System; using System.Diagnostics; +using Bit.App.Abstractions; +using Foundation; +using Microsoft.AppCenter.Crashes; +using Newtonsoft.Json.Linq; +using UserNotifications; using Xamarin.Forms; namespace Bit.iOS.Services { - public class iOSPushNotificationHandler + public class iOSPushNotificationHandler : NSObject, IUNUserNotificationCenterDelegate { private const string TokenSetting = "token"; private const string DomainName = "iOSPushNotificationService"; @@ -22,20 +24,27 @@ namespace Bit.iOS.Services 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) + try { - foreach (var apsKey in aps) + var json = DictionaryToJson(userInfo); + var values = JObject.Parse(json); + var keyAps = new NSString("aps"); + if (userInfo.ContainsKey(keyAps) && userInfo.ValueForKey(keyAps) is NSDictionary aps) { - if (!values.TryGetValue(apsKey.Key.ToString(), out JToken temp)) + foreach (var apsKey in aps) { - values.Add(apsKey.Key.ToString(), apsKey.Value.ToString()); + if (!values.TryGetValue(apsKey.Key.ToString(), out JToken temp)) + { + values.Add(apsKey.Key.ToString(), apsKey.Value.ToString()); + } } } + _pushNotificationListenerService.OnMessageAsync(values, Device.iOS); + } + catch (Exception ex) + { + Crashes.TrackError(ex); } - _pushNotificationListenerService.OnMessageAsync(values, Device.iOS); } public void OnErrorReceived(NSError error) @@ -47,9 +56,15 @@ namespace Bit.iOS.Services public void OnRegisteredSuccess(NSData token) { Debug.WriteLine("{0} - Successfully Registered.", DomainName); + var hexDeviceToken = BitConverter.ToString(token.ToArray()) - .Replace("-", string.Empty).ToLowerInvariant(); - Console.WriteLine("{0} - Token: {1}", DomainName, hexDeviceToken); + .Replace("-", string.Empty) + .ToLowerInvariant(); + + Debug.WriteLine("{0} - Token: {1}", DomainName, hexDeviceToken); + + UNUserNotificationCenter.Current.Delegate = this; + _pushNotificationListenerService.OnRegisteredAsync(hexDeviceToken, Device.iOS); NSUserDefaults.StandardUserDefaults.SetString(hexDeviceToken, TokenSetting); NSUserDefaults.StandardUserDefaults.Synchronize(); @@ -60,5 +75,29 @@ namespace Bit.iOS.Services var json = NSJsonSerialization.Serialize(dictionary, NSJsonWritingOptions.PrettyPrinted, out NSError error); return json.ToString(NSStringEncoding.UTF8); } + + // To receive notifications in foreground on iOS 10 devices. + [Export("userNotificationCenter:willPresentNotification:withCompletionHandler:")] + public void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action completionHandler) + { + Debug.WriteLine($"{DomainName} WillPresentNotification {notification?.Request?.Content?.UserInfo}"); + + OnMessageReceived(notification?.Request?.Content?.UserInfo); + completionHandler(UNNotificationPresentationOptions.Alert); + } + + [Export("userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:")] + public void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler) + { + Debug.WriteLine($"{DomainName} DidReceiveNotificationResponse {response?.Notification?.Request?.Content?.UserInfo}"); + + if (response.IsDefaultAction) + { + OnMessageReceived(response?.Notification?.Request?.Content?.UserInfo); + } + + // Inform caller it has been handled + completionHandler(); + } } } diff --git a/src/iOS/Services/iOSPushNotificationService.cs b/src/iOS/Services/iOSPushNotificationService.cs index dedd715a5..ceab64c25 100644 --- a/src/iOS/Services/iOSPushNotificationService.cs +++ b/src/iOS/Services/iOSPushNotificationService.cs @@ -1,11 +1,14 @@ -using System.Threading.Tasks; +using System; +using System.Diagnostics; +using System.Threading.Tasks; using Bit.App.Abstractions; using Foundation; using UIKit; +using UserNotifications; namespace Bit.iOS.Services { - public class iOSPushNotificationService : IPushNotificationService + public class iOSPushNotificationService : NSObject, IPushNotificationService, IUNUserNotificationCenterDelegate { private const string TokenSetting = "token"; @@ -14,13 +17,31 @@ namespace Bit.iOS.Services return Task.FromResult(NSUserDefaults.StandardUserDefaults.StringForKey(TokenSetting)); } - public Task RegisterAsync() + public bool IsRegisteredForPush => UIApplication.SharedApplication.IsRegisteredForRemoteNotifications; + + public async Task RegisterAsync() { - var userNotificationTypes = UIUserNotificationType.Alert | UIUserNotificationType.Badge | - UIUserNotificationType.Sound; - var settings = UIUserNotificationSettings.GetSettingsForTypes(userNotificationTypes, null); - UIApplication.SharedApplication.RegisterUserNotificationSettings(settings); - return Task.FromResult(0); + var tcs = new TaskCompletionSource(); + + var authOptions = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound; + UNUserNotificationCenter.Current.RequestAuthorization(authOptions, (granted, error) => + { + if (error != null) + { + Debug.WriteLine($"Push Notifications {error}"); + } + else + { + Debug.WriteLine($"Push Notifications {granted}"); + } + + tcs.SetResult(granted); + }); + + if (await tcs.Task) + { + UIApplication.SharedApplication.RegisterForRemoteNotifications(); + } } public Task UnregisterAsync()