266 lines
7.4 KiB
Objective-C
266 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 *, id> *)getAddress
|
|
{
|
|
if (_tcpSocket)
|
|
{
|
|
if (_tcpSocket.isConnected) {
|
|
return @{ @"port": @(_tcpSocket.connectedPort),
|
|
@"address": _tcpSocket.connectedHost ?: @"unknown",
|
|
@"family": _tcpSocket.isIPv6?@"IPv6":@"IPv4" };
|
|
} else {
|
|
return @{ @"port": @(_tcpSocket.localPort),
|
|
@"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 getNextId]
|
|
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
|
|
// for now close the stream completely
|
|
[sock disconnect];
|
|
}
|
|
|
|
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
|
|
{
|
|
if (!_clientDelegate) return;
|
|
[_clientDelegate onClose:self withError:(!err || err.code == GCDAsyncSocketClosedError ? nil : 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
|