@@ -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,
@@ -284,7 +282,7 @@ private extension AlamofireNetwork {
284282 network: self
285283 ) )
286284 requestAuthenticator. delegate = self
287- appPasswordFailures . removeValue ( forKey : site. siteID) // reset failure count
285+ errorHandler . resetFailureCount ( for : site. siteID) // reset failure count
288286 }
289287 }
290288}
@@ -293,127 +291,22 @@ private extension AlamofireNetwork {
293291//
294292private extension AlamofireNetwork {
295293 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 {
294+ if errorHandler. isRequestRetried ( request) {
302295 return request // do not convert
303296 }
304297 return requestConverter. convert ( request)
305298 }
306299
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- }
377300}
378301
379302// MARK: `RequestProcessorDelegate` conformance
380303//
381304extension 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
305+ func didFailToAuthenticateRequestWithAppPassword( siteID: Int64 ) {
306+ errorHandler. flagSiteAsUnsupported ( for: siteID)
404307 }
405308}
406309
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- }
417310
418311private extension DataRequest {
419312 /// Validates only for `RESTRequest`
@@ -450,6 +343,10 @@ extension Alamofire.DataResponse {
450343 return error
451344 }
452345
346+ if case . some( AFError . requestAdaptationFailed) = error? . asAFError {
347+ return error
348+ }
349+
453350 return response. flatMap { response in
454351 NetworkError ( responseData: data,
455352 statusCode: response. statusCode)
0 commit comments