Use websocket for sends.
This commit is contained in:
parent
5f1682deab
commit
8a76e778b5
|
@ -976,240 +976,192 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|||
messages:deviceMessages
|
||||
relay:recipient.relay
|
||||
timeStamp:message.timestamp];
|
||||
if (YES) {
|
||||
if (TSSocketManager.sharedManager.canMakeRequests) {
|
||||
[TSSocketManager.sharedManager makeRequest:request
|
||||
success:^{
|
||||
DDLogInfo(@"%@ Message send succeeded.", self.logTag);
|
||||
if (isLocalNumber && deviceMessages.count == 0) {
|
||||
DDLogInfo(
|
||||
@"%@ Sent a message with no device messages; clearing 'mayHaveLinkedDevices'.", self.logTag);
|
||||
// In order to avoid skipping necessary sync messages, the default value
|
||||
// for mayHaveLinkedDevices is YES. Once we've successfully sent a
|
||||
// sync message with no device messages (e.g. the service has confirmed
|
||||
// that we have no linked devices), we can set mayHaveLinkedDevices to NO
|
||||
// to avoid unnecessary message sends for sync messages until we learn
|
||||
// of a linked device (e.g. through the device linking UI or by receiving
|
||||
// a sync message, etc.).
|
||||
[OWSDeviceManager.sharedManager clearMayHaveLinkedDevicesIfNotSet];
|
||||
}
|
||||
|
||||
dispatch_async([OWSDispatch sendingQueue], ^{
|
||||
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[recipient saveWithTransaction:transaction];
|
||||
[message updateWithSentRecipient:recipient.uniqueId transaction:transaction];
|
||||
}];
|
||||
|
||||
[self handleMessageSentLocally:message];
|
||||
successHandler();
|
||||
});
|
||||
success:^(id _Nullable responseObject) {
|
||||
[self messageSendDidSucceed:message
|
||||
recipient:recipient
|
||||
isLocalNumber:isLocalNumber
|
||||
deviceMessages:deviceMessages
|
||||
success:successHandler];
|
||||
}
|
||||
failure:^(NSInteger statusCode, NSError *error) {
|
||||
DDLogError(@"%@ Message send failed.", self.logTag);
|
||||
DDLogInfo(@"%@ sending to recipient: %@, failed with error.", self.logTag, recipient.uniqueId);
|
||||
[DDLog flushLog];
|
||||
|
||||
// NSHTTPURLResponse *response = (NSHTTPURLResponse
|
||||
// *)task.response; long statuscode = response.statusCode;
|
||||
NSData *responseData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
|
||||
|
||||
void (^retrySend)(void) = ^void() {
|
||||
if (remainingAttempts <= 0) {
|
||||
// Since we've already repeatedly failed to send to the messaging API,
|
||||
// it's unlikely that repeating the whole process will succeed.
|
||||
[error setIsRetryable:NO];
|
||||
return failureHandler(error);
|
||||
}
|
||||
|
||||
dispatch_async([OWSDispatch sendingQueue], ^{
|
||||
DDLogDebug(@"%@ Retrying: %@", self.logTag, message.debugDescription);
|
||||
[self sendMessageToService:message
|
||||
recipient:recipient
|
||||
thread:thread
|
||||
attempts:remainingAttempts
|
||||
success:successHandler
|
||||
failure:failureHandler];
|
||||
});
|
||||
};
|
||||
|
||||
switch (statusCode) {
|
||||
case 401: {
|
||||
DDLogWarn(
|
||||
@"%@ Unable to send due to invalid credentials. Did the user's client get de-authed by "
|
||||
@"registering elsewhere?",
|
||||
self.logTag);
|
||||
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeSignalServiceFailure,
|
||||
NSLocalizedString(@"ERROR_DESCRIPTION_SENDING_UNAUTHORIZED",
|
||||
@"Error message when attempting to send message"));
|
||||
// No need to retry if we've been de-authed.
|
||||
[error setIsRetryable:NO];
|
||||
return failureHandler(error);
|
||||
}
|
||||
case 404: {
|
||||
DDLogWarn(@"%@ Unregistered recipient: %@", self.logTag, recipient.uniqueId);
|
||||
|
||||
[self unregisteredRecipient:recipient message:message thread:thread];
|
||||
NSError *error = OWSErrorMakeNoSuchSignalRecipientError();
|
||||
// No need to retry if the recipient is not registered.
|
||||
[error setIsRetryable:NO];
|
||||
// If one member of a group deletes their account,
|
||||
// the group should ignore errors when trying to send
|
||||
// messages to this ex-member.
|
||||
[error setShouldBeIgnoredForGroups:YES];
|
||||
return failureHandler(error);
|
||||
}
|
||||
case 409: {
|
||||
// Mismatched devices
|
||||
DDLogWarn(@"%@ Mismatch Devices for recipient: %@", self.logTag, recipient.uniqueId);
|
||||
|
||||
NSError *error;
|
||||
NSDictionary *serializedResponse =
|
||||
[NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
|
||||
if (error) {
|
||||
OWSProdError([OWSAnalyticsEvents messageSenderErrorCouldNotParseMismatchedDevicesJson]);
|
||||
[error setIsRetryable:YES];
|
||||
return failureHandler(error);
|
||||
}
|
||||
|
||||
[self handleMismatchedDevices:serializedResponse recipient:recipient completion:retrySend];
|
||||
break;
|
||||
}
|
||||
case 410: {
|
||||
// Stale devices
|
||||
DDLogWarn(@"%@ Stale devices for recipient: %@", self.logTag, recipient.uniqueId);
|
||||
|
||||
if (!responseData) {
|
||||
DDLogWarn(@"Stale devices but server didn't specify devices in response.");
|
||||
NSError *error = OWSErrorMakeUnableToProcessServerResponseError();
|
||||
[error setIsRetryable:YES];
|
||||
return failureHandler(error);
|
||||
}
|
||||
|
||||
[self handleStaleDevicesWithResponse:responseData
|
||||
recipientId:recipient.uniqueId
|
||||
completion:retrySend];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
retrySend();
|
||||
break;
|
||||
}
|
||||
[self messageSendDidFail:message
|
||||
recipient:recipient
|
||||
thread:thread
|
||||
isLocalNumber:isLocalNumber
|
||||
deviceMessages:deviceMessages
|
||||
remainingAttempts:remainingAttempts
|
||||
statusCode:statusCode
|
||||
error:error
|
||||
success:successHandler
|
||||
failure:failureHandler];
|
||||
}];
|
||||
} else
|
||||
} else {
|
||||
[self.networkManager makeRequest:request
|
||||
success:^(NSURLSessionDataTask *task, id responseObject) {
|
||||
if (isLocalNumber && deviceMessages.count == 0) {
|
||||
DDLogInfo(
|
||||
@"%@ Sent a message with no device messages; clearing 'mayHaveLinkedDevices'.", self.logTag);
|
||||
// In order to avoid skipping necessary sync messages, the default value
|
||||
// for mayHaveLinkedDevices is YES. Once we've successfully sent a
|
||||
// sync message with no device messages (e.g. the service has confirmed
|
||||
// that we have no linked devices), we can set mayHaveLinkedDevices to NO
|
||||
// to avoid unnecessary message sends for sync messages until we learn
|
||||
// of a linked device (e.g. through the device linking UI or by receiving
|
||||
// a sync message, etc.).
|
||||
[OWSDeviceManager.sharedManager clearMayHaveLinkedDevicesIfNotSet];
|
||||
}
|
||||
|
||||
dispatch_async([OWSDispatch sendingQueue], ^{
|
||||
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[recipient saveWithTransaction:transaction];
|
||||
[message updateWithSentRecipient:recipient.uniqueId transaction:transaction];
|
||||
}];
|
||||
|
||||
[self handleMessageSentLocally:message];
|
||||
successHandler();
|
||||
});
|
||||
[self messageSendDidSucceed:message
|
||||
recipient:recipient
|
||||
isLocalNumber:isLocalNumber
|
||||
deviceMessages:deviceMessages
|
||||
success:successHandler];
|
||||
}
|
||||
failure:^(NSURLSessionDataTask *task, NSError *error) {
|
||||
DDLogInfo(@"%@ sending to recipient: %@, failed with error.", self.logTag, recipient.uniqueId);
|
||||
[DDLog flushLog];
|
||||
|
||||
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
|
||||
long statuscode = response.statusCode;
|
||||
NSData *responseData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
|
||||
NSInteger statusCode = response.statusCode;
|
||||
|
||||
void (^retrySend)(void) = ^void() {
|
||||
if (remainingAttempts <= 0) {
|
||||
// Since we've already repeatedly failed to send to the messaging API,
|
||||
// it's unlikely that repeating the whole process will succeed.
|
||||
[error setIsRetryable:NO];
|
||||
return failureHandler(error);
|
||||
}
|
||||
|
||||
dispatch_async([OWSDispatch sendingQueue], ^{
|
||||
DDLogDebug(@"%@ Retrying: %@", self.logTag, message.debugDescription);
|
||||
[self sendMessageToService:message
|
||||
recipient:recipient
|
||||
thread:thread
|
||||
attempts:remainingAttempts
|
||||
success:successHandler
|
||||
failure:failureHandler];
|
||||
});
|
||||
};
|
||||
|
||||
switch (statuscode) {
|
||||
case 401: {
|
||||
DDLogWarn(
|
||||
@"%@ Unable to send due to invalid credentials. Did the user's client get de-authed by "
|
||||
@"registering elsewhere?",
|
||||
self.logTag);
|
||||
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeSignalServiceFailure,
|
||||
NSLocalizedString(@"ERROR_DESCRIPTION_SENDING_UNAUTHORIZED",
|
||||
@"Error message when attempting to send message"));
|
||||
// No need to retry if we've been de-authed.
|
||||
[error setIsRetryable:NO];
|
||||
return failureHandler(error);
|
||||
}
|
||||
case 404: {
|
||||
DDLogWarn(@"%@ Unregistered recipient: %@", self.logTag, recipient.uniqueId);
|
||||
|
||||
[self unregisteredRecipient:recipient message:message thread:thread];
|
||||
NSError *error = OWSErrorMakeNoSuchSignalRecipientError();
|
||||
// No need to retry if the recipient is not registered.
|
||||
[error setIsRetryable:NO];
|
||||
// If one member of a group deletes their account,
|
||||
// the group should ignore errors when trying to send
|
||||
// messages to this ex-member.
|
||||
[error setShouldBeIgnoredForGroups:YES];
|
||||
return failureHandler(error);
|
||||
}
|
||||
case 409: {
|
||||
// Mismatched devices
|
||||
DDLogWarn(@"%@ Mismatch Devices for recipient: %@", self.logTag, recipient.uniqueId);
|
||||
|
||||
NSError *error;
|
||||
NSDictionary *serializedResponse =
|
||||
[NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
|
||||
if (error) {
|
||||
OWSProdError([OWSAnalyticsEvents messageSenderErrorCouldNotParseMismatchedDevicesJson]);
|
||||
[error setIsRetryable:YES];
|
||||
return failureHandler(error);
|
||||
}
|
||||
|
||||
[self handleMismatchedDevices:serializedResponse recipient:recipient completion:retrySend];
|
||||
break;
|
||||
}
|
||||
case 410: {
|
||||
// Stale devices
|
||||
DDLogWarn(@"%@ Stale devices for recipient: %@", self.logTag, recipient.uniqueId);
|
||||
|
||||
if (!responseData) {
|
||||
DDLogWarn(@"Stale devices but server didn't specify devices in response.");
|
||||
NSError *error = OWSErrorMakeUnableToProcessServerResponseError();
|
||||
[error setIsRetryable:YES];
|
||||
return failureHandler(error);
|
||||
}
|
||||
|
||||
[self handleStaleDevicesWithResponse:responseData
|
||||
recipientId:recipient.uniqueId
|
||||
completion:retrySend];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
retrySend();
|
||||
break;
|
||||
}
|
||||
[self messageSendDidFail:message
|
||||
recipient:recipient
|
||||
thread:thread
|
||||
isLocalNumber:isLocalNumber
|
||||
deviceMessages:deviceMessages
|
||||
remainingAttempts:remainingAttempts
|
||||
statusCode:statusCode
|
||||
error:error
|
||||
success:successHandler
|
||||
failure:failureHandler];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)messageSendDidSucceed:(TSOutgoingMessage *)message
|
||||
recipient:(SignalRecipient *)recipient
|
||||
isLocalNumber:(BOOL)isLocalNumber
|
||||
deviceMessages:(NSArray<NSDictionary *> *)deviceMessages
|
||||
success:(void (^)(void))successHandler
|
||||
{
|
||||
OWSAssert(message);
|
||||
OWSAssert(recipient);
|
||||
OWSAssert(deviceMessages);
|
||||
OWSAssert(successHandler);
|
||||
|
||||
DDLogInfo(@"%@ Message send succeeded.", self.logTag);
|
||||
|
||||
if (isLocalNumber && deviceMessages.count == 0) {
|
||||
DDLogInfo(@"%@ Sent a message with no device messages; clearing 'mayHaveLinkedDevices'.", self.logTag);
|
||||
// In order to avoid skipping necessary sync messages, the default value
|
||||
// for mayHaveLinkedDevices is YES. Once we've successfully sent a
|
||||
// sync message with no device messages (e.g. the service has confirmed
|
||||
// that we have no linked devices), we can set mayHaveLinkedDevices to NO
|
||||
// to avoid unnecessary message sends for sync messages until we learn
|
||||
// of a linked device (e.g. through the device linking UI or by receiving
|
||||
// a sync message, etc.).
|
||||
[OWSDeviceManager.sharedManager clearMayHaveLinkedDevicesIfNotSet];
|
||||
}
|
||||
|
||||
dispatch_async([OWSDispatch sendingQueue], ^{
|
||||
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[recipient saveWithTransaction:transaction];
|
||||
[message updateWithSentRecipient:recipient.uniqueId transaction:transaction];
|
||||
}];
|
||||
|
||||
[self handleMessageSentLocally:message];
|
||||
successHandler();
|
||||
});
|
||||
}
|
||||
|
||||
- (void)messageSendDidFail:(TSOutgoingMessage *)message
|
||||
recipient:(SignalRecipient *)recipient
|
||||
thread:(TSThread *)thread
|
||||
isLocalNumber:(BOOL)isLocalNumber
|
||||
deviceMessages:(NSArray<NSDictionary *> *)deviceMessages
|
||||
remainingAttempts:(int)remainingAttempts
|
||||
statusCode:(NSInteger)statusCode
|
||||
error:(NSError *)error
|
||||
success:(void (^)(void))successHandler
|
||||
failure:(RetryableFailureHandler)failureHandler
|
||||
{
|
||||
OWSAssert(message);
|
||||
OWSAssert(recipient);
|
||||
OWSAssert(thread);
|
||||
OWSAssert(deviceMessages);
|
||||
OWSAssert(error);
|
||||
OWSAssert(successHandler);
|
||||
OWSAssert(failureHandler);
|
||||
|
||||
DDLogInfo(@"%@ sending to recipient: %@, failed with error.", self.logTag, recipient.uniqueId);
|
||||
[DDLog flushLog];
|
||||
|
||||
NSData *responseData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
|
||||
|
||||
void (^retrySend)(void) = ^void() {
|
||||
if (remainingAttempts <= 0) {
|
||||
// Since we've already repeatedly failed to send to the messaging API,
|
||||
// it's unlikely that repeating the whole process will succeed.
|
||||
[error setIsRetryable:NO];
|
||||
return failureHandler(error);
|
||||
}
|
||||
|
||||
dispatch_async([OWSDispatch sendingQueue], ^{
|
||||
DDLogDebug(@"%@ Retrying: %@", self.logTag, message.debugDescription);
|
||||
[self sendMessageToService:message
|
||||
recipient:recipient
|
||||
thread:thread
|
||||
attempts:remainingAttempts
|
||||
success:successHandler
|
||||
failure:failureHandler];
|
||||
});
|
||||
};
|
||||
|
||||
switch (statusCode) {
|
||||
case 401: {
|
||||
DDLogWarn(@"%@ Unable to send due to invalid credentials. Did the user's client get de-authed by "
|
||||
@"registering elsewhere?",
|
||||
self.logTag);
|
||||
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeSignalServiceFailure,
|
||||
NSLocalizedString(
|
||||
@"ERROR_DESCRIPTION_SENDING_UNAUTHORIZED", @"Error message when attempting to send message"));
|
||||
// No need to retry if we've been de-authed.
|
||||
[error setIsRetryable:NO];
|
||||
return failureHandler(error);
|
||||
}
|
||||
case 404: {
|
||||
DDLogWarn(@"%@ Unregistered recipient: %@", self.logTag, recipient.uniqueId);
|
||||
|
||||
[self unregisteredRecipient:recipient message:message thread:thread];
|
||||
NSError *error = OWSErrorMakeNoSuchSignalRecipientError();
|
||||
// No need to retry if the recipient is not registered.
|
||||
[error setIsRetryable:NO];
|
||||
// If one member of a group deletes their account,
|
||||
// the group should ignore errors when trying to send
|
||||
// messages to this ex-member.
|
||||
[error setShouldBeIgnoredForGroups:YES];
|
||||
return failureHandler(error);
|
||||
}
|
||||
case 409: {
|
||||
// Mismatched devices
|
||||
DDLogWarn(@"%@ Mismatch Devices for recipient: %@", self.logTag, recipient.uniqueId);
|
||||
|
||||
NSError *error;
|
||||
NSDictionary *serializedResponse =
|
||||
[NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
|
||||
if (error) {
|
||||
OWSProdError([OWSAnalyticsEvents messageSenderErrorCouldNotParseMismatchedDevicesJson]);
|
||||
[error setIsRetryable:YES];
|
||||
return failureHandler(error);
|
||||
}
|
||||
|
||||
[self handleMismatchedDevices:serializedResponse recipient:recipient completion:retrySend];
|
||||
break;
|
||||
}
|
||||
case 410: {
|
||||
// Stale devices
|
||||
DDLogWarn(@"%@ Stale devices for recipient: %@", self.logTag, recipient.uniqueId);
|
||||
|
||||
if (!responseData) {
|
||||
DDLogWarn(@"Stale devices but server didn't specify devices in response.");
|
||||
NSError *error = OWSErrorMakeUnableToProcessServerResponseError();
|
||||
[error setIsRetryable:YES];
|
||||
return failureHandler(error);
|
||||
}
|
||||
|
||||
[self handleStaleDevicesWithResponse:responseData recipientId:recipient.uniqueId completion:retrySend];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
retrySend();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleMismatchedDevices:(NSDictionary *)dictionary
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
#import <SocketRocket/SRWebSocket.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static void *SocketManagerStateObservationContext = &SocketManagerStateObservationContext;
|
||||
|
||||
extern NSString *const kNSNotification_SocketManagerStateDidChange;
|
||||
|
@ -14,7 +16,8 @@ typedef NS_ENUM(NSUInteger, SocketManagerState) {
|
|||
SocketManagerStateOpen,
|
||||
};
|
||||
|
||||
typedef void (^TSSocketMessageSuccess)(void);
|
||||
typedef void (^TSSocketMessageSuccess)(id _Nullable responseObject);
|
||||
// statusCode is zero by default, if request never made or failed.
|
||||
typedef void (^TSSocketMessageFailure)(NSInteger statusCode, NSError *error);
|
||||
|
||||
@class TSRequest;
|
||||
|
@ -22,6 +25,7 @@ typedef void (^TSSocketMessageFailure)(NSInteger statusCode, NSError *error);
|
|||
@interface TSSocketManager : NSObject <SRWebSocketDelegate>
|
||||
|
||||
@property (nonatomic, readonly) SocketManagerState state;
|
||||
@property (atomic, readonly) BOOL canMakeRequests;
|
||||
|
||||
+ (instancetype)sharedManager;
|
||||
|
||||
|
@ -45,3 +49,5 @@ typedef void (^TSSocketMessageFailure)(NSInteger statusCode, NSError *error);
|
|||
failure:(TSSocketMessageFailure)failure;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
#import "Threading.h"
|
||||
#import "WebSocketResources.pb.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static const CGFloat kSocketHeartbeatPeriodSeconds = 30.f;
|
||||
static const CGFloat kSocketReconnectDelaySeconds = 5.f;
|
||||
|
||||
|
@ -51,7 +53,7 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
|
|||
|
||||
@implementation TSSocketMessage
|
||||
|
||||
- (void)didSucceed
|
||||
- (void)didSucceedWithResponseObject:(id _Nullable)responseObject
|
||||
{
|
||||
@synchronized(self)
|
||||
{
|
||||
|
@ -64,7 +66,7 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
|
|||
OWSAssert(self.success);
|
||||
OWSAssert(self.failure);
|
||||
|
||||
self.success();
|
||||
self.success(responseObject);
|
||||
|
||||
self.success = nil;
|
||||
self.failure = nil;
|
||||
|
@ -156,10 +158,11 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
|
|||
|
||||
@property (nonatomic) BOOL hasObservedNotifications;
|
||||
|
||||
// These two properties should only be accessed while synchronized on the socket manager.
|
||||
@property (nonatomic) UInt64 requestIdCounter;
|
||||
// This property should only be accessed while synchronized on the socket manager.
|
||||
@property (nonatomic, readonly) NSMutableDictionary<NSNumber *, TSSocketMessage *> *socketMessageMap;
|
||||
|
||||
@property (atomic) BOOL canMakeRequests;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
@ -179,8 +182,6 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
|
|||
_signalService = [OWSSignalService sharedInstance];
|
||||
_messageReceiver = [OWSMessageReceiver sharedInstance];
|
||||
_state = SocketManagerStateClosed;
|
||||
// Ensure that all request ids are non-zero.
|
||||
_requestIdCounter = 1;
|
||||
_socketMessageMap = [NSMutableDictionary new];
|
||||
|
||||
OWSSingletonAssert();
|
||||
|
@ -366,12 +367,14 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
|
|||
// the socket failed immediately for some reason), so we update the state
|
||||
// _before_ calling it, not after.
|
||||
_state = state;
|
||||
_canMakeRequests = state == SocketManagerStateOpen;
|
||||
[socket open];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_state = state;
|
||||
_canMakeRequests = state == SocketManagerStateOpen;
|
||||
|
||||
[self notifyStatusChange];
|
||||
}
|
||||
|
@ -410,18 +413,6 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
|
|||
|
||||
- (void)makeRequest:(TSRequest *)request success:(TSSocketMessageSuccess)success failure:(TSSocketMessageFailure)failure
|
||||
{
|
||||
DispatchMainThreadSafe(^{
|
||||
[self makeRequestOnMain:request success:success failure:failure];
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Currently we schedule requests on main since we want to consult the state property.
|
||||
// There are alternatives.
|
||||
- (void)makeRequestOnMain:(TSRequest *)request
|
||||
success:(TSSocketMessageSuccess)success
|
||||
failure:(TSSocketMessageFailure)failure
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(request);
|
||||
OWSAssert(request.HTTPMethod.length > 0);
|
||||
OWSAssert(success);
|
||||
|
@ -432,7 +423,8 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
|
|||
socketMessage.failure = failure;
|
||||
@synchronized(self)
|
||||
{
|
||||
socketMessage.requestId = self.requestIdCounter++;
|
||||
// TODO: Should we use another random number generator?
|
||||
socketMessage.requestId = arc4random();
|
||||
self.socketMessageMap[@(socketMessage.requestId)] = socketMessage;
|
||||
}
|
||||
|
||||
|
@ -444,9 +436,7 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
|
|||
NSError *error;
|
||||
jsonData = [NSJSONSerialization
|
||||
dataWithJSONObject:request.parameters
|
||||
// TODO:
|
||||
options:(NSJSONWritingOptions)0
|
||||
// options:NSJSONWritingPrettyPrinted
|
||||
error:&error];
|
||||
if (!jsonData || error) {
|
||||
OWSProdLogAndFail(@"%@ could not serialize request JSON: %@", self.logTag, error);
|
||||
|
@ -456,22 +446,18 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
|
|||
}
|
||||
}
|
||||
|
||||
if (self.state != SocketManagerStateOpen) {
|
||||
DDLogError(@"%@ makeRequest: socket not open.", self.logTag);
|
||||
// TODO:
|
||||
[socketMessage didFailBeforeSending];
|
||||
return;
|
||||
}
|
||||
|
||||
WebSocketResourcesWebSocketRequestMessageBuilder *requestBuilder =
|
||||
[WebSocketResourcesWebSocketRequestMessageBuilder new];
|
||||
requestBuilder.id = socketMessage.requestId;
|
||||
[requestBuilder setVerb:request.HTTPMethod];
|
||||
[requestBuilder setPath:requestPath];
|
||||
[requestBuilder setBody:jsonData];
|
||||
[requestBuilder setHeadersArray:@[
|
||||
@"content-type:application/json",
|
||||
]];
|
||||
if (jsonData) {
|
||||
// TODO: Do we need body & headers for requests with no parameters?
|
||||
[requestBuilder setBody:jsonData];
|
||||
[requestBuilder setHeadersArray:@[
|
||||
@"content-type:application/json",
|
||||
]];
|
||||
}
|
||||
|
||||
WebSocketResourcesWebSocketMessageBuilder *messageBuilder = [WebSocketResourcesWebSocketMessageBuilder new];
|
||||
[messageBuilder setType:WebSocketResourcesWebSocketMessageTypeRequest];
|
||||
|
@ -480,18 +466,20 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
|
|||
NSData *messageData = [messageBuilder build].data;
|
||||
if (!messageData) {
|
||||
OWSProdLogAndFail(@"%@ could not serialize message.", self.logTag);
|
||||
// TODO:
|
||||
[socketMessage didFailBeforeSending];
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self.canMakeRequests) {
|
||||
DDLogError(@"%@ makeRequest: socket not open.", self.logTag);
|
||||
[socketMessage didFailBeforeSending];
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
BOOL wasScheduled =
|
||||
// TODO: Consider using sendDataNoCopy.
|
||||
[self.websocket sendData:messageData error:&error];
|
||||
BOOL wasScheduled = [self.websocket sendDataNoCopy:messageData error:&error];
|
||||
if (!wasScheduled || error) {
|
||||
OWSProdLogAndFail(@"%@ could not serialize request JSON: %@", self.logTag, error);
|
||||
// TODO:
|
||||
[socketMessage didFailBeforeSending];
|
||||
return;
|
||||
}
|
||||
|
@ -501,9 +489,99 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
|
|||
request.HTTPMethod,
|
||||
requestPath,
|
||||
jsonData.length);
|
||||
// [socketMessage didSucceed];
|
||||
|
||||
// Ensure requests "timeout" after 30 seconds.
|
||||
__weak TSSocketMessage *weakSocketMessage = socketMessage;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(30 * NSEC_PER_SEC)),
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
|
||||
^{
|
||||
[weakSocketMessage didFailBeforeSending];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)processWebSocketResponseMessage:(WebSocketResourcesWebSocketResponseMessage *)message
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(message);
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[self processWebSocketResponseMessageAsync:message];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)processWebSocketResponseMessageAsync:(WebSocketResourcesWebSocketResponseMessage *)message
|
||||
{
|
||||
OWSAssert(message);
|
||||
|
||||
DDLogInfo(@"%@ received WebSocket response.", self.logTag);
|
||||
|
||||
if (![message hasId]) {
|
||||
DDLogError(@"%@ received incomplete WebSocket response.", self.logTag);
|
||||
return;
|
||||
}
|
||||
|
||||
UInt64 requestId = message.id;
|
||||
UInt32 responseStatus = 0;
|
||||
if (message.hasStatus) {
|
||||
responseStatus = message.status;
|
||||
}
|
||||
NSString *_Nullable responseMessage;
|
||||
if (message.hasMessage) {
|
||||
responseMessage = message.message;
|
||||
}
|
||||
NSData *_Nullable responseBody;
|
||||
if (message.hasBody) {
|
||||
responseBody = message.body;
|
||||
}
|
||||
NSArray<NSString *> *_Nullable responseHeaders = message.headers;
|
||||
|
||||
id responseObject = responseBody;
|
||||
if (responseBody) {
|
||||
NSError *error;
|
||||
id _Nullable responseJson =
|
||||
[NSJSONSerialization JSONObjectWithData:responseBody options:(NSJSONReadingOptions)0 error:&error];
|
||||
if (!responseJson || error) {
|
||||
DDLogError(@"%@ could not parse WebSocket response JSON: %@.", self.logTag, error);
|
||||
// TODO: Should we require JSON parsing to succeed?
|
||||
} else {
|
||||
responseObject = responseJson;
|
||||
}
|
||||
}
|
||||
|
||||
TSSocketMessage *_Nullable socketMessage;
|
||||
@synchronized(self)
|
||||
{
|
||||
socketMessage = self.socketMessageMap[@(requestId)];
|
||||
[self.socketMessageMap removeObjectForKey:@(requestId)];
|
||||
}
|
||||
|
||||
if (!socketMessage) {
|
||||
DDLogError(@"%@ received response to unknown request.", self.logTag);
|
||||
} else {
|
||||
BOOL didSucceed = 200 <= responseStatus && responseStatus <= 299;
|
||||
if (didSucceed) {
|
||||
[socketMessage didSucceedWithResponseObject:responseObject];
|
||||
} else {
|
||||
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeMessageResponseFailed,
|
||||
NSLocalizedString(
|
||||
@"ERROR_DESCRIPTION_RESPONSE_FAILED", @"Error indicating that a socket response failed."));
|
||||
[socketMessage didFailWithStatusCode:(NSInteger)responseStatus error:error];
|
||||
}
|
||||
}
|
||||
|
||||
DDLogVerbose(@"%@ received WebSocket response: %llu, %zd, %@, %zd, %@, %d, %@.",
|
||||
self.logTag,
|
||||
(unsigned long long)requestId,
|
||||
(NSInteger)responseStatus,
|
||||
responseMessage,
|
||||
responseBody.length,
|
||||
responseHeaders,
|
||||
socketMessage != nil,
|
||||
responseObject);
|
||||
}
|
||||
|
||||
// didSucceedWithResponseObject:(id _Nullable)responseObject
|
||||
|
||||
#pragma mark - Delegate methods
|
||||
|
||||
- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
|
||||
|
@ -538,13 +616,18 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
|
|||
return;
|
||||
}
|
||||
|
||||
WebSocketResourcesWebSocketMessage *wsMessage = [WebSocketResourcesWebSocketMessage parseFromData:data];
|
||||
WebSocketResourcesWebSocketMessage *wsMessage;
|
||||
@try {
|
||||
wsMessage = [WebSocketResourcesWebSocketMessage parseFromData:data];
|
||||
} @catch (NSException *exception) {
|
||||
OWSProdLogAndFail(@"%@ Received an invalid message: %@", self.logTag, exception.debugDescription);
|
||||
// TODO: Add analytics.
|
||||
return;
|
||||
}
|
||||
|
||||
if (wsMessage.type == WebSocketResourcesWebSocketMessageTypeRequest) {
|
||||
DDLogVerbose(@"%@ webSocket:didReceiveMessage: request.", self.logTag);
|
||||
[self processWebSocketRequestMessage:wsMessage.request];
|
||||
} else if (wsMessage.type == WebSocketResourcesWebSocketMessageTypeResponse) {
|
||||
DDLogVerbose(@"%@ webSocket:didReceiveMessage: response.", self.logTag);
|
||||
[self processWebSocketResponseMessage:wsMessage.response];
|
||||
} else {
|
||||
DDLogWarn(@"%@ webSocket:didReceiveMessage: unknown.", self.logTag);
|
||||
|
@ -609,64 +692,6 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
|
|||
}
|
||||
}
|
||||
|
||||
- (void)processWebSocketResponseMessage:(WebSocketResourcesWebSocketResponseMessage *)message
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(message);
|
||||
|
||||
DDLogInfo(@"%@ received WebSocket response.", self.logTag);
|
||||
|
||||
if (![message hasId]) {
|
||||
DDLogError(@"%@ received incomplete WebSocket response.", self.logTag);
|
||||
return;
|
||||
}
|
||||
|
||||
UInt64 requestId = message.id;
|
||||
UInt32 responseStatus = 0;
|
||||
if (message.hasStatus) {
|
||||
responseStatus = message.status;
|
||||
}
|
||||
NSString *_Nullable responseMessage;
|
||||
if (message.hasMessage) {
|
||||
responseMessage = message.message;
|
||||
}
|
||||
NSData *_Nullable responseBody;
|
||||
if (message.hasBody) {
|
||||
responseBody = message.body;
|
||||
}
|
||||
NSArray<NSString *> *_Nullable responseHeaders = message.headers;
|
||||
|
||||
TSSocketMessage *_Nullable socketMessage;
|
||||
@synchronized(self)
|
||||
{
|
||||
socketMessage = self.socketMessageMap[@(requestId)];
|
||||
[self.socketMessageMap removeObjectForKey:@(requestId)];
|
||||
|
||||
if (!socketMessage) {
|
||||
DDLogError(@"%@ received response to unknown request.", self.logTag);
|
||||
} else {
|
||||
BOOL didSucceed = 200 <= responseStatus && responseStatus <= 299;
|
||||
if (didSucceed) {
|
||||
[socketMessage didSucceed];
|
||||
} else {
|
||||
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeMessageResponseFailed,
|
||||
NSLocalizedString(
|
||||
@"ERROR_DESCRIPTION_RESPONSE_FAILED", @"Error indicating that a socket response failed."));
|
||||
[socketMessage didFailWithStatusCode:(NSInteger)responseStatus error:error];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DDLogVerbose(@"%@ received WebSocket response: %llu, %zd, %@, %zd, %@, %d.",
|
||||
self.logTag,
|
||||
(unsigned long long)requestId,
|
||||
(NSInteger)responseStatus,
|
||||
responseMessage,
|
||||
responseBody.length,
|
||||
responseHeaders,
|
||||
socketMessage != nil);
|
||||
}
|
||||
|
||||
- (void)sendWebSocketResourcesWebSocketMessageAcknowledgement:(WebSocketResourcesWebSocketRequestMessage *)request
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
@ -705,9 +730,10 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
|
|||
}
|
||||
|
||||
- (void)webSocket:(SRWebSocket *)webSocket
|
||||
didCloseWithCode:(NSInteger)code
|
||||
reason:(NSString *)reason
|
||||
wasClean:(BOOL)wasClean {
|
||||
didCloseWithCode:(NSInteger)code
|
||||
reason:(nullable NSString *)reason
|
||||
wasClean:(BOOL)wasClean
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(webSocket);
|
||||
if (webSocket != self.websocket) {
|
||||
|
@ -960,3 +986,5 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
|
|||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -59,7 +59,7 @@ NSError *OWSErrorMakeMessageSendDisabledDueToPreKeyUpdateFailuresError()
|
|||
|
||||
NSError *OWSErrorMakeMessageSendFailedToBlockListError()
|
||||
{
|
||||
return OWSErrorWithCodeDescription(OWSErrorCodeMessageRequestFailedToBlockList,
|
||||
return OWSErrorWithCodeDescription(OWSErrorCodeMessageSendFailedToBlockList,
|
||||
NSLocalizedString(@"ERROR_DESCRIPTION_MESSAGE_SEND_FAILED_DUE_TO_BLOCK_LIST",
|
||||
@"Error message indicating that message send failed due to block list"));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue