diff --git a/src/Network/WebSockets/TSSocketManager.h b/src/Network/WebSockets/TSSocketManager.h index d6909a26c..b90fc4c14 100644 --- a/src/Network/WebSockets/TSSocketManager.h +++ b/src/Network/WebSockets/TSSocketManager.h @@ -5,14 +5,22 @@ #import #import "SRWebSocket.h" -static void *kSocketStatusObservationContext = &kSocketStatusObservationContext; +static void *SocketManagerStateObservationContext = &SocketManagerStateObservationContext; -extern NSString *const SocketOpenedNotification; -extern NSString *const SocketClosedNotification; -extern NSString *const SocketConnectingNotification; +extern NSString *const kNSNotification_SocketManagerStateDidChange; + +typedef NS_ENUM(NSUInteger, SocketManagerState) { + SocketManagerStateClosed, + SocketManagerStateConnecting, + SocketManagerStateOpen, +}; @interface TSSocketManager : NSObject +@property (nonatomic, readonly) SocketManagerState state; + ++ (instancetype)sharedManager; + - (instancetype)init NS_UNAVAILABLE; // If the app is in the foreground, we'll try to open the socket unless it's already @@ -26,6 +34,4 @@ extern NSString *const SocketConnectingNotification; // This method can be called from any thread. + (void)requestSocketOpen; -+ (void)sendNotification; - @end diff --git a/src/Network/WebSockets/TSSocketManager.m b/src/Network/WebSockets/TSSocketManager.m index a3d906699..e294d95da 100644 --- a/src/Network/WebSockets/TSSocketManager.m +++ b/src/Network/WebSockets/TSSocketManager.m @@ -14,12 +14,6 @@ #import "TSStorageManager+keyingMaterial.h" #import "Threading.h" -typedef enum : NSUInteger { - kSocketStatusOpen, - kSocketStatusClosed, - kSocketStatusConnecting, -} SocketStatus; - static const CGFloat kSocketHeartbeatPeriodSeconds = 30.f; static const CGFloat kSocketReconnectDelaySeconds = 5.f; @@ -31,9 +25,7 @@ static const CGFloat kBackgroundOpenSocketDurationSeconds = 25.f; // b) It has received a message over the socket in the last 15 seconds. static const CGFloat kBackgroundKeepSocketAliveDurationSeconds = 15.f; -NSString *const SocketOpenedNotification = @"SocketOpenedNotification"; -NSString *const SocketClosedNotification = @"SocketClosedNotification"; -NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; +NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_SocketManagerStateDidChange"; // TSSocketManager's properties should only be accessed from the main thread. @interface TSSocketManager () @@ -50,7 +42,7 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; #pragma mark - -// The second tier is the status property. We initiate changes +// The second tier is the state property. We initiate changes // to the websocket by changing this property's value, and delegate // events from the websocket also update this value as the websocket's // state changes. @@ -58,7 +50,7 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; // Due to concurrency, this property can fall out of sync with the // websocket's actual state, so we're defensive and distrustful of // this property. -@property (nonatomic) SocketStatus status; +@property (nonatomic) SocketManagerState state; #pragma mark - @@ -102,11 +94,9 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; OWSAssert([NSThread isMainThread]); _signalService = [OWSSignalService sharedInstance]; - _status = kSocketStatusClosed; + _state = SocketManagerStateClosed; _fetchingTaskIdentifier = UIBackgroundTaskInvalid; - [self addObserver:self forKeyPath:@"status" options:0 context:kSocketStatusObservationContext]; - OWSSingletonAssert(); return self; @@ -167,11 +157,11 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; switch ([self.websocket readyState]) { case SR_OPEN: DDLogVerbose(@"WebSocket already open on connection request"); - self.status = kSocketStatusOpen; + self.state = SocketManagerStateOpen; return; case SR_CONNECTING: DDLogVerbose(@"WebSocket is already connecting"); - self.status = kSocketStatusConnecting; + self.state = SocketManagerStateConnecting; return; default: break; @@ -186,18 +176,19 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; // The websocket delegate methods are invoked _after_ the websocket // state changes, so we may be just learning about a socket failure // or close event now. - self.status = kSocketStatusClosed; + self.state = SocketManagerStateClosed; // Now open a new socket. - self.status = kSocketStatusConnecting; + self.state = SocketManagerStateConnecting; } -- (NSString *)stringFromSocketStatus:(SocketStatus)status { - switch (status) { - case kSocketStatusClosed: +- (NSString *)stringFromSocketManagerState:(SocketManagerState)state +{ + switch (state) { + case SocketManagerStateClosed: return @"Closed"; - case kSocketStatusOpen: + case SocketManagerStateOpen: return @"Open"; - case kSocketStatusConnecting: + case SocketManagerStateConnecting: return @"Connecting"; } } @@ -216,46 +207,47 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; // it's not safe to assume we'll learn of changes to websocket state // in the websocket delegate methods. // -// Therefore, we use the [setStatus:] setter to ensure alignment between +// Therefore, we use the [setState:] setter to ensure alignment between // websocket state and class state. -- (void)setStatus:(SocketStatus)status { +- (void)setState:(SocketManagerState)state +{ OWSAssert([NSThread isMainThread]); - // If this status update is redundant, verify that + // If this state update is redundant, verify that // class state and socket state are aligned. // // Note: it's not safe to check the socket's readyState here as // it may have been just updated on another thread. If so, // we'll learn of that state change soon. - if (_status == status) { - switch (status) { - case kSocketStatusClosed: + if (_state == state) { + switch (state) { + case SocketManagerStateClosed: OWSAssert(!self.websocket); break; - case kSocketStatusOpen: + case SocketManagerStateOpen: OWSAssert(self.websocket); break; - case kSocketStatusConnecting: + case SocketManagerStateConnecting: OWSAssert(self.websocket); break; } return; } - - DDLogWarn(@"%@ Socket status change: %@ -> %@", - self.tag, - [self stringFromSocketStatus:_status], - [self stringFromSocketStatus:status]); - - // If this status update is _not_ redundant, - // update class state to reflect the new status. - switch (status) { - case kSocketStatusClosed: { + + DDLogWarn(@"%@ Socket state change: %@ -> %@", + self.tag, + [self stringFromSocketManagerState:_state], + [self stringFromSocketManagerState:state]); + + // If this state update is _not_ redundant, + // update class state to reflect the new state. + switch (state) { + case SocketManagerStateClosed: { [self resetSocket]; break; } - case kSocketStatusOpen: { - OWSAssert(self.status == kSocketStatusConnecting); + case SocketManagerStateOpen: { + OWSAssert(self.state == SocketManagerStateConnecting); self.heartbeatTimer = [NSTimer timerWithTimeInterval:kSocketHeartbeatPeriodSeconds target:self @@ -270,7 +262,7 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; [self clearReconnect]; break; } - case kSocketStatusConnecting: { + case SocketManagerStateConnecting: { // Discard the old socket which is already closed or is closing. [self resetSocket]; @@ -286,17 +278,28 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; [self setWebsocket:socket]; // [SRWebSocket open] could hypothetically call a delegate method (e.g. if - // the socket failed immediately for some reason), so we update the status + // the socket failed immediately for some reason), so we update the state // _before_ calling it, not after. - _status = status; + _state = state; [socket open]; return; } } - _status = status; + _state = state; + + [self notifyStatusChange]; } +- (void)notifyStatusChange +{ + [[NSNotificationCenter defaultCenter] postNotificationName:kNSNotification_SocketManagerStateDidChange + object:nil + userInfo:nil]; +} + +#pragma mark - + - (void)resetSocket { OWSAssert([NSThread isMainThread]); @@ -315,7 +318,7 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; DDLogWarn(@"%@ closeWebSocket.", self.tag); } - self.status = kSocketStatusClosed; + self.state = SocketManagerStateClosed; } #pragma mark - Delegate methods @@ -328,7 +331,7 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; return; } - self.status = kSocketStatusOpen; + self.state = SocketManagerStateOpen; } - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error { @@ -480,42 +483,6 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; [TSStorageManager serverAuthToken]]; } -#pragma mark UI Delegates - -- (void)observeValueForKeyPath:(NSString *)keyPath - ofObject:(id)object - change:(NSDictionary *)change - context:(void *)context -{ - if (context == kSocketStatusObservationContext) { - [self notifyStatusChange]; - } else { - [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; - } -} - -- (void)notifyStatusChange -{ - switch (self.status) { - case kSocketStatusOpen: - [[NSNotificationCenter defaultCenter] postNotificationName:SocketOpenedNotification object:self]; - break; - case kSocketStatusClosed: - [[NSNotificationCenter defaultCenter] postNotificationName:SocketClosedNotification object:self]; - break; - case kSocketStatusConnecting: - [[NSNotificationCenter defaultCenter] postNotificationName:SocketConnectingNotification object:self]; - break; - default: - break; - } -} - -+ (void)sendNotification -{ - [[self sharedManager] notifyStatusChange]; -} - #pragma mark - Socket LifeCycle - (BOOL)shouldSocketBeOpen @@ -626,7 +593,7 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; OWSAssert([NSThread isMainThread]); if ([self shouldSocketBeOpen]) { - if (self.status != kSocketStatusOpen) { + if (self.state != SocketManagerStateOpen) { // If we want the socket to be open and it's not open, // start up the reconnect timer immediately (don't wait for an error). // There's little harm in it and this will make us more robust to edge