mirror of
https://github.com/bitwarden/android.git
synced 2024-12-27 11:28:28 +03:00
PM-3349 Implemented HybridWebViewHandler for Android which enables 2nd factor auth flows
Ensured CustomTabbedPageHandler had it's DisconnectHandler called Some minor code upgrades of older obsolete Xamarin Forms code.
This commit is contained in:
parent
1dcd3a3daa
commit
c92cd90a97
9 changed files with 159 additions and 40 deletions
25
src/App/Handlers/HybridWebViewHandler.cs
Normal file
25
src/App/Handlers/HybridWebViewHandler.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
#if IOS || MACCATALYST
|
||||
using PlatformView = WebKit.WKWebView;
|
||||
#elif ANDROID
|
||||
using PlatformView = Android.Webkit.WebView;
|
||||
#elif (NETSTANDARD || !PLATFORM) || (NET6_0_OR_GREATER && !IOS && !ANDROID)
|
||||
using PlatformView = System.Object;
|
||||
#endif
|
||||
|
||||
using Bit.App.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
|
||||
namespace Bit.App.Handlers
|
||||
{
|
||||
public partial class HybridWebViewHandler
|
||||
{
|
||||
public static PropertyMapper<HybridWebView, HybridWebViewHandler> PropertyMapper = new PropertyMapper<HybridWebView, HybridWebViewHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(HybridWebView.Uri)] = MapUri
|
||||
};
|
||||
|
||||
public HybridWebViewHandler() : base(PropertyMapper)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
namespace Bit.App
|
||||
namespace Bit.App
|
||||
{
|
||||
public class MauiProgram
|
||||
{
|
||||
|
@ -28,7 +28,8 @@ namespace Bit.App
|
|||
Bit.App.Handlers.TimePickerHandlerMappings.Setup();
|
||||
Bit.App.Handlers.ButtonHandlerMappings.Setup();
|
||||
|
||||
handlers.AddHandler(typeof(TabbedPage), typeof(Bit.App.Handlers.CustomTabbedPageHandler));
|
||||
handlers.AddHandler(typeof(Bit.App.Pages.TabsPage), typeof(Bit.App.Handlers.CustomTabbedPageHandler));
|
||||
handlers.AddHandler(typeof(Bit.App.Controls.HybridWebView), typeof(Bit.App.Handlers.HybridWebViewHandler));
|
||||
#else
|
||||
iOS.Core.Utilities.iOSCoreHelpers.ConfigureMAUIHandlers(handlers);
|
||||
#endif
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using AndroidX.AppCompat.View.Menu;
|
||||
using AndroidX.Navigation.UI;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Google.Android.Material.BottomNavigation;
|
||||
|
@ -95,6 +94,7 @@ namespace Bit.App.Handlers
|
|||
}
|
||||
}
|
||||
|
||||
//Currently the Disconnect Handler needs to be manually called from the App: https://github.com/dotnet/maui/issues/3604
|
||||
protected override void DisconnectHandler(global::Android.Views.View platformView)
|
||||
{
|
||||
if(_bottomNavigationViewGroup != null)
|
||||
|
|
96
src/App/Platforms/Android/Handlers/HybridWebViewHandler.cs
Normal file
96
src/App/Platforms/Android/Handlers/HybridWebViewHandler.cs
Normal file
|
@ -0,0 +1,96 @@
|
|||
using Bit.App.Controls;
|
||||
using Java.Interop;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using AWebkit = Android.Webkit;
|
||||
|
||||
namespace Bit.App.Handlers
|
||||
{
|
||||
public partial class HybridWebViewHandler : ViewHandler<HybridWebView, AWebkit.WebView>
|
||||
{
|
||||
private const string JSFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
|
||||
|
||||
public HybridWebViewHandler([NotNull] IPropertyMapper mapper, CommandMapper commandMapper = null) : base(mapper, commandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AWebkit.WebView CreatePlatformView()
|
||||
{
|
||||
var context = MauiContext?.Context ?? throw new InvalidOperationException($"Context cannot be null here");
|
||||
var webView = new AWebkit.WebView(context);
|
||||
webView.Settings.JavaScriptEnabled = true;
|
||||
webView.SetWebViewClient(new JSWebViewClient(string.Format("javascript: {0}", JSFunction)));
|
||||
return webView;
|
||||
}
|
||||
|
||||
public static void MapUri(HybridWebViewHandler handler, HybridWebView view)
|
||||
{
|
||||
if (view != null && view.Uri != null)
|
||||
{
|
||||
handler?.PlatformView?.LoadUrl(view.Uri);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(AWebkit.WebView platformView)
|
||||
{
|
||||
platformView?.AddJavascriptInterface(new JSBridge(this), "jsBridge");
|
||||
platformView?.LoadUrl(VirtualView?.Uri);
|
||||
|
||||
base.ConnectHandler(platformView);
|
||||
}
|
||||
|
||||
//Currently the Disconnect Handler needs to be manually called from the App: https://github.com/dotnet/maui/issues/3604
|
||||
protected override void DisconnectHandler(AWebkit.WebView platformView)
|
||||
{
|
||||
platformView?.RemoveJavascriptInterface("jsBridge");
|
||||
platformView?.Dispose();
|
||||
VirtualView?.Cleanup();
|
||||
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
internal void InvokeActionOnVirtual(string data)
|
||||
{
|
||||
VirtualView?.InvokeAction(data);
|
||||
}
|
||||
}
|
||||
|
||||
public class JSBridge : Java.Lang.Object
|
||||
{
|
||||
private readonly WeakReference<HybridWebViewHandler> _hybridWebViewRenderer;
|
||||
|
||||
public JSBridge(HybridWebViewHandler hybridRenderer)
|
||||
{
|
||||
_hybridWebViewRenderer = new WeakReference<HybridWebViewHandler>(hybridRenderer);
|
||||
}
|
||||
|
||||
[AWebkit.JavascriptInterface]
|
||||
[Export("invokeAction")]
|
||||
public void InvokeAction(string data)
|
||||
{
|
||||
if (_hybridWebViewRenderer != null &&_hybridWebViewRenderer.TryGetTarget(out HybridWebViewHandler hybridRenderer))
|
||||
{
|
||||
hybridRenderer?.InvokeActionOnVirtual(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class JSWebViewClient : AWebkit.WebViewClient
|
||||
{
|
||||
private readonly string _javascript;
|
||||
|
||||
public JSWebViewClient(string javascript)
|
||||
{
|
||||
_javascript = javascript;
|
||||
}
|
||||
|
||||
public override void OnPageFinished(AWebkit.WebView view, string url)
|
||||
{
|
||||
base.OnPageFinished(view, url);
|
||||
if (view != null)
|
||||
{
|
||||
view.EvaluateJavascript(_javascript, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,4 @@
|
|||
using System;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class HybridWebView : View
|
||||
{
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
x:DataType="pages:TwoFactorPageViewModel"
|
||||
Unloaded="TwoFactorPage_OnUnloaded"
|
||||
Title="{Binding PageTitle}">
|
||||
|
||||
<ContentPage.BindingContext>
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
|
@ -33,24 +28,23 @@ namespace Bit.App.Pages
|
|||
_vm.Page = this;
|
||||
_vm.AuthingWithSso = authingWithSso ?? false;
|
||||
_vm.StartSetPasswordAction = () =>
|
||||
Device.BeginInvokeOnMainThread(async () => await StartSetPasswordAsync());
|
||||
MainThread.BeginInvokeOnMainThread(async () => await StartSetPasswordAsync());
|
||||
_vm.TwoFactorAuthSuccessAction = () =>
|
||||
Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessToMainAsync());
|
||||
MainThread.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessToMainAsync());
|
||||
_vm.LockAction = () =>
|
||||
Device.BeginInvokeOnMainThread(TwoFactorAuthSuccessWithSSOLocked);
|
||||
MainThread.BeginInvokeOnMainThread(TwoFactorAuthSuccessWithSSOLocked);
|
||||
_vm.UpdateTempPasswordAction =
|
||||
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
||||
() => MainThread.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
||||
_vm.StartDeviceApprovalOptionsAction =
|
||||
() => Device.BeginInvokeOnMainThread(async () => await StartDeviceApprovalOptionsAsync());
|
||||
() => MainThread.BeginInvokeOnMainThread(async () => await StartDeviceApprovalOptionsAsync());
|
||||
_vm.CloseAction = async () => await Navigation.PopModalAsync();
|
||||
DuoWebView = _duoWebView;
|
||||
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
|
||||
if (Device.RuntimePlatform == Device.Android)
|
||||
|
||||
if (DeviceInfo.Platform == DevicePlatform.Android)
|
||||
{
|
||||
ToolbarItems.Remove(_cancelItem);
|
||||
}
|
||||
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
if (DeviceInfo.Platform == DevicePlatform.iOS)
|
||||
{
|
||||
ToolbarItems.Add(_moreItem);
|
||||
}
|
||||
|
@ -62,7 +56,7 @@ namespace Bit.App.Pages
|
|||
|
||||
public HybridWebView DuoWebView { get; set; }
|
||||
|
||||
protected async override void OnAppearing()
|
||||
protected override async void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
_broadcasterService.Subscribe(nameof(TwoFactorPage), (message) =>
|
||||
|
@ -73,7 +67,7 @@ namespace Bit.App.Pages
|
|||
if (_vm.YubikeyMethod && !string.IsNullOrWhiteSpace(token) &&
|
||||
token.Length == 44 && !token.Contains(" "))
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(async () =>
|
||||
MainThread.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
_vm.Token = token;
|
||||
await _vm.SubmitAsync();
|
||||
|
@ -117,6 +111,12 @@ namespace Bit.App.Pages
|
|||
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
|
||||
}
|
||||
}
|
||||
|
||||
private void TwoFactorPage_OnUnloaded(object sender, EventArgs e)
|
||||
{
|
||||
_duoWebView?.Handler?.DisconnectHandler();
|
||||
}
|
||||
|
||||
protected override bool OnBackButtonPressed()
|
||||
{
|
||||
if (_vm.YubikeyMethod)
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Maui.Controls.PlatformConfiguration;
|
||||
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
|
||||
using Microsoft.Maui.ApplicationModel.Communication;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
|
@ -23,8 +18,7 @@ namespace Bit.App.Pages
|
|||
|
||||
public BaseContentPage()
|
||||
{
|
||||
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
if (DeviceInfo.Platform == DevicePlatform.iOS)
|
||||
{
|
||||
On<iOS>().SetUseSafeArea(true);
|
||||
On<iOS>().SetModalPresentationStyle(UIModalPresentationStyle.FullScreen);
|
||||
|
@ -35,7 +29,7 @@ namespace Bit.App.Pages
|
|||
|
||||
public bool IsThemeDirty { get; set; }
|
||||
|
||||
protected async override void OnAppearing()
|
||||
protected override async void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
|
@ -70,7 +64,7 @@ namespace Bit.App.Pages
|
|||
var indicator = new ActivityIndicator
|
||||
{
|
||||
IsRunning = true,
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
Color = ThemeManager.GetResourceColor("PrimaryColor"),
|
||||
};
|
||||
|
@ -102,8 +96,7 @@ namespace Bit.App.Pages
|
|||
}
|
||||
}
|
||||
}
|
||||
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
if (DeviceInfo.Platform == DevicePlatform.iOS)
|
||||
{
|
||||
await DoWorkAsync();
|
||||
return;
|
||||
|
@ -111,7 +104,7 @@ namespace Bit.App.Pages
|
|||
await Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(fromModal ? ShowModalAnimationDelay : ShowPageAnimationDelay);
|
||||
Device.BeginInvokeOnMainThread(async () => await DoWorkAsync());
|
||||
MainThread.BeginInvokeOnMainThread(async () => await DoWorkAsync());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -120,7 +113,7 @@ namespace Bit.App.Pages
|
|||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(ShowModalAnimationDelay);
|
||||
Device.BeginInvokeOnMainThread(() => input.Focus());
|
||||
MainThread.BeginInvokeOnMainThread(() => input.Focus());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,8 @@ namespace Bit.App.Pages
|
|||
};
|
||||
Children.Add(settingsPage);
|
||||
|
||||
Unloaded += OnUnloaded;
|
||||
|
||||
if (DeviceInfo.Platform == DevicePlatform.Android)
|
||||
{
|
||||
Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific.TabbedPage.SetToolbarPlacement(this,
|
||||
|
@ -109,6 +111,11 @@ namespace Bit.App.Pages
|
|||
_broadcasterService.Unsubscribe(nameof(TabsPage));
|
||||
}
|
||||
|
||||
private void OnUnloaded(object sender, EventArgs e)
|
||||
{
|
||||
Handler?.DisconnectHandler();
|
||||
}
|
||||
|
||||
public void ResetToVaultPage()
|
||||
{
|
||||
CurrentPage = _groupingsPage;
|
||||
|
|
Loading…
Reference in a new issue