Merge branch 'feature/fixWebsocket2'

pull/1/head
Matthew Chen 8 years ago
commit 45391cadd3

@ -1,9 +1,5 @@
//
// TSSocketManager.m
// TextSecureiOS
//
// Created by Frederic Jacobs on 17/05/14.
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "SubProtocol.pb.h"
@ -26,28 +22,31 @@ NSString *const SocketOpenedNotification = @"SocketOpenedNotification";
NSString *const SocketClosedNotification = @"SocketClosedNotification";
NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
// TSSocketManager's properties should only be accessed from the main thread.
@interface TSSocketManager ()
@property (nonatomic, readonly, strong) OWSSignalService *signalService;
@property (nonatomic, readonly) OWSSignalService *signalService;
@property (nonatomic, retain) NSTimer *pingTimer;
@property (nonatomic, retain) NSTimer *reconnectTimer;
@property (nonatomic) NSTimer *pingTimer;
@property (nonatomic) NSTimer *reconnectTimer;
@property (nonatomic, retain) SRWebSocket *websocket;
@property (nonatomic) SRWebSocket *websocket;
@property (nonatomic) SocketStatus status;
@property (nonatomic) UIBackgroundTaskIdentifier fetchingTaskIdentifier;
@property BOOL didConnectBg;
@property BOOL didRetreiveMessageBg;
@property BOOL shouldDownloadMessage;
@property (nonatomic) BOOL didConnectBg;
@property (nonatomic) BOOL didRetreiveMessageBg;
@property (nonatomic) BOOL shouldDownloadMessage;
@property (nonatomic, retain) NSTimer *backgroundKeepAliveTimer;
@property (nonatomic, retain) NSTimer *backgroundConnectTimer;
@property (nonatomic) NSTimer *backgroundKeepAliveTimer;
@property (nonatomic) NSTimer *backgroundConnectTimer;
@end
#pragma mark -
@implementation TSSocketManager
- (instancetype)init
@ -57,9 +56,11 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
if (!self) {
return self;
}
OWSAssert([NSThread isMainThread]);
_signalService = [OWSSignalService new];
_websocket = nil;
[self addObserver:self forKeyPath:@"status" options:0 context:kSocketStatusObservationContext];
return self;
@ -87,6 +88,21 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
- (void)becomeActive
{
OWSAssert([NSThread isMainThread]);
if ([NSThread isMainThread]) {
[self ensureWebsocket];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self ensureWebsocket];
});
}
}
- (void)ensureWebsocket
{
OWSAssert([NSThread isMainThread]);
if (self.signalService.isCensored) {
DDLogWarn(@"%@ Refusing to start websocket in `becomeActive`.", self.tag);
return;
@ -98,10 +114,12 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
switch ([socket readyState]) {
case SR_OPEN:
DDLogVerbose(@"WebSocket already open on connection request");
OWSAssert(self.status == kSocketStatusOpen);
self.status = kSocketStatusOpen;
return;
case SR_CONNECTING:
DDLogVerbose(@"WebSocket is already connecting");
OWSAssert(self.status == kSocketStatusConnecting);
self.status = kSocketStatusConnecting;
return;
default:
@ -110,9 +128,10 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
}
// Discard the old socket which is already closed or is closing.
[socket close];
self.status = kSocketStatusClosed;
socket.delegate = nil;
[self closeWebSocket];
OWSAssert(self.status == kSocketStatusClosed);
self.status = kSocketStatusConnecting;
// Create a new web socket.
NSString *webSocketConnect = [textSecureWebSocketAPI stringByAppendingString:[self webSocketAuthenticationString]];
@ -127,13 +146,32 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
}
+ (void)resignActivity {
SRWebSocket *socket = [[self sharedManager] websocket];
[socket close];
OWSAssert([NSThread isMainThread]);
[[self sharedManager] closeWebSocket];
}
- (void)closeWebSocket
{
OWSAssert([NSThread isMainThread]);
if (self.websocket) {
DDLogWarn(@"%@ closeWebSocket.", self.tag);
}
[self.websocket close];
self.websocket.delegate = nil;
self.websocket = nil;
[self.pingTimer invalidate];
self.pingTimer = nil;
self.status = kSocketStatusClosed;
}
#pragma mark - Delegate methods
- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
OWSAssert([NSThread isMainThread]);
self.pingTimer = [NSTimer timerWithTimeInterval:kWebSocketHeartBeat
target:self
selector:@selector(webSocketHeartBeat)
@ -143,6 +181,7 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
// Additionally, we want the ping timer to work in the background too.
[[NSRunLoop mainRunLoop] addTimer:self.pingTimer forMode:NSDefaultRunLoopMode];
OWSAssert(self.status == kSocketStatusConnecting);
self.status = kSocketStatusOpen;
[self.reconnectTimer invalidate];
self.reconnectTimer = nil;
@ -150,10 +189,12 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
OWSAssert([NSThread isMainThread]);
DDLogError(@"Error connecting to socket %@", error);
[self.pingTimer invalidate];
self.status = kSocketStatusClosed;
[self closeWebSocket];
[self scheduleRetry];
}
@ -194,6 +235,8 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
}
- (void)keepAliveBackground {
OWSAssert([NSThread isMainThread]);
[self.backgroundConnectTimer invalidate];
if (self.fetchingTaskIdentifier) {
@ -214,6 +257,8 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
}
- (void)sendWebSocketMessageAcknowledgement:(WebSocketRequestMessage *)request {
OWSAssert([NSThread isMainThread]);
WebSocketResponseMessageBuilder *response = [WebSocketResponseMessage builder];
[response setStatus:200];
[response setMessage:@"OK"];
@ -227,6 +272,7 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
[self.websocket sendDataNoCopy:message.build.data error:&error];
if (error) {
DDLogWarn(@"Error while trying to write on websocket %@", error);
[self scheduleRetry];
}
}
@ -234,20 +280,27 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
didCloseWithCode:(NSInteger)code
reason:(NSString *)reason
wasClean:(BOOL)wasClean {
[self.pingTimer invalidate];
self.status = kSocketStatusClosed;
OWSAssert([NSThread isMainThread]);
[self closeWebSocket];
if (!wasClean && [UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
if (!wasClean && [self shouldKeepWebSocketAlive]) {
[self scheduleRetry];
}
}
- (void)webSocketHeartBeat {
NSError *error;
[self.websocket sendPing:nil error:&error];
if (error) {
DDLogWarn(@"Error in websocket heartbeat: %@", error.localizedDescription);
OWSAssert([NSThread isMainThread]);
if ([self shouldKeepWebSocketAlive]) {
NSError *error;
[self.websocket sendPing:nil error:&error];
if (error) {
DDLogWarn(@"Error in websocket heartbeat: %@", error.localizedDescription);
[self scheduleRetry];
}
} else {
[self closeWebSocket];
}
}
@ -258,8 +311,29 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
[TSStorageManager serverAuthToken]];
}
- (BOOL)shouldKeepWebSocketAlive {
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
// If app is active, keep web socket alive.
return YES;
} else if (self.backgroundKeepAliveTimer ||
self.backgroundConnectTimer ||
self.fetchingTaskIdentifier != UIBackgroundTaskInvalid) {
// If app is doing any work in the background, keep web socket alive.
return YES;
} else {
return NO;
}
}
- (void)scheduleRetry {
if (![self.reconnectTimer isValid]) {
OWSAssert([NSThread isMainThread]);
if (![self shouldKeepWebSocketAlive]) {
// Don't bother retrying if app is inactive and not doing any background activity.
[self.reconnectTimer invalidate];
self.reconnectTimer = nil;
} else if (![self.reconnectTimer isValid]) {
// TODO: It'd be nice to do exponential backoff.
self.reconnectTimer = [NSTimer timerWithTimeInterval:kWebSocketReconnectTry
target:[self class]
selector:@selector(becomeActive)
@ -275,37 +349,53 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
#pragma mark - Background Connect
+ (void)becomeActiveFromForeground {
TSSocketManager *sharedInstance = [self sharedManager];
dispatch_async(dispatch_get_main_queue(), ^{
[[self sharedManager] becomeActiveFromForeground];
});
}
if (sharedInstance.fetchingTaskIdentifier != UIBackgroundTaskInvalid) {
[sharedInstance closeBackgroundTask];
- (void)becomeActiveFromForeground {
OWSAssert([NSThread isMainThread]);
if (self.fetchingTaskIdentifier != UIBackgroundTaskInvalid) {
[self closeBackgroundTask];
}
[self becomeActive];
}
+ (void)becomeActiveFromBackgroundExpectMessage:(BOOL)expected {
TSSocketManager *sharedInstance = [TSSocketManager sharedManager];
if (sharedInstance.fetchingTaskIdentifier == UIBackgroundTaskInvalid) {
sharedInstance.backgroundConnectTimer = [NSTimer timerWithTimeInterval:kBackgroundConnectTimer
target:sharedInstance
selector:@selector(backgroundConnectTimerExpired)
userInfo:nil
repeats:NO];
dispatch_async(dispatch_get_main_queue(), ^{
[[TSSocketManager sharedManager] becomeActiveFromBackgroundExpectMessage:expected];
});
}
- (void)becomeActiveFromBackgroundExpectMessage:(BOOL)expected {
OWSAssert([NSThread isMainThread]);
if (self.fetchingTaskIdentifier == UIBackgroundTaskInvalid) {
[self.backgroundConnectTimer invalidate];
self.backgroundConnectTimer = [NSTimer timerWithTimeInterval:kBackgroundConnectTimer
target:self
selector:@selector(backgroundConnectTimerExpired)
userInfo:nil
repeats:NO];
NSRunLoop *loop = [NSRunLoop mainRunLoop];
[loop addTimer:[TSSocketManager sharedManager].backgroundConnectTimer forMode:NSDefaultRunLoopMode];
[sharedInstance.backgroundKeepAliveTimer invalidate];
sharedInstance.didConnectBg = NO;
sharedInstance.didRetreiveMessageBg = NO;
sharedInstance.shouldDownloadMessage = expected;
sharedInstance.fetchingTaskIdentifier =
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[TSSocketManager resignActivity];
[[TSSocketManager sharedManager] closeBackgroundTask];
}];
[self.backgroundKeepAliveTimer invalidate];
self.backgroundKeepAliveTimer = nil;
self.didConnectBg = NO;
self.didRetreiveMessageBg = NO;
self.shouldDownloadMessage = expected;
self.fetchingTaskIdentifier =
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
OWSAssert([NSThread isMainThread]);
[TSSocketManager resignActivity];
[[TSSocketManager sharedManager] closeBackgroundTask];
}];
[self becomeActive];
} else {
DDLogWarn(@"Got called to become active in the background but there was already a background task running.");
@ -313,17 +403,28 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
}
- (void)backgroundConnectTimerExpired {
[self.backgroundConnectTimer invalidate];
self.backgroundConnectTimer = nil;
[self backgroundTimeExpired];
}
- (void)backgroundTimeExpired {
[[self class] resignActivity];
OWSAssert([NSThread isMainThread]);
if (![self shouldKeepWebSocketAlive]) {
[[self class] resignActivity];
}
[self closeBackgroundTask];
}
- (void)closeBackgroundTask {
OWSAssert([NSThread isMainThread]);
[self.backgroundKeepAliveTimer invalidate];
self.backgroundKeepAliveTimer = nil;
[self.backgroundConnectTimer invalidate];
self.backgroundConnectTimer = nil;
/*
If VOIP Push worked, we should just have to check if message was retreived and if not, alert the user.

Loading…
Cancel
Save