Skip to content

Commit 438935f

Browse files
committed
Merge branch 'trunk' into feat/WOOMOB-1289-persist-incremental-sync-date
2 parents 331b152 + 3914fc5 commit 438935f

File tree

72 files changed

+1649
-31916
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+1649
-31916
lines changed

Modules/Sources/NetworkingCore/ApplicationPassword/RequestProcessor.swift

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
import Alamofire
22
import Foundation
33

4-
enum AppPasswordFailureReason {
5-
case notSupported
6-
case unknown
7-
}
8-
94
protocol RequestProcessorDelegate: AnyObject {
10-
func didFailToAuthenticateRequestWithAppPassword(siteID: Int64, reason: AppPasswordFailureReason)
5+
func didFailToAuthenticateRequestWithAppPassword(siteID: Int64)
116
}
127

138
/// Authenticates and retries requests
@@ -92,7 +87,7 @@ private extension RequestProcessor {
9287
isAuthenticating = false
9388
completeRequests(false)
9489
if let currentSiteID {
95-
notifyFailure(error, for: currentSiteID)
90+
notifyFailureIfNeeded(error, for: currentSiteID)
9691
}
9792
}
9893
}
@@ -120,22 +115,24 @@ private extension RequestProcessor {
120115
}
121116
}
122117

123-
func notifyFailure(_ error: Error, for siteID: Int64) {
124-
let reason: AppPasswordFailureReason = {
118+
func notifyFailureIfNeeded(_ error: Error, for siteID: Int64) {
119+
let appPasswordNotSupported: Bool = {
125120
switch error {
126121
case NetworkError.notFound:
127-
return .notSupported
122+
return true
128123
case let networkError as NetworkError:
129124
if let code = networkError.errorCode,
130125
AppPasswordConstants.disabledCodes.contains(code) {
131-
return .notSupported
126+
return true
132127
}
133-
return .unknown
128+
return false
134129
default:
135-
return .unknown
130+
return false
136131
}
137132
}()
138-
delegate?.didFailToAuthenticateRequestWithAppPassword(siteID: siteID, reason: reason)
133+
if appPasswordNotSupported {
134+
delegate?.didFailToAuthenticateRequestWithAppPassword(siteID: siteID)
135+
}
139136
}
140137

141138
func shouldRetry(_ error: Error) -> Bool {

Modules/Sources/NetworkingCore/Network/AlamofireNetwork.swift

Lines changed: 23 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -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
//
294295
private 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
//
381307
extension 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

418314
private 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
//
462362
extension 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

Comments
 (0)