react-native-tcp/ios/TcpSocketClient.m

265 lines
7.4 KiB
Objective-C

/**
* Copyright (c) 2015-present, Peel Technologies, Inc.
* All rights reserved.
*/
#import <netinet/in.h>
#import <arpa/inet.h>
#import "TcpSocketClient.h"
#import "RCTBridgeModule.h"
#import "GCDAsyncSocket.h"
NSString *const RCTTCPErrorDomain = @"RCTTCPErrorDomain";
@interface TcpSocketClient()
{
@private
GCDAsyncSocket *_tcpSocket;
NSMutableDictionary<NSNumber *, RCTResponseSenderBlock> *_pendingSends;
NSLock *_lock;
long _sendTag;
}
- (id)initWithClientId:(NSNumber *)clientID andConfig:(id<SocketClientDelegate>)aDelegate;
- (id)initWithClientId:(NSNumber *)clientID andConfig:(id<SocketClientDelegate>)aDelegate andSocket:(GCDAsyncSocket*)tcpSocket;
@end
@implementation TcpSocketClient
+ (id)socketClientWithId:(nonnull NSNumber *)clientID andConfig:(id<SocketClientDelegate>)delegate
{
return [[[self class] alloc] initWithClientId:clientID andConfig:delegate andSocket:nil];
}
- (id)initWithClientId:(NSNumber *)clientID andConfig:(id<SocketClientDelegate>)aDelegate
{
return [self initWithClientId:clientID andConfig:aDelegate andSocket:nil];
}
- (id)initWithClientId:(NSNumber *)clientID andConfig:(id<SocketClientDelegate>)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<NSString *, NSString *> *)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