|
28 | 28 | */ |
29 | 29 |
|
30 | 30 | #import "SFSDKAuthErrorManager.h" |
| 31 | +#import "SFSDKAuthErrorManager+Internal.h" |
31 | 32 | #import "SFAuthErrorHandlerList.h" |
32 | 33 | #import "SFAuthErrorHandler.h" |
33 | 34 | #import "SFOAuthCoordinator+Internal.h" |
@@ -135,14 +136,19 @@ - (SFAuthErrorHandlerList *)populateDefaultAuthErrorHandlerList |
135 | 136 | }]; |
136 | 137 | [authHandlerList addAuthErrorHandler:self.networkFailureAuthErrorHandler]; |
137 | 138 |
|
138 | | - // Host connection error handler |
| 139 | + // Host connection error handler. |
| 140 | + // |
| 141 | + // NSURLErrorTimedOut / CannotConnectToHost / NetworkConnectionLost / NotConnectedToInternet |
| 142 | + // also appear in +errorIsNetworkFailure:. NetworkFailureErrorHandler runs first in the chain |
| 143 | + // and claims those codes only on Refresh flows with an existing access token; all other |
| 144 | + // contexts return NO there and fall through to this handler. Ordering is guarded by |
| 145 | + // testNetworkFailureClaimsFirst_RefreshWithToken. |
139 | 146 | self.hostConnectionErrorHandler = [[SFAuthErrorHandler alloc] initWithName:kSFHostConnectionErrorHandler |
140 | 147 | authSessionBlock:^BOOL(NSError *error, SFSDKAuthSession *authSession, NSDictionary *options) { |
141 | | - if ((error.userInfo[@"_kCFStreamErrorCodeKey"] && error.userInfo[@"_kCFStreamErrorDomainKey"]) || |
142 | | - ([error.domain isEqualToString:kSFOAuthErrorDomain] && error.code == kSFOAuthErrorInvalidURL)) { |
| 148 | + if ([[weakSelf class] errorIsHostConnectionFailure:error]) { |
143 | 149 | if (self.hostConnectionErrorHandlerBlock) { |
144 | 150 | self.hostConnectionErrorHandlerBlock(error, authSession, options); |
145 | | - return YES; |
| 151 | + return YES; |
146 | 152 | } |
147 | 153 | } |
148 | 154 | return NO; |
@@ -187,11 +193,43 @@ + (BOOL)errorIsInvalidAuthCredentials:(NSError *)error |
187 | 193 | } |
188 | 194 |
|
189 | 195 | /** |
190 | | - * Evaluates an NSError object to see if it represents a network failure during |
191 | | - * an attempted connection. |
| 196 | + * Evaluates an NSError object to see if it represents a host-connection failure — |
| 197 | + * an unreachable or non-existent login host — rather than a transient network |
| 198 | + * failure on an otherwise reachable host. |
192 | 199 | * @param error The NSError to evaluate. |
193 | | - * @return YES if the error represents a network failure, NO otherwise. |
| 200 | + * @return YES if the error should trigger the host-connection recovery path, NO otherwise. |
194 | 201 | */ |
| 202 | ++ (BOOL)errorIsHostConnectionFailure:(NSError *)error |
| 203 | +{ |
| 204 | + if (error == nil || error.domain == nil) { |
| 205 | + return NO; |
| 206 | + } |
| 207 | + // Legacy iOS <= 18 shape: CFNetwork stream stack attached _kCFStreamError* keys to userInfo. |
| 208 | + if (error.userInfo[@"_kCFStreamErrorCodeKey"] && error.userInfo[@"_kCFStreamErrorDomainKey"]) { |
| 209 | + return YES; |
| 210 | + } |
| 211 | + // OAuth invalid-URL is a bad-host signal. |
| 212 | + if ([error.domain isEqualToString:kSFOAuthErrorDomain] && error.code == kSFOAuthErrorInvalidURL) { |
| 213 | + return YES; |
| 214 | + } |
| 215 | + // iOS 26+ shape: DNS resolution moved to Network.framework, so the CFStream keys are absent. |
| 216 | + // NSURLErrorDomain surfaces these codes with a bare userInfo instead. |
| 217 | + if ([error.domain isEqualToString:NSURLErrorDomain]) { |
| 218 | + switch (error.code) { |
| 219 | + case NSURLErrorCannotFindHost: // -1003 |
| 220 | + case NSURLErrorDNSLookupFailed: // -1006 |
| 221 | + case NSURLErrorCannotConnectToHost: // -1004 |
| 222 | + case NSURLErrorTimedOut: // -1001 |
| 223 | + case NSURLErrorNotConnectedToInternet: // -1009 |
| 224 | + case NSURLErrorNetworkConnectionLost: // -1005 |
| 225 | + return YES; |
| 226 | + default: |
| 227 | + break; |
| 228 | + } |
| 229 | + } |
| 230 | + return NO; |
| 231 | +} |
| 232 | + |
195 | 233 | + (BOOL)errorIsNetworkFailure:(NSError *)error |
196 | 234 | { |
197 | 235 | BOOL isNetworkFailure = NO; |
|
0 commit comments