@@ -7,6 +7,8 @@ import MozillaAppServices
77import Account
88import Shared
99
10+ typealias RelayPopulateCompletion = @MainActor @Sendable ( RelayMaskGenerationResult ) -> Void
11+
1012/// Describes public protocol for Relay component to track state and facilitate
1113/// messaging between the BVC, keyboard accessory, and A~S Relay APIs.
1214protocol RelayControllerProtocol {
@@ -31,7 +33,7 @@ protocol RelayControllerProtocol {
3133 /// - Parameter tab: the tab to populate. The email field is expected to be focused, otherwise a JS error will be logged.
3234 @MainActor
3335 func populateEmailFieldWithRelayMask( for tab: Tab ,
34- completion: @Sendable @ MainActor @ escaping ( RelayMaskGenerationResult ) -> Void )
36+ completion: @escaping RelayPopulateCompletion )
3537
3638 /// Notifies the RelayController which tab is currently focused for the purposes of generating a Relay mask.
3739 /// - Parameter tab: the current tab.
@@ -46,6 +48,8 @@ enum RelayMaskGenerationResult {
4648 /// User is on a free plan and their limit has been reached.
4749 /// For Phase 1, one of the user's existing masks will be randomly picked.
4850 case freeTierLimitReached
51+ /// Generation failed due to expired OAuth token.
52+ case expiredToken
4953 /// A problem occurred.
5054 case error
5155}
@@ -146,7 +150,13 @@ final class RelayController: RelayControllerProtocol, Notifiable {
146150 }
147151
148152 func populateEmailFieldWithRelayMask( for tab: Tab ,
149- completion: @escaping @MainActor @Sendable ( RelayMaskGenerationResult ) -> Void ) {
153+ completion: @escaping RelayPopulateCompletion ) {
154+ populateEmailFieldWithRelayMask ( for: tab, isRetry: false , completion: completion)
155+ }
156+
157+ private func populateEmailFieldWithRelayMask( for tab: Tab ,
158+ isRetry: Bool ,
159+ completion: @escaping RelayPopulateCompletion ) {
150160 guard focusedTab == nil || focusedTab === tab else {
151161 logger. log ( " Attempting to populate Relay mask after tab has changed. Bailing. " ,
152162 level: . warning,
@@ -161,11 +171,20 @@ final class RelayController: RelayControllerProtocol, Notifiable {
161171 logger. log ( " No tab webview available, or client is nil. Will not populate email field. " ,
162172 level: . warning,
163173 category: . relay)
174+ completion ( . error)
164175 return
165176 }
166177 Task {
167178 let ( email, result) = await generateRelayMask ( for: tab. url? . baseDomain ?? " " , client: client)
168- guard result != . error else { completion ( . error) ; return }
179+
180+ if result == . expiredToken && !isRetry {
181+ // Attempt a single retry of OAuth refresh
182+ attemptOAuthTokenRefresh ( tab: tab, completion: completion)
183+ return
184+ }
185+
186+ // If an error occurred, or our OAuth token refresh attempt failed, complete with error.
187+ guard result != . error && result != . expiredToken else { completion ( . error) ; return }
169188
170189 guard let jsonData = try ? JSONEncoder ( ) . encode ( email) ,
171190 let encodedEmailStr = String ( data: jsonData, encoding: . utf8) else {
@@ -197,6 +216,21 @@ final class RelayController: RelayControllerProtocol, Notifiable {
197216
198217 // MARK: - Private Utilities
199218
219+ private func invalidateClient( ) {
220+ client = nil
221+ }
222+
223+ private func attemptOAuthTokenRefresh( tab: Tab , completion: @escaping RelayPopulateCompletion ) {
224+ // Attempt to refresh OAuth token and retry.
225+ logger. log ( " Attempting OAuth refresh. Will re-create Relay client. " , level: . info, category: . relay)
226+ invalidateClient ( )
227+ createRelayClientIfNeeded { [ weak self] in
228+ // This completion will be called async after we attempt to re-create a new RelayClient
229+ // with a fresh OAuth token. At this point we can re-try to populate.
230+ self ? . populateEmailFieldWithRelayMask ( for: tab, isRetry: true , completion: completion)
231+ }
232+ }
233+
200234 nonisolated private func generateRelayMask( for websiteDomain: String ,
201235 client: RelayClient ) async -> ( mask: String ? ,
202236 result: RelayMaskGenerationResult ) {
@@ -206,7 +240,12 @@ final class RelayController: RelayControllerProtocol, Notifiable {
206240 return ( relayAddress. fullAddress, . newMaskGenerated)
207241 } catch {
208242 // Certain errors we need to custom-handle
209- if case let RelayApiError . Api( _, code, _) = error, code == " free_tier_limit " {
243+
244+ if case let RelayApiError . Api( status, code, _) = error, status == 401 , code == " invalid_token " {
245+ // Invalid OAuth token
246+ logger. log ( " OAuth token expired. " , level: . info, category: . relay)
247+ return ( nil , . expiredToken)
248+ } else if case let RelayApiError . Api( _, code, _) = error, code == " free_tier_limit " {
210249 // For Phase 1, we return a random email from the user's list
211250 logger. log ( " Free tier limit reached. Using random mask. " , level: . info, category: . relay)
212251 do {
@@ -274,7 +313,8 @@ final class RelayController: RelayControllerProtocol, Notifiable {
274313 }
275314
276315 /// Creates the Relay client, if needed. This is safe to call redundantly.
277- private func createRelayClientIfNeeded( ) {
316+ /// Optional completion block.
317+ private func createRelayClientIfNeeded( completion: ( ( ) -> Void ) ? = nil ) {
278318 guard client == nil , !isCreatingClient else { return }
279319 guard let acctManager = RustFirefoxAccounts . shared. accountManager else {
280320 logger. log ( " Couldn't create client, no account manager. " , level: . debug, category: . relay)
@@ -286,6 +326,7 @@ final class RelayController: RelayControllerProtocol, Notifiable {
286326 case . failure( let error) :
287327 self ? . logger. log ( " Error getting access token for Relay: \( error) " , level: . warning, category: . relay)
288328 self ? . isCreatingClient = false
329+ completion ? ( )
289330 case . success( let tokenInfo) :
290331 do {
291332 let clientResult = try RelayClient ( serverUrl: config. serverURL, authToken: tokenInfo. token)
@@ -294,6 +335,7 @@ final class RelayController: RelayControllerProtocol, Notifiable {
294335 self ? . logger. log ( " Error creating Relay client: \( error) " , level: . warning, category: . relay)
295336 }
296337 self ? . isCreatingClient = false
338+ completion ? ( )
297339 }
298340 }
299341 }
0 commit comments