diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs
index 285be41ea..639f499e1 100644
--- a/src/App/App.xaml.cs
+++ b/src/App/App.xaml.cs
@@ -103,7 +103,7 @@ namespace Bit.App
{
return;
}
- await LogOutAsync(false);
+ Device.BeginInvokeOnMainThread(async () => await LogOutAsync(false));
}
else if(message.Command == "loggedOut")
{
diff --git a/src/iOS.Core/Constants.cs b/src/iOS.Core/Constants.cs
new file mode 100644
index 000000000..50941c0bb
--- /dev/null
+++ b/src/iOS.Core/Constants.cs
@@ -0,0 +1,32 @@
+namespace Bit.iOS.Core
+{
+ public static class Constants
+ {
+ public const string AppExtensionVersionNumberKey = "version_number";
+ public const string AppExtensionUrlStringKey = "url_string";
+ public const string AppExtensionUsernameKey = "username";
+ public const string AppExtensionPasswordKey = "password";
+ public const string AppExtensionTotpKey = "totp";
+ public const string AppExtensionTitleKey = "login_title";
+ public const string AppExtensionNotesKey = "notes";
+ public const string AppExtensionSectionTitleKey = "section_title";
+ public const string AppExtensionFieldsKey = "fields";
+ public const string AppExtensionReturnedFieldsKey = "returned_fields";
+ public const string AppExtensionOldPasswordKey = "old_password";
+ public const string AppExtensionPasswordGeneratorOptionsKey = "password_generator_options";
+ public const string AppExtensionGeneratedPasswordMinLengthKey = "password_min_length";
+ public const string AppExtensionGeneratedPasswordMaxLengthKey = "password_max_length";
+ public const string AppExtensionGeneratedPasswordRequireDigitsKey = "password_require_digits";
+ public const string AppExtensionGeneratedPasswordRequireSymbolsKey = "password_require_symbols";
+ public const string AppExtensionGeneratedPasswordForbiddenCharactersKey = "password_forbidden_characters";
+ public const string AppExtensionWebViewPageFillScript = "fillScript";
+ public const string AppExtensionWebViewPageDetails = "pageDetails";
+
+ public const string UTTypeAppExtensionFindLoginAction = "org.appextension.find-login-action";
+ public const string UTTypeAppExtensionSaveLoginAction = "org.appextension.save-login-action";
+ public const string UTTypeAppExtensionChangePasswordAction = "org.appextension.change-password-action";
+ public const string UTTypeAppExtensionFillWebViewAction = "org.appextension.fill-webview-action";
+ public const string UTTypeAppExtensionFillBrowserAction = "org.appextension.fill-browser-action";
+ public const string UTTypeAppExtensionSetup = "com.8bit.bitwarden.extension-setup";
+ }
+}
diff --git a/src/iOS.Core/Utilities/HockeyAppCrashManagerDelegate.cs b/src/iOS.Core/Utilities/HockeyAppCrashManagerDelegate.cs
new file mode 100644
index 000000000..e85b2c87a
--- /dev/null
+++ b/src/iOS.Core/Utilities/HockeyAppCrashManagerDelegate.cs
@@ -0,0 +1,40 @@
+using Bit.Core.Abstractions;
+using HockeyApp.iOS;
+using Newtonsoft.Json;
+using System.Threading.Tasks;
+
+namespace Bit.iOS.Core.Utilities
+{
+ public class HockeyAppCrashManagerDelegate : BITCrashManagerDelegate
+ {
+ private readonly IAppIdService _appIdService;
+ private readonly IUserService _userService;
+
+ private string _userId;
+ private string _appId;
+
+ public HockeyAppCrashManagerDelegate(
+ IAppIdService appIdService,
+ IUserService userService)
+ {
+ _appIdService = appIdService;
+ _userService = userService;
+ }
+
+ public async Task InitAsync(BITHockeyManager manager)
+ {
+ _userId = await _userService.GetUserIdAsync();
+ _appId = await _appIdService.GetAppIdAsync();
+ manager.UserId = _userId;
+ }
+
+ public override string ApplicationLogForCrashManager(BITCrashManager crashManager)
+ {
+ return JsonConvert.SerializeObject(new
+ {
+ AppId = _appId,
+ UserId = _userId
+ }, Formatting.Indented);
+ }
+ }
+}
diff --git a/src/iOS.Core/iOS.Core.csproj b/src/iOS.Core/iOS.Core.csproj
index f8cae9d08..0129d3e5e 100644
--- a/src/iOS.Core/iOS.Core.csproj
+++ b/src/iOS.Core/iOS.Core.csproj
@@ -53,6 +53,8 @@
+
+
diff --git a/src/iOS/AppDelegate.cs b/src/iOS/AppDelegate.cs
index 061183d91..2d7fc4a11 100644
--- a/src/iOS/AppDelegate.cs
+++ b/src/iOS/AppDelegate.cs
@@ -1,16 +1,20 @@
using System;
+using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Bit.App.Abstractions;
+using Bit.App.Resources;
using Bit.App.Services;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.iOS.Core.Services;
+using Bit.iOS.Core.Utilities;
using Bit.iOS.Services;
using CoreNFC;
using Foundation;
+using HockeyApp.iOS;
using UIKit;
namespace Bit.iOS
@@ -26,18 +30,131 @@ namespace Bit.iOS
private iOSPushNotificationHandler _pushHandler = null;
private NFCReaderDelegate _nfcDelegate = null;
+ private IDeviceActionService _deviceActionService;
+ private IMessagingService _messagingService;
+ private IBroadcasterService _broadcasterService;
+ private IStorageService _storageService;
+
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
Xamarin.Forms.Forms.Init();
InitApp();
Bootstrap();
+ _deviceActionService = ServiceContainer.Resolve("deviceActionService");
+ _messagingService = ServiceContainer.Resolve("messagingService");
+ _broadcasterService = ServiceContainer.Resolve("broadcasterService");
+ _storageService = ServiceContainer.Resolve("storageService");
LoadApplication(new App.App(null));
-
+ AppearanceAdjustments();
ZXing.Net.Mobile.Forms.iOS.Platform.Init();
+
+ _broadcasterService.Subscribe(nameof(AppDelegate), (message) =>
+ {
+ if(message.Command == "scheduleLockTimer")
+ {
+ var lockOptionMinutes = (int)message.Data;
+ }
+ else if(message.Command == "cancelLockTimer")
+ {
+
+ }
+ else if(message.Command == "updatedTheme")
+ {
+
+ }
+ else if(message.Command == "copiedToClipboard")
+ {
+
+ }
+ else if(message.Command == "listenYubiKeyOTP")
+ {
+ ListenYubiKey((bool)message.Data);
+ }
+ else if(message.Command == "showAppExtension")
+ {
+
+ }
+ else if(message.Command == "showStatusBar")
+ {
+ UIApplication.SharedApplication.SetStatusBarHidden(!(bool)message.Data, false);
+ }
+ else if(message.Command == "syncCompleted")
+ {
+ if(message.Data is Dictionary data && data.ContainsKey("successfully"))
+ {
+ var success = data["successfully"] as bool?;
+ if(success.GetValueOrDefault())
+ {
+
+ }
+ }
+ }
+ else if(message.Command == "addedCipher" || message.Command == "editedCipher")
+ {
+
+ }
+ else if(message.Command == "deletedCipher")
+ {
+
+ }
+ else if(message.Command == "loggedOut")
+ {
+
+ }
+ });
+
return base.FinishedLaunching(app, options);
}
+ public override void DidEnterBackground(UIApplication uiApplication)
+ {
+ var view = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
+ {
+ Tag = 4321
+ };
+ var backgroundView = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
+ {
+ BackgroundColor = new UIColor(red: 0.93f, green: 0.94f, blue: 0.96f, alpha: 1.0f)
+ };
+ var imageView = new UIImageView(new UIImage("logo.png"))
+ {
+ Center = new CoreGraphics.CGPoint(view.Center.X, view.Center.Y - 30)
+ };
+ view.AddSubview(backgroundView);
+ view.AddSubview(imageView);
+ UIApplication.SharedApplication.KeyWindow.AddSubview(view);
+ UIApplication.SharedApplication.KeyWindow.BringSubviewToFront(view);
+ UIApplication.SharedApplication.KeyWindow.EndEditing(true);
+ UIApplication.SharedApplication.SetStatusBarHidden(true, false);
+ _storageService.SaveAsync(Bit.Core.Constants.LastActiveKey, DateTime.UtcNow);
+ base.DidEnterBackground(uiApplication);
+ }
+
+ public override void OnActivated(UIApplication uiApplication)
+ {
+ base.OnActivated(uiApplication);
+ UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
+ var view = UIApplication.SharedApplication.KeyWindow.ViewWithTag(4321);
+ if(view != null)
+ {
+ view.RemoveFromSuperview();
+ UIApplication.SharedApplication.SetStatusBarHidden(false, false);
+ }
+ }
+
+ public override void WillEnterForeground(UIApplication uiApplication)
+ {
+ _messagingService.Send("Resumed");
+ base.WillEnterForeground(uiApplication);
+ }
+
+ public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication,
+ NSObject annotation)
+ {
+ return true;
+ }
+
public override void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
{
_pushHandler?.OnErrorReceived(error);
@@ -75,8 +192,19 @@ namespace Bit.iOS
ServiceContainer.Init();
_pushHandler = new iOSPushNotificationHandler(
ServiceContainer.Resolve("pushNotificationListenerService"));
- _nfcDelegate = new NFCReaderDelegate((success, message) => { }); // TODO: process YubiKey
- // TODO: HockeyApp Init
+ _nfcDelegate = new NFCReaderDelegate((success, message) =>
+ _messagingService.Send("GotYubiKeyOTP", message));
+
+ var crashManagerDelegate = new HockeyAppCrashManagerDelegate(
+ ServiceContainer.Resolve("appIdService"),
+ ServiceContainer.Resolve("userService"));
+ var manager = BITHockeyManager.SharedHockeyManager;
+ manager.Configure("51f96ae568ba45f699a18ad9f63046c3", crashManagerDelegate);
+ manager.CrashManager.CrashManagerStatus = BITCrashManagerStatus.AutoSend;
+ manager.StartManager();
+ manager.Authenticator.AuthenticateInstallation();
+ manager.DisableMetricsManager = manager.DisableFeedbackManager = manager.DisableUpdateManager = true;
+ var task = crashManagerDelegate.InitAsync(manager);
}
private void RegisterLocalServices()
@@ -146,16 +274,67 @@ namespace Bit.iOS
private async Task BootstrapAsync()
{
var disableFavicon = await ServiceContainer.Resolve("storageService").GetAsync(
- Constants.DisableFaviconKey);
- await ServiceContainer.Resolve("stateService").SaveAsync(Constants.DisableFaviconKey,
- disableFavicon);
+ Bit.Core.Constants.DisableFaviconKey);
+ await ServiceContainer.Resolve("stateService").SaveAsync(
+ Bit.Core.Constants.DisableFaviconKey, disableFavicon);
await ServiceContainer.Resolve("environmentService").SetUrlsFromStorageAsync();
}
private void AppearanceAdjustments()
{
+ var primaryColor = new UIColor(red: 0.24f, green: 0.55f, blue: 0.74f, alpha: 1.0f);
+ var grayLight = new UIColor(red: 0.47f, green: 0.47f, blue: 0.47f, alpha: 1.0f);
+
UINavigationBar.Appearance.ShadowImage = new UIImage();
UINavigationBar.Appearance.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
+ UIBarButtonItem.AppearanceWhenContainedIn(new Type[] { typeof(UISearchBar) }).TintColor = primaryColor;
+ UIButton.AppearanceWhenContainedIn(new Type[] { typeof(UISearchBar) }).SetTitleColor(primaryColor,
+ UIControlState.Normal);
+ UIButton.AppearanceWhenContainedIn(new Type[] { typeof(UISearchBar) }).TintColor = primaryColor;
+ UIStepper.Appearance.TintColor = grayLight;
+ UISlider.Appearance.TintColor = primaryColor;
+ UIApplication.SharedApplication.StatusBarHidden = false;
+ UIApplication.SharedApplication.StatusBarStyle = UIStatusBarStyle.LightContent;
+ }
+
+ private void ListenYubiKey(bool listen)
+ {
+ if(_deviceActionService.SupportsNfc())
+ {
+ _nfcSession?.InvalidateSession();
+ _nfcSession?.Dispose();
+ _nfcSession = null;
+ if(listen)
+ {
+ _nfcSession = new NFCNdefReaderSession(_nfcDelegate, null, true)
+ {
+ AlertMessage = AppResources.HoldYubikeyNearTop
+ };
+ _nfcSession.BeginSession();
+ }
+ }
+ }
+
+ private void ShowAppExtension()
+ {
+ var itemProvider = new NSItemProvider(new NSDictionary(), Core.Constants.UTTypeAppExtensionSetup);
+ var extensionItem = new NSExtensionItem();
+ extensionItem.Attachments = new NSItemProvider[] { itemProvider };
+ var activityViewController = new UIActivityViewController(new NSExtensionItem[] { extensionItem }, null);
+ activityViewController.CompletionHandler = (activityType, completed) =>
+ {
+ // TODO
+ //page.EnabledExtension(completed && activityType == "com.8bit.bitwarden.find-login-action-extension");
+ };
+ var modal = UIApplication.SharedApplication.KeyWindow.RootViewController.ModalViewController;
+ if(activityViewController.PopoverPresentationController != null)
+ {
+ activityViewController.PopoverPresentationController.SourceView = modal.View;
+ var frame = UIScreen.MainScreen.Bounds;
+ frame.Height /= 2;
+ activityViewController.PopoverPresentationController.SourceRect = frame;
+ }
+ modal.PresentViewController(activityViewController, true, null);
}
}
}