diff --git a/Assets/Thirdweb/Plugins/macOS.meta b/Assets/Thirdweb/Plugins/macOS.meta new file mode 100644 index 00000000..fffc90f1 --- /dev/null +++ b/Assets/Thirdweb/Plugins/macOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c6e9b5dce5f146478df9f3e2df5abe10 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Thirdweb/Plugins/macOS/MacBrowser.mm b/Assets/Thirdweb/Plugins/macOS/MacBrowser.mm new file mode 100644 index 00000000..405cf150 --- /dev/null +++ b/Assets/Thirdweb/Plugins/macOS/MacBrowser.mm @@ -0,0 +1,106 @@ +#import +#import + +#include "../iOS/Common.h" + +typedef void (*ASWebAuthenticationSessionCompletionCallback)(void* sessionPtr, const char* callbackUrl, int errorCode, const char* errorMessage); + +@interface Thirdweb_ASWebAuthenticationSession : NSObject + +@property (readonly, nonatomic) ASWebAuthenticationSession* session; + +@end + +@implementation Thirdweb_ASWebAuthenticationSession + +- (instancetype)initWithURL:(NSURL *)URL callbackURLScheme:(nullable NSString *)callbackURLScheme completionCallback:(ASWebAuthenticationSessionCompletionCallback)completionCallback +{ + self = [super init]; + if (self) + { + _session = [[ASWebAuthenticationSession alloc] initWithURL:URL + callbackURLScheme:callbackURLScheme + completionHandler:^(NSURL * _Nullable callbackURL, NSError * _Nullable error) + { + if (error != nil) + { + NSLog(@"[ASWebAuthenticationSession:CompletionHandler] %@", error.description); + } + + completionCallback((__bridge void*)self, toString(callbackURL.absoluteString), (int)error.code, toString(error.localizedDescription)); + }]; + + if (@available(macOS 10.15, *)) + { + _session.presentationContextProvider = self; + } + } + return self; +} + +- (ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(ASWebAuthenticationSession *)session +{ + if (@available(macOS 10.15, *)) + { + NSWindow* anchor = [NSApplication sharedApplication].keyWindow; + if (anchor == nil) + { + anchor = [NSApplication sharedApplication].mainWindow; + } + if (anchor == nil) + { + anchor = [NSApplication sharedApplication].windows.firstObject; + } + return anchor; + } + return nil; +} + +@end + +extern "C" +{ + Thirdweb_ASWebAuthenticationSession* Thirdweb_ASWebAuthenticationSession_InitWithURL( + const char* urlStr, const char* urlSchemeStr, ASWebAuthenticationSessionCompletionCallback completionCallback) + { + NSURL* url = [NSURL URLWithString: toString(urlStr)]; + NSString* urlScheme = toString(urlSchemeStr); + + Thirdweb_ASWebAuthenticationSession* session = [[Thirdweb_ASWebAuthenticationSession alloc] initWithURL:url + callbackURLScheme:urlScheme + completionCallback:completionCallback]; + return session; + } + + int Thirdweb_ASWebAuthenticationSession_Start(void* sessionPtr) + { + Thirdweb_ASWebAuthenticationSession* session = (__bridge Thirdweb_ASWebAuthenticationSession*) sessionPtr; + BOOL started = [[session session] start]; + return toBool(started); + } + + void Thirdweb_ASWebAuthenticationSession_Cancel(void* sessionPtr) + { + Thirdweb_ASWebAuthenticationSession* session = (__bridge Thirdweb_ASWebAuthenticationSession*) sessionPtr; + [[session session] cancel]; + } + + int Thirdweb_ASWebAuthenticationSession_GetPrefersEphemeralWebBrowserSession(void* sessionPtr) + { + Thirdweb_ASWebAuthenticationSession* session = (__bridge Thirdweb_ASWebAuthenticationSession*) sessionPtr; + if (@available(macOS 10.15, *)) + { + return toBool([[session session] prefersEphemeralWebBrowserSession]); + } + return 0; + } + + void Thirdweb_ASWebAuthenticationSession_SetPrefersEphemeralWebBrowserSession(void* sessionPtr, int enable) + { + Thirdweb_ASWebAuthenticationSession* session = (__bridge Thirdweb_ASWebAuthenticationSession*) sessionPtr; + if (@available(macOS 10.15, *)) + { + [[session session] setPrefersEphemeralWebBrowserSession:toBool(enable)]; + } + } +} diff --git a/Assets/Thirdweb/Plugins/macOS/MacBrowser.mm.meta b/Assets/Thirdweb/Plugins/macOS/MacBrowser.mm.meta new file mode 100644 index 00000000..b7cddad5 --- /dev/null +++ b/Assets/Thirdweb/Plugins/macOS/MacBrowser.mm.meta @@ -0,0 +1,84 @@ +fileFormatVersion: 2 +guid: 934bb3da36e34dfcb207b4c1d77efc9a +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 0 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + AndroidSharedLibraryType: Executable + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: AnyCPU + FrameworkDependencies: + CompileFlags: + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Thirdweb/Plugins/macOS/README.md b/Assets/Thirdweb/Plugins/macOS/README.md new file mode 100644 index 00000000..e155fdec --- /dev/null +++ b/Assets/Thirdweb/Plugins/macOS/README.md @@ -0,0 +1,17 @@ +# macOS OAuth Plugin + +The native bridge in this folder (`libMacBrowser.dylib`) is a universal binary (arm64 + x86_64) built from `MacBrowser.mm`. + +## Rebuild steps + +Run these commands from the repository root on macOS: + +```bash +cd Assets/Thirdweb/Plugins/macOS +clang++ -std=c++17 -ObjC++ -fmodules -Wall -Werror -arch arm64 -framework Cocoa -framework AuthenticationServices -dynamiclib MacBrowser.mm -o /tmp/libMacBrowser_arm64.dylib +clang++ -std=c++17 -ObjC++ -fmodules -Wall -Werror -arch x86_64 -framework Cocoa -framework AuthenticationServices -dynamiclib MacBrowser.mm -o /tmp/libMacBrowser_x86_64.dylib +lipo -create /tmp/libMacBrowser_arm64.dylib /tmp/libMacBrowser_x86_64.dylib -output libMacBrowser.dylib +rm /tmp/libMacBrowser_arm64.dylib /tmp/libMacBrowser_x86_64.dylib +``` + +After rebuilding, re-run your Unity macOS build to make sure the new binary is picked up. diff --git a/Assets/Thirdweb/Plugins/macOS/README.md.meta b/Assets/Thirdweb/Plugins/macOS/README.md.meta new file mode 100644 index 00000000..89019c9b --- /dev/null +++ b/Assets/Thirdweb/Plugins/macOS/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3a9f5a8a4f5b4139af80d91a1b66ec97 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Thirdweb/Plugins/macOS/libMacBrowser.dylib b/Assets/Thirdweb/Plugins/macOS/libMacBrowser.dylib new file mode 100755 index 00000000..15973309 Binary files /dev/null and b/Assets/Thirdweb/Plugins/macOS/libMacBrowser.dylib differ diff --git a/Assets/Thirdweb/Plugins/macOS/libMacBrowser.dylib.meta b/Assets/Thirdweb/Plugins/macOS/libMacBrowser.dylib.meta new file mode 100644 index 00000000..fe0011dd --- /dev/null +++ b/Assets/Thirdweb/Plugins/macOS/libMacBrowser.dylib.meta @@ -0,0 +1,83 @@ +fileFormatVersion: 2 +guid: d6547937dc5d4cd09d0f566cbb286ee0 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 0 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: AnyCPU + FrameworkDependencies: AuthenticationServices; + CompileFlags: + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Thirdweb/Runtime/Unity/Browser/CrossPlatformUnityBrowser.cs b/Assets/Thirdweb/Runtime/Unity/Browser/CrossPlatformUnityBrowser.cs index b87c4b16..06b11b43 100644 --- a/Assets/Thirdweb/Runtime/Unity/Browser/CrossPlatformUnityBrowser.cs +++ b/Assets/Thirdweb/Runtime/Unity/Browser/CrossPlatformUnityBrowser.cs @@ -37,6 +37,8 @@ public CrossPlatformUnityBrowser(string htmlOverride = null) _unityBrowser = new AndroidBrowser(); #elif UNITY_IOS _unityBrowser = new IOSBrowser(); +#elif UNITY_STANDALONE_OSX + _unityBrowser = new MacBrowser(); #else _unityBrowser = new InAppWalletBrowser(htmlOverride); #endif diff --git a/Assets/Thirdweb/Runtime/Unity/Browser/iOSBrowser.cs b/Assets/Thirdweb/Runtime/Unity/Browser/iOSBrowser.cs index a3f3ba82..718f5497 100644 --- a/Assets/Thirdweb/Runtime/Unity/Browser/iOSBrowser.cs +++ b/Assets/Thirdweb/Runtime/Unity/Browser/iOSBrowser.cs @@ -1,13 +1,12 @@ -#if UNITY_IOS && !UNITY_EDITOR +#if (UNITY_IOS || UNITY_STANDALONE_OSX) && !UNITY_EDITOR using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using AOT; -using System.Runtime.InteropServices; - namespace Thirdweb.Unity { public class ASWebAuthenticationSession : IDisposable @@ -26,7 +25,12 @@ public ASWebAuthenticationSession(string url, string callbackUrlScheme, ASWebAut { _sessionPtr = Thirdweb_ASWebAuthenticationSession_InitWithURL(url, callbackUrlScheme, OnAuthenticationSessionCompleted); - CompletionCallbacks.Add(_sessionPtr, completionHandler); + if (_sessionPtr == IntPtr.Zero) + { + throw new InvalidOperationException("Failed to initialize ASWebAuthenticationSession."); + } + + CompletionCallbacks[_sessionPtr] = completionHandler; } public bool Start() @@ -41,11 +45,18 @@ public void Cancel() public void Dispose() { - CompletionCallbacks.Remove(_sessionPtr); + if (_sessionPtr != IntPtr.Zero) + { + CompletionCallbacks.Remove(_sessionPtr); + } _sessionPtr = IntPtr.Zero; } +#if UNITY_STANDALONE_OSX + private const string DllName = "MacBrowser"; +#else private const string DllName = "__Internal"; +#endif [DllImport(DllName)] private static extern IntPtr Thirdweb_ASWebAuthenticationSession_InitWithURL(string url, string callbackUrlScheme, AuthenticationSessionCompletedCallback completionHandler); @@ -76,6 +87,7 @@ private static void OnAuthenticationSessionCompleted(IntPtr session, string call } } +#if UNITY_IOS public class IOSBrowser : IThirdwebBrowser { private TaskCompletionSource _taskCompletionSource; @@ -134,6 +146,68 @@ private void AuthenticationSessionCompletionHandler(string callbackUrl, ASWebAut } } } +#endif + +#if UNITY_STANDALONE_OSX + public class MacBrowser : IThirdwebBrowser + { + private TaskCompletionSource _taskCompletionSource; + + public bool prefersEphemeralWebBrowserSession { get; set; } = false; + + public async Task Login(ThirdwebClient client, string loginUrl, string redirectUrl, Action browserOpenAction, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(loginUrl)) + throw new ArgumentNullException(nameof(loginUrl)); + + if (string.IsNullOrEmpty(redirectUrl)) + throw new ArgumentNullException(nameof(redirectUrl)); + + _taskCompletionSource = new TaskCompletionSource(); + + redirectUrl = redirectUrl.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries)[0]; + + using var authenticationSession = new ASWebAuthenticationSession(loginUrl, redirectUrl, AuthenticationSessionCompletionHandler); + authenticationSession.prefersEphemeralWebBrowserSession = prefersEphemeralWebBrowserSession; + + cancellationToken.Register(() => + { + _taskCompletionSource?.TrySetCanceled(); + }); + + try + { + if (!authenticationSession.Start()) + { + _taskCompletionSource.SetResult(new BrowserResult(BrowserStatus.UnknownError, "Browser could not be started.")); + } + + return await _taskCompletionSource.Task; + } + catch (TaskCanceledException) + { + authenticationSession?.Cancel(); + throw; + } + } + + private void AuthenticationSessionCompletionHandler(string callbackUrl, ASWebAuthenticationSessionError error) + { + if (error.code == ASWebAuthenticationSessionErrorCode.None) + { + _taskCompletionSource.SetResult(new BrowserResult(BrowserStatus.Success, callbackUrl)); + } + else if (error.code == ASWebAuthenticationSessionErrorCode.CanceledLogin) + { + _taskCompletionSource.SetResult(new BrowserResult(BrowserStatus.UserCanceled, callbackUrl, error.message)); + } + else + { + _taskCompletionSource.SetResult(new BrowserResult(BrowserStatus.UnknownError, callbackUrl, error.message)); + } + } + } +#endif public class ASWebAuthenticationSessionError { diff --git a/Assets/Thirdweb/Runtime/Unity/ThirdwebManagerBase.cs b/Assets/Thirdweb/Runtime/Unity/ThirdwebManagerBase.cs index 3ff9a314..b9613f5a 100644 --- a/Assets/Thirdweb/Runtime/Unity/ThirdwebManagerBase.cs +++ b/Assets/Thirdweb/Runtime/Unity/ThirdwebManagerBase.cs @@ -452,7 +452,7 @@ public virtual async Task ConnectWallet(WalletOptions walletOpt break; case AuthProvider.SiweExternal: _ = await inAppWallet.LoginWithSiweExternal( - isMobile: Application.isMobilePlatform, + isMobile: IsMobileRuntime(), browserOpenAction: (url) => Application.OpenURL(url), forceWalletIds: walletOptions.InAppWalletOptions.ForceSiweExternalWalletIds == null || walletOptions.InAppWalletOptions.ForceSiweExternalWalletIds.Count == 0 ? null @@ -463,7 +463,7 @@ public virtual async Task ConnectWallet(WalletOptions walletOpt break; default: _ = await inAppWallet.LoginWithOauth( - isMobile: Application.isMobilePlatform, + isMobile: IsMobileRuntime(), browserOpenAction: (url) => Application.OpenURL(url), mobileRedirectScheme: MobileRedirectScheme, browser: new CrossPlatformUnityBrowser(RedirectPageHtmlOverride) @@ -501,7 +501,7 @@ public virtual async Task ConnectWallet(WalletOptions walletOpt break; case AuthProvider.SiweExternal: _ = await ecosystemWallet.LoginWithSiweExternal( - isMobile: Application.isMobilePlatform, + isMobile: IsMobileRuntime(), browserOpenAction: (url) => Application.OpenURL(url), forceWalletIds: walletOptions.EcosystemWalletOptions.ForceSiweExternalWalletIds == null || walletOptions.EcosystemWalletOptions.ForceSiweExternalWalletIds.Count == 0 ? null @@ -512,7 +512,7 @@ public virtual async Task ConnectWallet(WalletOptions walletOpt break; default: _ = await ecosystemWallet.LoginWithOauth( - isMobile: Application.isMobilePlatform, + isMobile: IsMobileRuntime(), browserOpenAction: (url) => Application.OpenURL(url), mobileRedirectScheme: MobileRedirectScheme, browser: new CrossPlatformUnityBrowser(RedirectPageHtmlOverride) @@ -588,7 +588,7 @@ public virtual async Task> LinkAccount(IThirdwebWallet mainW return await mainWallet.LinkAccount( walletToLink: walletToLink, otp: otp, - isMobile: Application.isMobilePlatform, + isMobile: IsMobileRuntime(), browserOpenAction: (url) => Application.OpenURL(url), mobileRedirectScheme: MobileRedirectScheme, browser: new CrossPlatformUnityBrowser(RedirectPageHtmlOverride), @@ -598,6 +598,16 @@ public virtual async Task> LinkAccount(IThirdwebWallet mainW ); } + protected virtual bool IsMobileRuntime() + { + if (Application.platform == RuntimePlatform.OSXPlayer) + { + return true; + } + + return Application.isMobilePlatform; + } + protected virtual bool GetAutoConnectOptions(out WalletOptions lastWalletOptions) { var connectOptionsStr = PlayerPrefs.GetString(THIRDWEB_AUTO_CONNECT_OPTIONS_KEY, null);