@@ -8,35 +8,29 @@ protocol RequestProcessorDelegate: AnyObject {
88/// Authenticates and retries requests
99///
1010final class RequestProcessor : RequestInterceptor {
11- private var requestsToRetry = [ ( RetryResult ) -> Void ] ( )
12-
13- private var isAuthenticating = false
14-
15- private var requestAuthenticator : RequestAuthenticator
16-
1711 private let notificationCenter : NotificationCenter
1812
19- private var currentSiteID : Int64 ?
13+ private let state : RequestProcessorState
2014
2115 weak var delegate : RequestProcessorDelegate ?
2216
2317 init ( requestAuthenticator: RequestAuthenticator ,
2418 notificationCenter: NotificationCenter = . default) {
25- self . requestAuthenticator = requestAuthenticator
2619 self . notificationCenter = notificationCenter
20+ self . state = RequestProcessorState ( requestAuthenticator: requestAuthenticator)
2721 }
2822
2923 func updateAuthenticator( _ authenticator: RequestAuthenticator ) {
30- requestAuthenticator = authenticator
31- currentSiteID = authenticator. jetpackSiteID
24+ state. updateAuthenticator ( authenticator)
3225 }
3326}
3427
3528// MARK: Request Authentication
3629//
3730extension RequestProcessor : RequestAdapter {
3831 func adapt( _ urlRequest: URLRequest , for session: Session , completion: @escaping ( Result < URLRequest , Error > ) -> Void ) {
39- let result = Result { try requestAuthenticator. authenticate ( urlRequest) }
32+ let authenticator = state. authenticator
33+ let result = Result { try authenticator. authenticate ( urlRequest) }
4034 completion ( result)
4135 }
4236}
@@ -45,18 +39,27 @@ extension RequestProcessor: RequestAdapter {
4539//
4640extension RequestProcessor : RequestRetrier {
4741 func retry( _ request: Alamofire . Request , for session: Session , dueTo error: Error , completion: @escaping ( RetryResult ) -> Void ) {
48- guard
49- request. retryCount == 0 , // Only retry once
50- let urlRequest = request. request,
51- requestAuthenticator. shouldRetry ( urlRequest) , // Retry only REST API requests that use application password
52- shouldRetry ( error) // Retry only specific errors
53- else {
54- return completion ( . doNotRetry)
55- }
56-
57- requestsToRetry. append ( completion)
58- if !isAuthenticating {
59- isAuthenticating = true
42+ guard request. retryCount == 0 , // Only retry once
43+ let urlRequest = request. request else {
44+ completion ( . doNotRetry)
45+ return
46+ }
47+
48+ guard shouldRetry ( error) else {
49+ completion ( . doNotRetry)
50+ return
51+ }
52+
53+ let shouldRetryRequest = state. shouldRetry ( urlRequest)
54+
55+ guard shouldRetryRequest else {
56+ completion ( . doNotRetry)
57+ return
58+ }
59+
60+ let shouldStartAuthentication = state. enqueueRetry ( completion)
61+
62+ if shouldStartAuthentication {
6063 generateApplicationPassword ( )
6164 }
6265 }
@@ -66,28 +69,30 @@ extension RequestProcessor: RequestRetrier {
6669//
6770private extension RequestProcessor {
6871 func generateApplicationPassword( ) {
69- Task ( priority: . medium) { @MainActor in
72+ Task ( priority: . medium) { @MainActor [ weak self] in
73+ guard let self else { return }
74+ let authenticator = self . state. authenticator
7075 do {
71- let _ = try await requestAuthenticator . generateApplicationPassword ( )
72- isAuthenticating = false
76+ let _ = try await authenticator . generateApplicationPassword ( )
77+ self . state . setAuthenticating ( false )
7378
7479 // Post a notification for tracking
75- notificationCenter. post ( name: . ApplicationPasswordsNewPasswordCreated, object: nil , userInfo: nil )
80+ self . notificationCenter. post ( name: . ApplicationPasswordsNewPasswordCreated, object: nil , userInfo: nil )
7681
77- completeRequests ( true )
82+ self . completeRequests ( true )
7883 } catch {
7984
8085 // Post a notification for tracking
81- notificationCenter. post ( name: . ApplicationPasswordsGenerationFailed, object: error, userInfo: nil )
86+ self . notificationCenter. post ( name: . ApplicationPasswordsGenerationFailed, object: error, userInfo: nil )
8287
83- let shouldRetry = await checkIfRetryingGenerationIsNeeded ( for: error)
88+ let shouldRetry = await self . checkIfRetryingGenerationIsNeeded ( for: error)
8489 if shouldRetry {
85- generateApplicationPassword ( )
90+ self . generateApplicationPassword ( )
8691 } else {
87- isAuthenticating = false
88- completeRequests ( false , error: error)
89- if let currentSiteID {
90- notifyFailureIfNeeded ( error, for: currentSiteID )
92+ self . state . setAuthenticating ( false )
93+ self . completeRequests ( false , error: error)
94+ if let siteID = self . state . currentSiteID {
95+ self . notifyFailureIfNeeded ( error, for: siteID )
9196 }
9297 }
9398 }
@@ -98,14 +103,15 @@ private extension RequestProcessor {
98103 /// Returns whether retry is needed.
99104 @MainActor
100105 func checkIfRetryingGenerationIsNeeded( for error: Error ) async -> Bool {
101- guard currentSiteID != nil else {
106+ guard state . currentSiteID != nil else {
102107 return false
103108 }
104109 switch error {
105110 case NetworkError . unacceptableStatusCode( let statusCode, _) where statusCode == 409 :
106111 /// Password with the same name already exists. Request deletion remotely and retry.
107112 do {
108- try await requestAuthenticator. deleteApplicationPassword ( )
113+ let authenticator = state. authenticator
114+ try await authenticator. deleteApplicationPassword ( )
109115 return true
110116 } catch {
111117 return false
@@ -156,10 +162,10 @@ private extension RequestProcessor {
156162 . doNotRetry
157163 }
158164 } ( )
159- requestsToRetry. forEach { ( completion) in
165+ let pendingCompletions = state. drainPendingRetries ( )
166+ pendingCompletions. forEach { completion in
160167 completion ( result)
161168 }
162- requestsToRetry. removeAll ( )
163169 }
164170}
165171
@@ -182,3 +188,68 @@ public extension NSNotification.Name {
182188 ///
183189 static let JetpackSiteEligibleForAppPasswordSupport = NSNotification . Name ( rawValue: " JetpackSiteEligibleForAppPasswordSupport " )
184190}
191+
192+ private extension RequestProcessor {
193+ final class RequestProcessorState {
194+ private var requestsToRetry : [ ( RetryResult ) -> Void ]
195+ private var isAuthenticating : Bool
196+ private var requestAuthenticator : RequestAuthenticator
197+ private var siteID : Int64 ?
198+
199+ private let queue = DispatchQueue (
200+ label: " com.woocommerce.networking.request-processor.state-queue " ,
201+ qos: . userInitiated
202+ )
203+
204+ init ( requestAuthenticator: RequestAuthenticator ) {
205+ self . requestsToRetry = [ ]
206+ self . isAuthenticating = false
207+ self . requestAuthenticator = requestAuthenticator
208+ self . siteID = requestAuthenticator. jetpackSiteID
209+ }
210+
211+ var authenticator : RequestAuthenticator {
212+ queue. sync { requestAuthenticator }
213+ }
214+
215+ var currentSiteID : Int64 ? {
216+ queue. sync { siteID }
217+ }
218+
219+ func shouldRetry( _ request: URLRequest ) -> Bool {
220+ queue. sync { requestAuthenticator. shouldRetry ( request) }
221+ }
222+
223+ func enqueueRetry( _ completion: @escaping ( RetryResult ) -> Void ) -> Bool {
224+ queue. sync {
225+ requestsToRetry. append ( completion)
226+ if isAuthenticating {
227+ return false
228+ }
229+ isAuthenticating = true
230+ return true
231+ }
232+ }
233+
234+ func setAuthenticating( _ value: Bool ) {
235+ queue. sync {
236+ isAuthenticating = value
237+ }
238+ }
239+
240+ func drainPendingRetries( ) -> [ ( RetryResult ) -> Void ] {
241+ queue. sync {
242+ let completions = requestsToRetry
243+ requestsToRetry. removeAll ( )
244+ return completions
245+ }
246+ }
247+
248+ func updateAuthenticator( _ authenticator: RequestAuthenticator ) {
249+ queue. sync {
250+ requestAuthenticator = authenticator
251+ siteID = authenticator. jetpackSiteID
252+ }
253+ }
254+ }
255+ }
0 commit comments