-
Notifications
You must be signed in to change notification settings - Fork 2
Replace shared(apiKey:) with configure() and setJWT #91
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 9 commits
4a82e63
f1a7196
5a6233c
adb95c5
f2b3580
eb13ddf
d7f3d08
e7df3a0
e3ed53f
5fbc9ff
d9155d5
9e3e512
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,36 +12,39 @@ import Web | |
|
|
||
| @MainActor | ||
| final public class CrossmintSDK: ObservableObject { | ||
| nonisolated(unsafe) private static var _shared: CrossmintSDK? | ||
| @MainActor private static var _shared: CrossmintSDK? | ||
|
|
||
| public static var shared: CrossmintSDK { | ||
| guard let shared = _shared else { | ||
| let newInstance = CrossmintSDK() | ||
| _shared = newInstance | ||
| return newInstance | ||
| @MainActor public static var shared: CrossmintSDK { | ||
| #if DEBUG | ||
| if _shared == nil, let envApiKey = ProcessInfo.processInfo.environment["CROSSMINT_API_KEY"] { | ||
| Logger.client.info("Using API key from the environment variable.") | ||
| configure(apiKey: envApiKey) | ||
| } | ||
| #endif | ||
| guard let instance = _shared else { | ||
| fatalError( | ||
| "CrossmintSDK is not configured. " + | ||
| "Call CrossmintSDK.configure(apiKey:) before accessing CrossmintSDK.shared." | ||
| ) | ||
| } | ||
| return shared | ||
| return instance | ||
| } | ||
|
|
||
| public static func shared( | ||
| apiKey: String, | ||
| authManager: AuthManager? = nil, | ||
| logLevel: LogLevel = .error | ||
| ) -> CrossmintSDK { | ||
| if let existing = _shared { | ||
| return existing | ||
| /// Configures the SDK with the given API key. Must be called before accessing `CrossmintSDK.shared`. | ||
| /// Subsequent calls are ignored — the SDK can only be configured once per process. | ||
| @MainActor public static func configure(apiKey: String, logLevel: LogLevel = .error) { | ||
| guard _shared == nil else { | ||
| Logger.sdk.warn("CrossmintSDK.configure() called after SDK is already configured — ignoring") | ||
| return | ||
| } | ||
|
|
||
| Logger.level = logLevel | ||
| let newInstance = CrossmintSDK(apiKey: apiKey, authManager: authManager) | ||
| _shared = newInstance | ||
| return newInstance | ||
| _shared = CrossmintSDK(apiKey: apiKey) | ||
| } | ||
|
|
||
| private let sdk: ClientSDK | ||
| private let sdk: ClientSDK | ||
|
tomas-martins-crossmint marked this conversation as resolved.
Outdated
|
||
|
|
||
| public let crossmintWallets: CrossmintWallets | ||
|
tomas-martins-crossmint marked this conversation as resolved.
Outdated
|
||
| public let authManager: AuthManager | ||
| public let authManager: CrossmintAuthManager | ||
| public let crossmintService: CrossmintService | ||
|
|
||
| let crossmintTEE: CrossmintTEE | ||
|
|
@@ -60,46 +63,64 @@ final public class CrossmintSDK: ObservableObject { | |
| crossmintService.isProductionEnvironment | ||
| } | ||
|
|
||
| private convenience init() { | ||
| #if DEBUG | ||
| if let apiKey = ProcessInfo.processInfo.environment["CROSSMINT_API_KEY"] { | ||
| Logger.client.info("Using API key from the environment variable.") | ||
| self.init(apiKey: apiKey) | ||
| return | ||
| } | ||
| #endif | ||
| Logger.client.error("Crossmint SDK requires an API key") | ||
| fatalError( | ||
| "Crossmint SDK requires an API key. " + | ||
| "Please call CrossmintSDK.shared(apiKey:) before accessing CrossmintSDK.shared" | ||
| ) | ||
| /// Sets a JWT for authentication. Use this when authenticating with an externally obtained token | ||
| /// rather than through the built-in OTP flow. | ||
| /// | ||
| /// - Note: Unlike the TypeScript SDK's synchronous `setJwt`, this is `async` because it | ||
| /// updates actor-isolated state on `CrossmintAuthManager`. | ||
| public func setJWT(_ jwt: String) async { | ||
| await authManager.setJWT(jwt) | ||
| } | ||
|
|
||
| private init(apiKey: String, authManager: AuthManager? = nil) { | ||
| private struct Components { | ||
| let sdk: ClientSDK | ||
| let wallets: CrossmintWallets | ||
| let authManager: CrossmintAuthManager | ||
| let service: CrossmintService | ||
| let tee: CrossmintTEE | ||
| } | ||
|
tomas-martins-crossmint marked this conversation as resolved.
Outdated
|
||
|
|
||
| private init(apiKey: String) { | ||
| sdkInstances += 1 | ||
| if sdkInstances > 1 { | ||
| Logger.sdk.error("Multiple SDK instances created, behaviour is undefined") | ||
| } | ||
| let components = Self.makeComponents(apiKey: apiKey) | ||
| sdk = components.sdk | ||
| crossmintWallets = components.wallets | ||
| authManager = components.authManager | ||
| crossmintService = components.service | ||
| crossmintTEE = components.tee | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is what I mean, I think it stems also from the try/catch maybe? Which step can actually throw? If we want to condense the code, i'd be thinking more: with
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, on a second look the whole |
||
| } | ||
|
|
||
| private static func makeComponents(apiKey: String) -> Components { | ||
| do { | ||
| sdk = try CrossmintClient.sdk(key: apiKey, authManager: authManager) | ||
| let sdk = try CrossmintClient.sdk(key: apiKey) | ||
| let authManager = sdk.authManager | ||
| self.crossmintWallets = sdk.crossmintWallets() | ||
| self.authManager = authManager | ||
| self.crossmintService = sdk.crossmintService | ||
| self.crossmintTEE = CrossmintTEE.start( | ||
| auth: authManager, | ||
| webProxy: DefaultWebViewCommunicationProxy(), | ||
| apiKey: apiKey, | ||
| isProductionEnvironment: sdk.crossmintService.isProductionEnvironment | ||
| return Components( | ||
| sdk: sdk, | ||
| wallets: sdk.crossmintWallets(), | ||
| authManager: authManager, | ||
| service: sdk.crossmintService, | ||
| tee: CrossmintTEE.start( | ||
| auth: authManager, | ||
| webProxy: DefaultWebViewCommunicationProxy(), | ||
| apiKey: apiKey, | ||
| isProductionEnvironment: sdk.crossmintService.isProductionEnvironment | ||
| ) | ||
| ) | ||
| } catch { | ||
| Logger.client.error("Invalid Crossmint API key provided: \(error)") | ||
| fatalError("Invalid Crossmint API key provided. Please verify your API key is a valid client key.") | ||
| } | ||
| } | ||
|
|
||
| public func logout() async throws { | ||
| public func logout() async { | ||
| do { | ||
| _ = try await authManager.logout() | ||
| } catch { | ||
| Logger.sdk.warn("Logout request failed: \(error) — clearing local state anyway") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what can happen here if we're not logged out but clear local state? What's the difference?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the intent was to avoid locking users out if the logout endpoint request fails. But on the current implementation, if that request throws, we'd also never clear the user's JWT and refresh tokens from keychain, which could lead to the user getting automatically logged in on the next app launch, if the client doesn't have any safeguards to that the only part that can throw is the logout network call, so we could clear the credentials before making the request instead, so even if that fails, the client would still lose access to the JWT and the user wouldn't end up accidentally being signed in again, regardless of the request result |
||
| } | ||
| crossmintTEE.resetState() | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.