Skip to content

Commit c2ff94b

Browse files
Switch to InitializationStatus publishing for Android parity (#597)
* Add InitializationStatus publishing like we do on Android * Lint
1 parent ef10508 commit c2ff94b

File tree

2 files changed

+52
-26
lines changed

2 files changed

+52
-26
lines changed

Sources/StytchCore/StartupClient.swift

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import Combine
22
import Foundation
33

4-
struct StartupClient {
4+
public enum InitializationStatus: Sendable {
5+
case success
6+
case failure(errors: [Error])
7+
}
8+
9+
enum StartupClient {
510
enum BootstrapRoute: BaseRouteType {
611
case fetch(Path)
712

@@ -19,11 +24,13 @@ struct StartupClient {
1924

2025
static var expectedClientType: ClientType?
2126

22-
static var isInitialized: AnyPublisher<Bool, Never> {
27+
static var isInitialized: AnyPublisher<InitializationStatus, Never> {
2328
isInitializedPublisher.eraseToAnyPublisher()
2429
}
2530

26-
private static let isInitializedPublisher = PassthroughSubject<Bool, Never>()
31+
private static let isInitializedPublisher = PassthroughSubject<InitializationStatus, Never>()
32+
private static var bootstrapError: Error?
33+
private static var sessionHydrationError: Error?
2734

2835
static func start() async throws {
2936
if let expectedClientType {
@@ -40,9 +47,18 @@ struct StartupClient {
4047
async let bootstrap: () = fetchAndApplyBootstrap()
4148

4249
// Await both tasks to complete
43-
_ = try await (auth, bootstrap)
44-
45-
isInitializedPublisher.send(true)
50+
do {
51+
_ = try await (auth, bootstrap)
52+
// We allow the calls to silently fail because they have safe fallbacks, but we want to let the developer know if something went wrong
53+
let potentialErrors = [bootstrapError, sessionHydrationError].compactMap { $0 }
54+
if !potentialErrors.isEmpty {
55+
isInitializedPublisher.send(.failure(errors: potentialErrors))
56+
} else {
57+
isInitializedPublisher.send(.success)
58+
}
59+
} catch {
60+
isInitializedPublisher.send(.failure(errors: [error]))
61+
}
4662

4763
StytchConsoleLogger.log(message: "Stytch SDK initialized for client type: \(clientType)")
4864
}
@@ -53,9 +69,19 @@ struct StartupClient {
5369
}
5470
switch clientType {
5571
case .consumer:
56-
_ = try? await StytchClient.sessions.authenticate(parameters: .init(sessionDurationMinutes: nil))
72+
do {
73+
_ = try await StytchClient.sessions.authenticate(parameters: .init(sessionDurationMinutes: nil))
74+
sessionHydrationError = nil
75+
} catch {
76+
sessionHydrationError = error
77+
}
5778
case .b2b:
58-
_ = try? await StytchB2BClient.sessions.authenticate(parameters: .init(sessionDurationMinutes: nil))
79+
do {
80+
_ = try await StytchB2BClient.sessions.authenticate(parameters: .init(sessionDurationMinutes: nil))
81+
sessionHydrationError = nil
82+
} catch {
83+
sessionHydrationError = error
84+
}
5985
}
6086
}
6187

@@ -83,26 +109,26 @@ struct StartupClient {
83109
}
84110

85111
@discardableResult static func bootstrap() async throws -> BootstrapResponseData {
86-
guard let publicToken = StytchClient.stytchClientConfiguration?.publicToken else {
87-
throw StytchSDKError.consumerSDKNotConfigured
88-
}
89-
90112
// Attempt to fetch the latest bootstrap data from the API using the provided public token.
91113
// If the network request succeeds, extract and use the wrapped response data.
92114
// If the network request fails, fall back to the locally stored bootstrap data.
93115
// If no local data exists, use a predefined default bootstrap data.
94-
let bootstrapResponseData: BootstrapResponseData
95-
if let bootstrapData = try? await router.get(route: .fetch(Path(rawValue: publicToken))) as BootstrapResponse {
96-
bootstrapResponseData = bootstrapData.wrapped
97-
} else if let currentBootstrapData = Current.localStorage.bootstrapData {
98-
bootstrapResponseData = currentBootstrapData
99-
} else {
100-
bootstrapResponseData = BootstrapResponseData.defaultBootstrapData
116+
do {
117+
guard let publicToken = StytchClient.stytchClientConfiguration?.publicToken else {
118+
throw StytchSDKError.consumerSDKNotConfigured
119+
}
120+
let updatedBootstrapData = try await router.get(route: .fetch(Path(rawValue: publicToken))) as BootstrapResponse
121+
bootstrapError = nil
122+
Current.localStorage.bootstrapData = updatedBootstrapData.wrapped
123+
return updatedBootstrapData.wrapped
124+
} catch {
125+
bootstrapError = error
126+
if let currentBootstrapData = Current.localStorage.bootstrapData {
127+
return currentBootstrapData
128+
} else {
129+
Current.localStorage.bootstrapData = BootstrapResponseData.defaultBootstrapData
130+
return BootstrapResponseData.defaultBootstrapData
131+
}
101132
}
102-
103-
// Update the local storage with the resolved bootstrap data before returning it.
104-
Current.localStorage.bootstrapData = bootstrapResponseData
105-
106-
return bootstrapResponseData
107133
}
108134
}

Sources/StytchCore/StytchClientCommon.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ protocol StytchClientCommonInternal: StytchClientCommonPublic {
2727
static var shared: Self { get }
2828
static var clientType: ClientType { get }
2929

30-
static var isInitialized: AnyPublisher<Bool, Never> { get }
30+
static var isInitialized: AnyPublisher<InitializationStatus, Never> { get }
3131

3232
static func handle(url: URL, sessionDurationMinutes: Minutes) async throws -> DeeplinkHandledStatus<DeeplinkResponse, DeeplinkTokenType, DeeplinkRedirectType>
3333
}
@@ -135,7 +135,7 @@ public extension StytchClientCommonPublic {
135135
1. Attempting to call sessions.authenticate (if there's a session token cached on the device).
136136
2. Bootstrapping configuration, including DFP and captcha setup.
137137
*/
138-
static var isInitialized: AnyPublisher<Bool, Never> {
138+
static var isInitialized: AnyPublisher<InitializationStatus, Never> {
139139
StartupClient.isInitialized
140140
}
141141

0 commit comments

Comments
 (0)