@@ -49,11 +49,8 @@ public class AlamofireNetwork: Network {
4949
5050 private var subscription : AnyCancellable ?
5151
52- /// Keeps track of failure counts for each site when switching to direct requests
53- private var appPasswordFailures : [ Int64 : Int ] = [ : ]
54-
55- /// Keeps track of retried requests when direct requests fail
56- private var retriedJetpackRequests : [ RetriedJetpackRequest ] = [ ]
52+ /// Thread-safe error handler for failure tracking and retry logic
53+ private let errorHandler : AlamofireNetworkErrorHandler
5754
5855 /// Public Initializer
5956 ///
@@ -71,6 +68,7 @@ public class AlamofireNetwork: Network {
7168 self . credentials = credentials
7269 self . selectedSite = selectedSite
7370 self . userDefaults = userDefaults
71+ self . errorHandler = AlamofireNetworkErrorHandler ( credentials: credentials, userDefaults: userDefaults)
7472 self . requestConverter = {
7573 let siteAddress : String ? = {
7674 switch credentials {
@@ -123,7 +121,7 @@ public class AlamofireNetwork: Network {
123121 alamofireSession. request ( convertedRequest)
124122 . validateIfRestRequest ( for: convertedRequest)
125123 . responseData { [ weak self] response in
126- self ? . handleFailureForDirectRequestIfNeeded (
124+ self ? . errorHandler . handleFailureForDirectRequestIfNeeded (
127125 originalRequest: request,
128126 convertedRequest: convertedRequest,
129127 failure: response. networkingError,
@@ -151,7 +149,7 @@ public class AlamofireNetwork: Network {
151149 alamofireSession. request ( convertedRequest)
152150 . validateIfRestRequest ( for: convertedRequest)
153151 . responseData { [ weak self] response in
154- self ? . handleFailureForDirectRequestIfNeeded (
152+ self ? . errorHandler . handleFailureForDirectRequestIfNeeded (
155153 originalRequest: request,
156154 convertedRequest: convertedRequest,
157155 failure: response. networkingError,
@@ -176,15 +174,15 @@ public class AlamofireNetwork: Network {
176174 let response = await sessionRequest. serializingData ( ) . response
177175 let failure = response. networkingError
178176
179- if shouldRetryJetpackRequest (
177+ if errorHandler . shouldRetryJetpackRequest (
180178 originalRequest: request,
181179 convertedRequest: convertedRequest,
182180 failure: failure
183181 ) {
184182 return try await responseDataAndHeaders ( for: request)
185183 }
186184
187- flagSiteAsUnsupportedForAppPasswordIfNeeded ( originalRequest: request, failure: failure)
185+ errorHandler . flagSiteAsUnsupportedForAppPasswordIfNeeded ( originalRequest: request, failure: failure)
188186
189187 if let error = response. networkingError {
190188 throw error
@@ -212,7 +210,7 @@ public class AlamofireNetwork: Network {
212210 . request ( convertedRequest)
213211 . validateIfRestRequest ( for: convertedRequest)
214212 . responseData { [ weak self] response in
215- self ? . handleFailureForDirectRequestIfNeeded (
213+ self ? . errorHandler . handleFailureForDirectRequestIfNeeded (
216214 originalRequest: request,
217215 convertedRequest: convertedRequest,
218216 failure: response. networkingError,
@@ -240,7 +238,7 @@ public class AlamofireNetwork: Network {
240238 alamofireSession
241239 . upload ( multipartFormData: multipartFormData, with: convertedRequest)
242240 . responseData { [ weak self] response in
243- self ? . handleFailureForDirectRequestIfNeeded (
241+ self ? . errorHandler . handleFailureForDirectRequestIfNeeded (
244242 originalRequest: request,
245243 convertedRequest: convertedRequest,
246244 failure: response. networkingError,
@@ -271,7 +269,10 @@ private extension AlamofireNetwork {
271269 . sink { [ weak self] site, unsupportedList in
272270 guard let self else { return }
273271 guard let site, site. applicationPasswordAvailable,
274- unsupportedList. contains ( site. siteID) == false else {
272+ errorHandler. siteFlaggedAsUnsupported (
273+ siteID: site. siteID,
274+ unsupportedList: unsupportedList
275+ ) == false else {
275276 requestConverter = RequestConverter ( siteAddress: nil )
276277 requestAuthenticator. updateAuthenticator ( DefaultRequestAuthenticator ( credentials: credentials) )
277278 requestAuthenticator. delegate = nil
@@ -284,7 +285,7 @@ private extension AlamofireNetwork {
284285 network: self
285286 ) )
286287 requestAuthenticator. delegate = self
287- appPasswordFailures . removeValue ( forKey : site. siteID) // reset failure count
288+ errorHandler . resetFailureCount ( for : site. siteID) // reset failure count
288289 }
289290 }
290291}
@@ -293,127 +294,22 @@ private extension AlamofireNetwork {
293294//
294295private extension AlamofireNetwork {
295296 func convertRequestIfNeeded( _ request: URLRequestConvertible ) -> URLRequestConvertible {
296- let isRetried = retriedJetpackRequests. contains { retriedRequest in
297- let urlRequest = try ? request. asURLRequest ( )
298- let currentItem = try ? retriedRequest. request. asURLRequest ( )
299- return currentItem == urlRequest
300- }
301- if isRetried {
297+ if errorHandler. isRequestRetried ( request) {
302298 return request // do not convert
303299 }
304300 return requestConverter. convert ( request)
305301 }
306302
307- /// Checks if the specified request and error are eligible for retrying as Jetpack request.
308- /// If yes, enqueue the original request to the retried list before returning.
309- ///
310- func shouldRetryJetpackRequest( originalRequest: URLRequestConvertible ,
311- convertedRequest: URLRequestConvertible ,
312- failure: Error ? ) -> Bool {
313- if let request = originalRequest as? JetpackRequest ,
314- convertedRequest is RESTRequest ,
315- case . some( . wpcom) = credentials,
316- let failure = failure as? NetworkError {
317- let retriedRequest = RetriedJetpackRequest ( request: request, error: failure)
318- retriedJetpackRequests. append ( retriedRequest)
319- return true
320- }
321- return false
322- }
323-
324- /// Determines if the site has issue with application password based on the original request.
325- ///
326- func flagSiteAsUnsupportedForAppPasswordIfNeeded( originalRequest: URLRequestConvertible ,
327- failure: Error ? ) {
328- let retriedRequestIndex = retriedJetpackRequests. firstIndex { retriedRequest in
329- let urlRequest = try ? originalRequest. asURLRequest ( )
330- let retriedRequest = try ? retriedRequest. request. asURLRequest ( )
331- return urlRequest == retriedRequest
332- }
333- guard let index = retriedRequestIndex else { return }
334-
335- if failure == nil {
336- let siteID = retriedJetpackRequests [ index] . request. siteID
337- let originalFailure = retriedJetpackRequests [ index] . error
338- switch originalFailure {
339- case . unacceptableStatusCode( statusCode: 401 , _) ,
340- . unacceptableStatusCode( statusCode: 403 , _) ,
341- . unacceptableStatusCode( statusCode: 429 , _) :
342- flagSiteAsUnsupported ( for: siteID)
343- default :
344- if let code = originalFailure. errorCode, AppPasswordConstants . disabledCodes. contains ( code) {
345- flagSiteAsUnsupported ( for: siteID)
346- } else {
347- incrementFailureCount ( for: siteID)
348- }
349- }
350- }
351-
352- // remove retried request from list
353- retriedJetpackRequests. remove ( at: index)
354- }
355-
356- func handleFailureForDirectRequestIfNeeded( originalRequest: URLRequestConvertible ,
357- convertedRequest: URLRequestConvertible ,
358- failure: Error ? ,
359- onRetry: @escaping ( ) -> Void ,
360- onCompletion: @escaping ( ) -> Void ) {
361- if shouldRetryJetpackRequest ( originalRequest: originalRequest,
362- convertedRequest: convertedRequest,
363- failure: failure) {
364- onRetry ( )
365- } else {
366- flagSiteAsUnsupportedForAppPasswordIfNeeded ( originalRequest: originalRequest, failure: failure)
367- onCompletion ( )
368- }
369- }
370-
371- /// Helper type to keep track of retried requests with accompanied error
372- ///
373- struct RetriedJetpackRequest {
374- let request : JetpackRequest
375- let error : NetworkError
376- }
377303}
378304
379305// MARK: `RequestProcessorDelegate` conformance
380306//
381307extension AlamofireNetwork : RequestProcessorDelegate {
382- func didFailToAuthenticateRequestWithAppPassword( siteID: Int64 , reason: AppPasswordFailureReason ) {
383- switch reason {
384- case . notSupported:
385- flagSiteAsUnsupported ( for: siteID)
386- case . unknown:
387- incrementFailureCount ( for: siteID)
388- }
389- }
390-
391- func flagSiteAsUnsupported( for siteID: Int64 ) {
392- let currentList = userDefaults. applicationPasswordUnsupportedList
393- userDefaults. applicationPasswordUnsupportedList = currentList + [ siteID]
394- }
395-
396- func incrementFailureCount( for siteID: Int64 ) {
397- let currentFailureCount = appPasswordFailures [ siteID] ?? 0
398- let updatedCount = currentFailureCount + 1
399- if updatedCount == AppPasswordConstants . requestFailureThreshold {
400- let currentList = userDefaults. applicationPasswordUnsupportedList
401- userDefaults. applicationPasswordUnsupportedList = currentList + [ siteID]
402- }
403- appPasswordFailures [ siteID] = updatedCount
308+ func didFailToAuthenticateRequestWithAppPassword( siteID: Int64 ) {
309+ errorHandler. flagSiteAsUnsupported ( for: siteID)
404310 }
405311}
406312
407- // MARK: - Constants for direct request error handling
408- enum AppPasswordConstants {
409- // flag site as disabled after threshold is reached
410- static let requestFailureThreshold = 10
411- static let disabledCodes = [
412- " application_passwords_disabled " ,
413- " application_passwords_disabled_for_user " ,
414- " incorrect_password "
415- ]
416- }
417313
418314private extension DataRequest {
419315 /// Validates only for `RESTRequest`
@@ -450,6 +346,10 @@ extension Alamofire.DataResponse {
450346 return error
451347 }
452348
349+ if case . some( AFError . requestAdaptationFailed) = error? . asAFError {
350+ return error
351+ }
352+
453353 return response. flatMap { response in
454354 NetworkError ( responseData: data,
455355 statusCode: response. statusCode)
@@ -460,8 +360,8 @@ extension Alamofire.DataResponse {
460360// MARK: - Helper extension to save internal flag for app password availability
461361//
462362extension UserDefaults {
463- @objc dynamic var applicationPasswordUnsupportedList : [ Int64 ] {
464- get { value ( forKey: Key . applicationPasswordUnsupportedList. rawValue) as? [ Int64 ] ?? [ ] }
363+ @objc dynamic var applicationPasswordUnsupportedList : [ String : Date ] {
364+ get { value ( forKey: Key . applicationPasswordUnsupportedList. rawValue) as? [ String : Date ] ?? [ : ] }
465365 set { setValue ( newValue, forKey: Key . applicationPasswordUnsupportedList. rawValue) }
466366 }
467367
0 commit comments