// // TSSocketManager.m // TextSecureiOS // // Created by Frederic Jacobs on 17/05/14. // Copyright (c) 2014 Open Whisper Systems. All rights reserved. // #import "TSConstants.h" #import "TSAccountManager.h" #import "TSMessagesManager.h" #import "TSSocketManager.h" #import "TSStorageManager+keyingMaterial.h" #import #define kWebSocketHeartBeat 15 NSString * const SocketOpenedNotification = @"SocketOpenedNotification"; NSString * const SocketClosedNotification = @"SocketClosedNotification"; NSString * const SocketConnectingNotification = @"SocketConnectingNotification"; @interface TSSocketManager () @property (nonatomic, retain) NSTimer *timer; @property (nonatomic, retain) SRWebSocket *websocket; @property (nonatomic) NSUInteger status; @end @implementation TSSocketManager - (id)init{ self = [super init]; if (self) { self.websocket = nil; [self addObserver:self forKeyPath:@"status" options:0 context:kSocketStatusObservationContext]; } return self; } + (id)sharedManager { static TSSocketManager *sharedMyManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedMyManager = [[self alloc] init]; }); return sharedMyManager; } #pragma mark - Manage Socket + (void)becomeActive{ TSSocketManager *sharedInstance = [self sharedManager]; SRWebSocket *socket =[sharedInstance websocket]; if (socket) { switch ([socket readyState]) { case SR_OPEN: DDLogVerbose(@"WebSocket already open on connection request"); sharedInstance.status = kSocketStatusOpen; return; case SR_CONNECTING: DDLogVerbose(@"WebSocket is already connecting"); sharedInstance.status = kSocketStatusConnecting; return; default: [socket close]; sharedInstance.status = kSocketStatusClosed; socket.delegate = nil; socket = nil; break; } } NSString *webSocketConnect = [textSecureWebSocketAPI stringByAppendingString:[[self sharedManager] webSocketAuthenticationString]]; NSURL *webSocketConnectURL = [NSURL URLWithString:webSocketConnect]; socket = [[SRWebSocket alloc] initWithURL:webSocketConnectURL]; socket.delegate = [self sharedManager]; [socket open]; [[self sharedManager] setWebsocket:socket]; } + (void)resignActivity{ SRWebSocket *socket =[[self sharedManager] websocket]; [socket close]; } #pragma mark - Delegate methods - (void) webSocketDidOpen:(SRWebSocket *)webSocket{ NSLog(@"WebSocket was sucessfully opened"); self.timer = [NSTimer scheduledTimerWithTimeInterval:kWebSocketHeartBeat target:self selector:@selector(webSocketHeartBeat) userInfo:nil repeats:YES]; self.status = kSocketStatusOpen; } - (void) webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error{ NSLog(@"Error connecting to socket %@", error); [self.timer invalidate]; self.status = kSocketStatusClosed; } - (void) webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message{ NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding]; NSError *error = nil; NSDictionary *serializedMessage = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; NSString *base64message = [serializedMessage objectForKey:@"message"]; if (!base64message || error) { return; } [[TSMessagesManager sharedManager] handleBase64MessageSignal:base64message]; NSString *ackedId = [serializedMessage objectForKey:@"id"]; [self.websocket send:[[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:@{@"type":@"1", @"id":ackedId} options:0 error:nil] encoding:NSUTF8StringEncoding]]; } - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean{ DDLogVerbose(@"WebSocket did close"); [self.timer invalidate]; self.status = kSocketStatusClosed; } - (void)webSocketHeartBeat{ DDLogVerbose(@"WebSocket sent ping"); [self.websocket sendPing:nil]; } - (NSString*)webSocketAuthenticationString{ return [NSString stringWithFormat:@"?login=%@&password=%@", [[TSAccountManager registeredNumber] stringByReplacingOccurrencesOfString:@"+" withString:@"%2B"],[TSStorageManager serverAuthToken]]; } #pragma mark UI Delegates - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == kSocketStatusObservationContext) { 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; } } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } @end