Skip to content

Commit d8d513e

Browse files
[FXIOS-14439] Relay OAuth refresh code
Only nil client, keep account status the same Completion with error if nil client
1 parent 1e2a524 commit d8d513e

File tree

2 files changed

+48
-6
lines changed

2 files changed

+48
-6
lines changed

firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4155,7 +4155,7 @@ class BrowserViewController: UIViewController,
41554155
switch result {
41564156
case .newMaskGenerated:
41574157
UIAccessibility.post(notification: .announcement, argument: a11yAnnounce)
4158-
case .error:
4158+
case .error, .expiredToken:
41594159
let message = String.RelayMask.RelayEmailMaskGenericErrorMessage
41604160
SimpleToast().showAlertWithText(message, bottomContainer: contentContainer, theme: currentTheme())
41614161
case .freeTierLimitReached:

firefox-ios/Client/Frontend/Browser/RelayController.swift

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import MozillaAppServices
77
import Account
88
import 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.
1214
protocol 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

Comments
 (0)