/** * Copyright (c) 2015-present, Peel Technologies, Inc. * All rights reserved. */ #import #import #import "TcpSocketClient.h" #import "RCTBridgeModule.h" #import "GCDAsyncSocket.h" NSString *const RCTTCPErrorDomain = @"RCTTCPErrorDomain"; @interface TcpSocketClient() { @private GCDAsyncSocket *_tcpSocket; NSMutableDictionary *_pendingSends; NSLock *_lock; long _sendTag; } - (id)initWithClientId:(NSNumber *)clientID andConfig:(id)aDelegate; - (id)initWithClientId:(NSNumber *)clientID andConfig:(id)aDelegate andSocket:(GCDAsyncSocket*)tcpSocket; @end @implementation TcpSocketClient + (id)socketClientWithId:(nonnull NSNumber *)clientID andConfig:(id)delegate { return [[[self class] alloc] initWithClientId:clientID andConfig:delegate andSocket:nil]; } - (id)initWithClientId:(NSNumber *)clientID andConfig:(id)aDelegate { return [self initWithClientId:clientID andConfig:aDelegate andSocket:nil]; } - (id)initWithClientId:(NSNumber *)clientID andConfig:(id)aDelegate andSocket:(GCDAsyncSocket*)tcpSocket; { self = [super init]; if (self) { _id = clientID; _clientDelegate = aDelegate; _pendingSends = [NSMutableDictionary dictionary]; _lock = [[NSLock alloc] init]; _tcpSocket = tcpSocket; } return self; } - (BOOL)connect:(NSString *)host port:(int)port withOptions:(NSDictionary *)options error:(NSError **)error { if (_tcpSocket) { if (error) { *error = [self badInvocationError:@"this client's socket is already connected"]; } return false; } _tcpSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:[self methodQueue]]; BOOL result = false; NSString *localAddress = (options?options[@"localAddress"]:nil); NSNumber *localPort = (options?options[@"localPort"]:nil); if (!localAddress && !localPort) { result = [_tcpSocket connectToHost:host onPort:port error:error]; } else { NSMutableArray *interface = [NSMutableArray arrayWithCapacity:2]; [interface addObject: localAddress?localAddress:@""]; if (localPort) { [interface addObject:[localPort stringValue]]; } result = [_tcpSocket connectToHost:host onPort:port viaInterface:[interface componentsJoinedByString:@":"] withTimeout:-1 error:error]; } return result; } - (NSDictionary *)getAddress { if (_tcpSocket) { if (_tcpSocket.isConnected) { return @{ @"port": @(_tcpSocket.connectedPort).stringValue, @"address": _tcpSocket.connectedHost ?: @"unknown", @"family": _tcpSocket.isIPv6?@"IPv6":@"IPv4" }; } else { return @{ @"port": @(_tcpSocket.localPort).stringValue, @"address": _tcpSocket.localHost ?: @"unknown", @"family": _tcpSocket.isIPv6?@"IPv6":@"IPv4" }; } } return @{ @"port": @"0", @"address": @"unknown", @"family": @"unkown" }; } - (BOOL)listen:(NSString *)host port:(int)port error:(NSError **)error { if (_tcpSocket) { if (error) { *error = [self badInvocationError:@"this client's socket is already connected"]; } return false; } _tcpSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:[self methodQueue]]; BOOL result = [_tcpSocket acceptOnInterface:host port:port error:error]; if (result == YES) { [_clientDelegate onConnect: self]; [_tcpSocket readDataWithTimeout:-1 tag:_id.longValue]; } return result; } - (void)setPendingSend:(RCTResponseSenderBlock)callback forKey:(NSNumber *)key { [_lock lock]; @try { [_pendingSends setObject:callback forKey:key]; } @finally { [_lock unlock]; } } - (RCTResponseSenderBlock)getPendingSend:(NSNumber *)key { [_lock lock]; @try { return [_pendingSends objectForKey:key]; } @finally { [_lock unlock]; } } - (void)dropPendingSend:(NSNumber *)key { [_lock lock]; @try { [_pendingSends removeObjectForKey:key]; } @finally { [_lock unlock]; } } - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)msgTag { NSNumber* tagNum = [NSNumber numberWithLong:msgTag]; RCTResponseSenderBlock callback = [self getPendingSend:tagNum]; if (callback) { callback(@[]); [self dropPendingSend:tagNum]; } } - (void) writeData:(NSData *)data callback:(RCTResponseSenderBlock)callback { [_tcpSocket writeData:data withTimeout:-1 tag:_sendTag]; if (callback) { [self setPendingSend:callback forKey:@(_sendTag)]; } _sendTag++; [_tcpSocket readDataWithTimeout:-1 tag:_id.longValue]; } - (void)end { [_tcpSocket disconnectAfterWriting]; } - (void)destroy { [_tcpSocket disconnect]; } - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { if (!_clientDelegate) return; [_clientDelegate onData:@(tag) data:data]; [sock readDataWithTimeout:-1 tag:tag]; } - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket { TcpSocketClient *inComing = [[TcpSocketClient alloc] initWithClientId:[_clientDelegate generateRandomId] andConfig:_clientDelegate andSocket:newSocket]; [_clientDelegate onConnection: inComing toClient: _id]; [newSocket readDataWithTimeout:-1 tag:inComing.id.longValue]; } - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port { if (!_clientDelegate) return; [_clientDelegate onConnect:self]; [sock readDataWithTimeout:-1 tag:_id.longValue]; } - (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock { // TODO : investigate for half-closed sockets /* no-op */ } - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err { if (!_clientDelegate) return; [_clientDelegate onClose:self withError:err]; } - (NSError *)badParamError:(NSString *)errMsg { NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; return [NSError errorWithDomain:RCTTCPErrorDomain code:RCTTCPBadParamError userInfo:userInfo]; } - (NSError *)badInvocationError:(NSString *)errMsg { NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; return [NSError errorWithDomain:RCTTCPErrorDomain code:RCTTCPInvalidInvocationError userInfo:userInfo]; } - (NSError *)sendFailedError:(NSString *)errMsg { NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; return [NSError errorWithDomain:RCTTCPErrorDomain code:RCTTCPSendFailedError userInfo:userInfo]; } - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); } @end