You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-ios/SignalServiceKit/src/Network/API/TSNetworkManager.m

273 lines
11 KiB
Matlab

10 years ago
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
10 years ago
//
#import "TSNetworkManager.h"
#import "AppContext.h"
10 years ago
#import "NSURLSessionDataTask+StatusCode.h"
#import "OWSSignalService.h"
10 years ago
#import "TSAccountManager.h"
#import "TSVerifyCodeRequest.h"
#import <AFNetworking/AFNetworking.h>
10 years ago
NSString *const TSNetworkManagerDomain = @"org.whispersystems.signal.networkManager";
10 years ago
BOOL IsNSErrorNetworkFailure(NSError *_Nullable error)
{
return ([error.domain isEqualToString:TSNetworkManagerDomain] && error.code == 0);
}
10 years ago
@interface TSNetworkManager ()
typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error);
@end
@implementation TSNetworkManager
#pragma mark Singleton implementation
+ (instancetype)sharedManager
{
10 years ago
static TSNetworkManager *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] initDefault];
10 years ago
});
return sharedMyManager;
}
- (instancetype)initDefault
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
9 years ago
{
self = [super init];
if (!self) {
return self;
10 years ago
}
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
9 years ago
OWSSingletonAssert();
10 years ago
return self;
}
#pragma mark Manager Methods
- (void)makeRequest:(TSRequest *)request
success:(TSNetworkManagerSuccess)success
failure:(TSNetworkManagerFailure)failure
{
7 years ago
return [self makeRequest:request completionQueue:dispatch_get_main_queue() success:success failure:failure];
}
- (void)makeRequest:(TSRequest *)request
7 years ago
completionQueue:(dispatch_queue_t)completionQueue
success:(TSNetworkManagerSuccess)successBlock
7 years ago
failure:(TSNetworkManagerFailure)failureBlock
{
OWSAssert(request);
OWSAssert(successBlock);
OWSAssert(failureBlock);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self makeRequestAsync:request completionQueue:completionQueue success:successBlock failure:failureBlock];
});
}
- (void)makeRequestAsync:(TSRequest *)request
completionQueue:(dispatch_queue_t)completionQueue
success:(TSNetworkManagerSuccess)successBlock
failure:(TSNetworkManagerFailure)failureBlock
{
OWSAssert(request);
OWSAssert(successBlock);
OWSAssert(failureBlock);
DDLogInfo(@"%@ Making request: %@", self.logTag, request);
// TODO: Remove this logging when the call connection issues have been resolved.
TSNetworkManagerSuccess success = ^(NSURLSessionDataTask *task, _Nullable id responseObject) {
DDLogInfo(@"%@ request succeeded : %@", self.logTag, request);
successBlock(task, responseObject);
};
TSNetworkManagerFailure failure = [TSNetworkManager errorPrettifyingForFailureBlock:failureBlock request:request];
10 years ago
AFHTTPSessionManager *sessionManager = [OWSSignalService sharedInstance].signalServiceSessionManager;
// [OWSSignalService signalServiceSessionManager] always returns a new instance of
// session manager, so its safe to reconfigure it here.
7 years ago
sessionManager.completionQueue = completionQueue;
10 years ago
if ([request isKindOfClass:[TSVerifyCodeRequest class]]) {
// We plant the Authorization parameter ourselves, no need to double add.
[sessionManager.requestSerializer
10 years ago
setAuthorizationHeaderFieldWithUsername:((TSVerifyCodeRequest *)request).numberToValidate
password:[request.parameters objectForKey:@"AuthKey"]];
NSMutableDictionary *parameters = [request.parameters mutableCopy];
[parameters removeObjectForKey:@"AuthKey"];
[sessionManager PUT:request.URL.absoluteString parameters:parameters success:success failure:failure];
10 years ago
} else {
if (request.shouldHaveAuthorizationHeaders) {
[sessionManager.requestSerializer
10 years ago
setAuthorizationHeaderFieldWithUsername:[TSAccountManager localNumber]
password:[TSAccountManager serverAuthToken]];
10 years ago
}
if ([request.HTTPMethod isEqualToString:@"GET"]) {
[sessionManager GET:request.URL.absoluteString
parameters:request.parameters
progress:nil
success:success
failure:failure];
10 years ago
} else if ([request.HTTPMethod isEqualToString:@"POST"]) {
[sessionManager POST:request.URL.absoluteString
parameters:request.parameters
progress:nil
success:success
failure:failure];
10 years ago
} else if ([request.HTTPMethod isEqualToString:@"PUT"]) {
[sessionManager PUT:request.URL.absoluteString
parameters:request.parameters
success:success
failure:failure];
10 years ago
} else if ([request.HTTPMethod isEqualToString:@"DELETE"]) {
[sessionManager DELETE:request.URL.absoluteString
parameters:request.parameters
success:success
failure:failure];
10 years ago
} else {
DDLogError(@"Trying to perform HTTP operation with unknown verb: %@", request.HTTPMethod);
}
}
}
+ (failureBlock)errorPrettifyingForFailureBlock:(failureBlock)failureBlock request:(TSRequest *)request
{
OWSAssert(failureBlock);
OWSAssert(request);
10 years ago
return ^(NSURLSessionDataTask *_Nullable task, NSError *_Nonnull networkError) {
NSInteger statusCode = [task statusCode];
NSError *error = [self errorWithHTTPCode:statusCode
description:nil
failureReason:nil
recoverySuggestion:nil
fallbackError:networkError];
switch (statusCode) {
case 0: {
DDLogWarn(@"The network request failed because of a connectivity error: %@", request);
10 years ago
failureBlock(task,
[self errorWithHTTPCode:statusCode
description:NSLocalizedString(@"ERROR_DESCRIPTION_NO_INTERNET",
@"Generic error used whenever Signal can't contact the server")
failureReason:networkError.localizedFailureReason
recoverySuggestion:NSLocalizedString(@"NETWORK_ERROR_RECOVERY", nil)
fallbackError:networkError]);
10 years ago
break;
}
case 400: {
DDLogError(@"The request contains an invalid parameter : %@, %@", networkError.debugDescription, request);
10 years ago
failureBlock(task, error);
break;
}
case 401: {
DDLogError(@"The server returned an error about the authorization header: %@, %@",
networkError.debugDescription,
request);
10 years ago
failureBlock(task, error);
break;
}
case 403: {
DDLogError(
@"The server returned an authentication failure: %@, %@", networkError.debugDescription, request);
10 years ago
failureBlock(task, error);
break;
}
case 404: {
DDLogError(@"The requested resource could not be found: %@, %@", networkError.debugDescription, request);
10 years ago
failureBlock(task, error);
break;
}
case 411: {
DDLogInfo(@"Multi-device pairing: %zd, %@, %@", statusCode, networkError.debugDescription, request);
10 years ago
failureBlock(task,
[self errorWithHTTPCode:statusCode
description:NSLocalizedString(@"MULTIDEVICE_PAIRING_MAX_DESC", nil)
failureReason:networkError.localizedFailureReason
recoverySuggestion:NSLocalizedString(@"MULTIDEVICE_PAIRING_MAX_RECOVERY", nil)
fallbackError:networkError]);
break;
}
case 413: {
DDLogWarn(@"Rate limit exceeded: %@", request);
10 years ago
failureBlock(task,
[self errorWithHTTPCode:statusCode
description:NSLocalizedString(@"REGISTRATION_ERROR", nil)
failureReason:networkError.localizedFailureReason
recoverySuggestion:NSLocalizedString(@"REGISTER_RATE_LIMITING_BODY", nil)
fallbackError:networkError]);
break;
}
case 417: {
DDLogWarn(@"The number is already registered on a relay. Please unregister there first: %@", request);
10 years ago
failureBlock(task,
[self errorWithHTTPCode:statusCode
description:NSLocalizedString(@"REGISTRATION_ERROR", nil)
failureReason:networkError.localizedFailureReason
recoverySuggestion:NSLocalizedString(@"RELAY_REGISTERED_ERROR_RECOVERY", nil)
fallbackError:networkError]);
break;
}
case 422: {
DDLogError(@"The registration was requested over an unknown transport: %@, %@",
networkError.debugDescription,
request);
10 years ago
failureBlock(task, error);
break;
}
default: {
DDLogWarn(@"Unknown error: %zd, %@, %@", statusCode, networkError.debugDescription, request);
10 years ago
failureBlock(task, error);
break;
}
}
};
}
+ (NSError *)errorWithHTTPCode:(NSInteger)code
description:(NSString *)description
failureReason:(NSString *)failureReason
recoverySuggestion:(NSString *)recoverySuggestion
fallbackError:(NSError *_Nonnull)fallbackError {
if (!description) {
description = fallbackError.localizedDescription;
}
if (!failureReason) {
failureReason = fallbackError.localizedFailureReason;
}
if (!recoverySuggestion) {
recoverySuggestion = fallbackError.localizedRecoverySuggestion;
}
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
if (description) {
[dict setObject:description forKey:NSLocalizedDescriptionKey];
}
if (failureReason) {
[dict setObject:failureReason forKey:NSLocalizedFailureReasonErrorKey];
}
if (recoverySuggestion) {
[dict setObject:recoverySuggestion forKey:NSLocalizedRecoverySuggestionErrorKey];
}
NSData *failureData = fallbackError.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
if (failureData) {
[dict setObject:failureData forKey:AFNetworkingOperationFailingURLResponseDataErrorKey];
}
return [NSError errorWithDomain:TSNetworkManagerDomain code:code userInfo:dict];
}
@end