diff --git a/src/Packages/Passport/Editor/PassportPostprocess.cs b/src/Packages/Passport/Editor/PassportPostprocess.cs index e105ae5c..0e641b15 100644 --- a/src/Packages/Passport/Editor/PassportPostprocess.cs +++ b/src/Packages/Passport/Editor/PassportPostprocess.cs @@ -84,7 +84,11 @@ public void OnPostprocessBuild(BuildReport report) var method = type.GetMethod("AddFrameworkToProject"); method.Invoke(proj, new object[] { target, "WebKit.framework", false }); } - + { + var method = type.GetMethod("AddFrameworkToProject"); + method.Invoke(proj, new object[] { target, "AuthenticationServices.framework", false }); + } + var cflags = ""; if (EditorUserBuildSettings.development) { diff --git a/src/Packages/Passport/Runtime/Scripts/Private/Core/BrowserCommunicationsManager.cs b/src/Packages/Passport/Runtime/Scripts/Private/Core/BrowserCommunicationsManager.cs index 60ef33e2..c8ea9e9c 100644 --- a/src/Packages/Passport/Runtime/Scripts/Private/Core/BrowserCommunicationsManager.cs +++ b/src/Packages/Passport/Runtime/Scripts/Private/Core/BrowserCommunicationsManager.cs @@ -19,7 +19,10 @@ namespace Immutable.Passport.Core public interface IBrowserCommunicationsManager { + public event OnUnityPostMessageDelegate? OnAuthPostMessage; + public event OnUnityPostMessageErrorDelegate? OnPostMessageError; public void SetCallTimeout(int ms); + public void LaunchAuthURL(string url); public UniTask Call(string fxName, string? data = null, bool ignoreTimeout = false); } @@ -34,6 +37,8 @@ public class BrowserCommunicationsManager : IBrowserCommunicationsManager private readonly IDictionary> requestTaskMap = new Dictionary>(); private readonly IWebBrowserClient webBrowserClient; public event OnBrowserReadyDelegate? OnReady; + public event OnUnityPostMessageDelegate? OnAuthPostMessage; + public event OnUnityPostMessageErrorDelegate? OnPostMessageError; /// /// Timeout time for waiting for each call to respond in milliseconds @@ -44,7 +49,9 @@ public class BrowserCommunicationsManager : IBrowserCommunicationsManager public BrowserCommunicationsManager(IWebBrowserClient webBrowserClient) { this.webBrowserClient = webBrowserClient; - this.webBrowserClient.OnUnityPostMessage += OnUnityPostMessage; + this.webBrowserClient.OnUnityPostMessage += onUnityPostMessage; + this.webBrowserClient.OnAuthPostMessage += onAuthPostMessage; + this.webBrowserClient.OnPostMessageError += onPostMessageError; } #region Unity to Browser @@ -84,14 +91,32 @@ private void CallFunction(string requestId, string fxName, string? data = null) webBrowserClient.ExecuteJs(js); } + public void LaunchAuthURL(string url) + { + Debug.Log($"{TAG} LaunchAuthURL"); + webBrowserClient.LaunchAuthURL(url); + } + + private void onUnityPostMessage(string message) + { + Debug.Log($"{TAG} onUnityPostMessage: {message}"); + HandleResponse(message); + } + #endregion #region Browser to Unity - private void OnUnityPostMessage(string message) + private void onAuthPostMessage(string message) { - Debug.Log($"{TAG} OnUnityPostMessage: {message}"); - HandleResponse(message); + Debug.Log($"{TAG} onAuthPostMessage: {message}"); + OnAuthPostMessage?.Invoke(message); + } + + private void onPostMessageError(string id, string message) + { + Debug.Log($"{TAG} onPostMessageError id: {id} message: {message}"); + OnPostMessageError?.Invoke(id, message); } private void HandleResponse(string message) diff --git a/src/Packages/Passport/Runtime/Scripts/Private/PassportImpl.cs b/src/Packages/Passport/Runtime/Scripts/Private/PassportImpl.cs index 2dae7707..13b05fd2 100644 --- a/src/Packages/Passport/Runtime/Scripts/Private/PassportImpl.cs +++ b/src/Packages/Passport/Runtime/Scripts/Private/PassportImpl.cs @@ -36,6 +36,8 @@ public PassportImpl(IBrowserCommunicationsManager communicationsManager) public async UniTask Init(string clientId, string environment, string? redirectUri = null, string? deeplink = null) { this.redirectUri = redirectUri; + this.communicationsManager.OnAuthPostMessage += OnDeepLinkActivated; + this.communicationsManager.OnPostMessageError += onPostMessageError; InitRequest request = new() { ClientId = clientId, Environment = environment, RedirectUri = redirectUri }; string response = await communicationsManager.Call( @@ -87,6 +89,7 @@ public async UniTask Connect(long? timeoutMs = null) public async void OnDeepLinkActivated(string url) { + Debug.Log($"{TAG} OnDeepLinkActivated: {url} starts with {redirectUri}"); if (url.StartsWith(redirectUri)) await CompletePKCEFlow(url); } @@ -107,7 +110,7 @@ private async UniTask LaunchAuthUrl() if (response?.Success == true && response?.Result != null) { - Application.OpenURL(response.Result.Replace(" ", "+")); + communicationsManager.LaunchAuthURL(response.Result.Replace(" ", "+")); return; } else @@ -339,5 +342,28 @@ public async UniTask ZkEvmGetBalance(string address, string blockNumberO string callResponse = await communicationsManager.Call(PassportFunction.ZK_EVM.GET_BALANCE, json); return JsonConvert.DeserializeObject(callResponse).Result ?? "0x0"; } + + private void onPostMessageError(string id, string message) + { + if (id == "CallFromAuthCallbackError") + { + if (message == "") + { + Debug.Log($"{TAG} Get PKCE Auth URL user cancelled"); + pkceCompletionSource.TrySetCanceled(); + } + else + { + Debug.Log($"{TAG} Get PKCE Auth URL error: {message}"); + pkceCompletionSource.TrySetException(new PassportException( + "Something went wrong, please call ConnectPKCE() again", + PassportErrorType.AUTHENTICATION_ERROR + )); + } + return; + } + + Debug.Log($"{TAG} Unhandled onPostMessageError. id: {id} message: {message}"); + } } } \ No newline at end of file diff --git a/src/Packages/Passport/Runtime/Scripts/Public/Passport.cs b/src/Packages/Passport/Runtime/Scripts/Public/Passport.cs index 5a8d9e13..270eed6d 100644 --- a/src/Packages/Passport/Runtime/Scripts/Public/Passport.cs +++ b/src/Packages/Passport/Runtime/Scripts/Public/Passport.cs @@ -314,7 +314,7 @@ private PassportImpl GetPassportImpl() throw new PassportException("Passport not initialised"); } - private async void onDeepLinkActivated(string url) + private void onDeepLinkActivated(string url) { deeplink = url; diff --git a/src/Packages/Passport/Runtime/ThirdParty/Gree/Assets/Plugins/GreeBrowserClient.cs b/src/Packages/Passport/Runtime/ThirdParty/Gree/Assets/Plugins/GreeBrowserClient.cs index 031e9486..fe367bc7 100644 --- a/src/Packages/Passport/Runtime/ThirdParty/Gree/Assets/Plugins/GreeBrowserClient.cs +++ b/src/Packages/Passport/Runtime/ThirdParty/Gree/Assets/Plugins/GreeBrowserClient.cs @@ -11,20 +11,17 @@ public class GreeBrowserClient : IWebBrowserClient private const string MAC_DATA_DIRECTORY = "/Resources/Data"; private readonly WebViewObject webViewObject; public event OnUnityPostMessageDelegate OnUnityPostMessage; + public event OnUnityPostMessageDelegate OnAuthPostMessage; + public event OnUnityPostMessageErrorDelegate OnPostMessageError; public GreeBrowserClient() { webViewObject = new(); webViewObject.Init( cb: _cb, - httpErr: (msg) => - { - Debug.LogError($"{TAG} http err: {msg}"); - }, - err: (msg) => - { - Debug.LogError($"{TAG} err: {msg}"); - } + httpErr: _onPostMessageError, + err: _onPostMessageError, + auth: _onAuthPostMessage ); #if UNITY_ANDROID string filePath = Constants.SCHEME_FILE + ANDROID_DATA_DIRECTORY + Constants.PASSPORT_DATA_DIRECTORY_NAME + Constants.PASSPORT_HTML_FILE_NAME; @@ -42,6 +39,23 @@ private void _cb(string msg) InvokeOnUnityPostMessage(msg); } + private void _onAuthPostMessage(string url) + { + Debug.Log($"Received auth url: {url}"); + InvokeOnAuthPostMessage(url); + } + + private void _onPostMessageError(string id, string message) + { + Debug.LogError($"{TAG} id: {id} err: {message}"); + OnPostMessageError.Invoke(id, message); + } + + internal void InvokeOnAuthPostMessage(string message) + { + OnAuthPostMessage.Invoke(message); + } + internal void InvokeOnUnityPostMessage(string message) { OnUnityPostMessage?.Invoke(message); @@ -51,5 +65,10 @@ public void ExecuteJs(string js) { webViewObject.EvaluateJS(js); } + + public void LaunchAuthURL(string url) + { + webViewObject.LaunchAuthURL(url); + } } } \ No newline at end of file diff --git a/src/Packages/Passport/Runtime/ThirdParty/Gree/Assets/Plugins/WebViewObject.cs b/src/Packages/Passport/Runtime/ThirdParty/Gree/Assets/Plugins/WebViewObject.cs index 10d3abc3..06e8c2c1 100644 --- a/src/Packages/Passport/Runtime/ThirdParty/Gree/Assets/Plugins/WebViewObject.cs +++ b/src/Packages/Passport/Runtime/ThirdParty/Gree/Assets/Plugins/WebViewObject.cs @@ -40,14 +40,16 @@ #endif using Callback = System.Action; +using ErrorCallback = System.Action; #if UNITY_IPHONE || UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX public class Singleton { private static Singleton _instance; public Callback onJS; - public Callback onError; - public Callback onHttpError; + public ErrorCallback onError; + public ErrorCallback onHttpError; + public Callback onAuth; public static Singleton Instance { @@ -67,8 +69,9 @@ public class WebViewObject { private const string TAG = "[WebViewObject]"; Callback onJS; - Callback onError; - Callback onHttpError; + ErrorCallback onError; + ErrorCallback onHttpError; + Callback onAuth; #if UNITY_ANDROID class AndroidCallback : AndroidJavaProxy { @@ -101,6 +104,8 @@ private static extern void _CWebViewPlugin_LoadURL( private static extern void _CWebViewPlugin_EvaluateJS( IntPtr instance, string url); [DllImport("__Internal")] + private static extern void _CWebViewPlugin_LaunchAuthURL(IntPtr instance, string url); + [DllImport("__Internal")] private static extern void _CWebViewPlugin_SetDelegate(DelegateMessage callback); #elif UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX [DllImport("WebView")] @@ -139,10 +144,10 @@ private static void delegateMessageReceived(string key, string message) { return; } - if (key == "CallOnError") { + if (key == "CallOnError" || key == "CallFromAuthCallbackError") { if (Singleton.Instance.onError != null) { Debug.Log($"{TAG} ==== onError callback running message: " + message); - Singleton.Instance.onError(message); + Singleton.Instance.onError(key, message); } return; } @@ -150,7 +155,15 @@ private static void delegateMessageReceived(string key, string message) { if (key == "CallOnHttpError") { if (Singleton.Instance.onHttpError != null) { Debug.Log($"{TAG} ==== onHttpError callback running message: " + message); - Singleton.Instance.onHttpError(message); + Singleton.Instance.onHttpError(key, message); + } + return; + } + + if (key == "CallFromAuthCallback") { + if (Singleton.Instance.onAuth != null) { + Debug.Log($"{TAG} ==== CallFromAuthCallback callback running message: " + message); + Singleton.Instance.onAuth(message); } return; } @@ -170,18 +183,19 @@ public void handleMessage(string message) CallFromJS(message.Substring(i + 1)); break; case "CallOnError": - CallOnError(message.Substring(i + 1)); + CallOnError("CallOnError", message.Substring(i + 1)); break; case "CallOnHttpError": - CallOnHttpError(message.Substring(i + 1)); + CallOnHttpError("CallOnHttpError", message.Substring(i + 1)); break; } } public void Init( Callback cb = null, - Callback err = null, - Callback httpErr = null, + ErrorCallback err = null, + ErrorCallback httpErr = null, + Callback auth = null, string ua = "", // android int androidForceDarkMode = 0 // 0: follow system setting, 1: force dark off, 2: force dark on @@ -190,6 +204,7 @@ public void Init( onJS = cb; onError = err; onHttpError = httpErr; + onAuth = auth; #if UNITY_WEBGL #if !UNITY_EDITOR _gree_unity_webview_init(); @@ -202,8 +217,9 @@ public void Init( #elif UNITY_IPHONE || UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX webView = _CWebViewPlugin_Init(ua); Singleton.Instance.onJS = ((message) => CallFromJS(message)); - Singleton.Instance.onError = ((message) => CallOnError(message)); - Singleton.Instance.onHttpError = ((message) => CallOnHttpError(message)); + Singleton.Instance.onError = ((id, message) => CallOnError(id, message)); + Singleton.Instance.onHttpError = ((id, message) => CallOnHttpError(id, message)); + Singleton.Instance.onAuth = ((message) => CallOnAuth(message)); _CWebViewPlugin_SetDelegate(delegateMessageReceived); #elif UNITY_ANDROID webView = new AndroidJavaObject("net.gree.unitywebview.CWebViewPluginNoUi"); @@ -258,19 +274,38 @@ public void EvaluateJS(string js) #endif } - public void CallOnError(string error) + public void LaunchAuthURL(string url) + { +#if UNITY_IPHONE + if (webView == IntPtr.Zero) + return; + _CWebViewPlugin_LaunchAuthURL(webView, url); +#else + Application.OpenURL(url); +#endif + } + + public void CallOnError(string id, string error) { if (onError != null) { - onError(error); + onError(id, error); } } - public void CallOnHttpError(string error) + public void CallOnHttpError(string id, string error) { if (onHttpError != null) { - onHttpError(error); + onHttpError(id, error); + } + } + + public void CallOnAuth(string url) + { + if (onAuth != null) + { + onAuth(url); } } diff --git a/src/Packages/Passport/Runtime/ThirdParty/Gree/Assets/Plugins/iOS/WebView.mm b/src/Packages/Passport/Runtime/ThirdParty/Gree/Assets/Plugins/iOS/WebView.mm index f8a3342d..973dd8a9 100644 --- a/src/Packages/Passport/Runtime/ThirdParty/Gree/Assets/Plugins/iOS/WebView.mm +++ b/src/Packages/Passport/Runtime/ThirdParty/Gree/Assets/Plugins/iOS/WebView.mm @@ -21,6 +21,7 @@ #import #import +#import // NOTE: we need extern without "C" before unity 4.5 extern "C" UIViewController *UnityGetGLViewController(); @@ -86,7 +87,7 @@ - (void)load:(NSURLRequest *)request } @end -@interface CWebViewPlugin : NSObject +@interface CWebViewPlugin : NSObject { WKWebView *webView; } @@ -97,6 +98,7 @@ @implementation CWebViewPlugin static WKProcessPool *_sharedProcessPool; static NSMutableArray *_instances = [[NSMutableArray alloc] init]; static CWebViewPlugin *__delegate = nil; +static ASWebAuthenticationSession *_authSession; - (id)initWithUa:(const char *)ua { @@ -343,6 +345,33 @@ - (void)evaluateJS:(const char *)js NSString *jsStr = [NSString stringWithUTF8String:js]; [webView evaluateJavaScript:jsStr completionHandler:^(NSString *result, NSError *error) {}]; } + +- (void)launchAuthURL:(const char *)url +{ + NSURL *URL = [[NSURL alloc] initWithString: [NSString stringWithUTF8String:url]]; + NSString *scheme = NSBundle.mainBundle.bundleIdentifier; + + _authSession = [[ASWebAuthenticationSession alloc] initWithURL:URL callbackURLScheme:scheme completionHandler:^(NSURL * _Nullable callbackURL, NSError * _Nullable error) { + _authSession = nil; + + if (error != nil && error.code == 1) { + // Cancelled + [self sendUnityCallback:"CallFromAuthCallbackError" message: ""]; + } else if (error != nil) { + [self sendUnityCallback:"CallFromAuthCallbackError" message:error.localizedDescription.UTF8String]; + } else { + [self sendUnityCallback:"CallFromAuthCallback" message: callbackURL.absoluteString.UTF8String]; + } + }]; + + _authSession.presentationContextProvider = self; + [_authSession start]; +} + +- (ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(ASWebAuthenticationSession *)session +{ + return UIApplication.sharedApplication.windows.firstObject; +} @end extern "C" { @@ -351,6 +380,7 @@ - (void)evaluateJS:(const char *)js void _CWebViewPlugin_LoadURL(void *instance, const char *url); void _CWebViewPlugin_EvaluateJS(void *instance, const char *url); void _CWebViewPlugin_SetDelegate(DelegateCallbackFunction callback); + void _CWebViewPlugin_LaunchAuthURL(void *instance, const char *url); } void _CWebViewPlugin_SetDelegate(DelegateCallbackFunction callback) { @@ -389,3 +419,11 @@ void _CWebViewPlugin_EvaluateJS(void *instance, const char *js) CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance; [webViewPlugin evaluateJS:js]; } + +void _CWebViewPlugin_LaunchAuthURL(void *instance, const char *url) +{ + if (instance == NULL) + return; + CWebViewPlugin *webViewPlugin = (__bridge CWebViewPlugin *)instance; + [webViewPlugin launchAuthURL:url]; +} diff --git a/src/Packages/Passport/Runtime/ThirdParty/ImmutableBrowserCore/IWebBrowserClient.cs b/src/Packages/Passport/Runtime/ThirdParty/ImmutableBrowserCore/IWebBrowserClient.cs index c0502a73..e29a25c5 100644 --- a/src/Packages/Passport/Runtime/ThirdParty/ImmutableBrowserCore/IWebBrowserClient.cs +++ b/src/Packages/Passport/Runtime/ThirdParty/ImmutableBrowserCore/IWebBrowserClient.cs @@ -3,7 +3,11 @@ namespace Immutable.Browser.Core public interface IWebBrowserClient { event OnUnityPostMessageDelegate OnUnityPostMessage; + event OnUnityPostMessageDelegate OnAuthPostMessage; + event OnUnityPostMessageErrorDelegate OnPostMessageError; void ExecuteJs(string js); + + void LaunchAuthURL(string url); } } \ No newline at end of file diff --git a/src/Packages/Passport/Runtime/ThirdParty/ImmutableBrowserCore/OnUnityPostMessage.cs b/src/Packages/Passport/Runtime/ThirdParty/ImmutableBrowserCore/OnUnityPostMessage.cs index 3e313f9b..6a4a2817 100644 --- a/src/Packages/Passport/Runtime/ThirdParty/ImmutableBrowserCore/OnUnityPostMessage.cs +++ b/src/Packages/Passport/Runtime/ThirdParty/ImmutableBrowserCore/OnUnityPostMessage.cs @@ -1,4 +1,5 @@ namespace Immutable.Browser.Core { public delegate void OnUnityPostMessageDelegate(string data); + public delegate void OnUnityPostMessageErrorDelegate(string id, string error); } \ No newline at end of file diff --git a/src/Packages/Passport/Runtime/ThirdParty/UnityWebBrowser/Runtime/Core/WebBrowserClient.cs b/src/Packages/Passport/Runtime/ThirdParty/UnityWebBrowser/Runtime/Core/WebBrowserClient.cs index 664e0c1f..c6d7ed7e 100644 --- a/src/Packages/Passport/Runtime/ThirdParty/UnityWebBrowser/Runtime/Core/WebBrowserClient.cs +++ b/src/Packages/Passport/Runtime/ThirdParty/UnityWebBrowser/Runtime/Core/WebBrowserClient.cs @@ -592,6 +592,8 @@ internal void InvokeLoadProgressChange(double progress) /// Invoked when the browser goes in or out of fullscreen /// public event OnFullscreenChange OnFullscreen; + public event OnUnityPostMessageDelegate OnAuthPostMessage; + public event OnUnityPostMessageErrorDelegate OnPostMessageError; internal void InvokeFullscreen(bool fullscreen) { @@ -737,6 +739,11 @@ public void ExecuteJs(string js) communicationsManager.ExecuteJs(js); } + public void LaunchAuthURL(string url) + { + Application.OpenURL(url); + } + [DebuggerStepThrough] private void CheckIfIsReadyAndConnected() { diff --git a/src/Packages/Passport/Tests/Runtime/Scripts/Core/BrowserCommunicationsManagerTests.cs b/src/Packages/Passport/Tests/Runtime/Scripts/Core/BrowserCommunicationsManagerTests.cs index 41b370c0..11043806 100644 --- a/src/Packages/Passport/Tests/Runtime/Scripts/Core/BrowserCommunicationsManagerTests.cs +++ b/src/Packages/Passport/Tests/Runtime/Scripts/Core/BrowserCommunicationsManagerTests.cs @@ -152,6 +152,9 @@ public void CallAndResponse_Success_BrowserReady() internal class MockBrowserClient : IWebBrowserClient { public event OnUnityPostMessageDelegate? OnUnityPostMessage; + public event OnUnityPostMessageDelegate? OnAuthPostMessage; + public event OnUnityPostMessageErrorDelegate? OnPostMessageError; + public BrowserRequest? request = null; public BrowserResponse? browserResponse = null; public bool setRequestId = true; @@ -191,5 +194,10 @@ private string Between(string value, string a, string b) } return value[adjustedPosA..posB]; } + + public void LaunchAuthURL(string url) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/Packages/Passport/Tests/Runtime/Scripts/PassportTests.cs b/src/Packages/Passport/Tests/Runtime/Scripts/PassportTests.cs index 6a86bd45..33f2f433 100644 --- a/src/Packages/Passport/Tests/Runtime/Scripts/PassportTests.cs +++ b/src/Packages/Passport/Tests/Runtime/Scripts/PassportTests.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Cysharp.Threading.Tasks; using Newtonsoft.Json; +using Immutable.Browser.Core; namespace Immutable.Passport { @@ -66,6 +67,9 @@ internal class MockBrowserCommsManager : IBrowserCommunicationsManager public string response = ""; public string fxName = ""; public string? data = ""; + public event OnUnityPostMessageDelegate? OnAuthPostMessage; + public event OnUnityPostMessageErrorDelegate? OnPostMessageError; + public UniTask Call(string fxName, string? data = null, bool ignoreTimeout = false) { this.fxName = fxName; @@ -73,6 +77,11 @@ public UniTask Call(string fxName, string? data = null, bool ignoreTimeo return UniTask.FromResult(response); } + public void LaunchAuthURL(string url) + { + throw new NotImplementedException(); + } + public void SetCallTimeout(int ms) { }