diff --git a/TcpSocket.js b/TcpSocket.js index bd12e69..47d46b8 100644 --- a/TcpSocket.js +++ b/TcpSocket.js @@ -21,6 +21,7 @@ var { } = require('react-native'); var Sockets = NativeModules.TcpSockets; var base64 = require('base64-js'); +var Base64Str = require('./base64-str'); var noop = function () {}; var instances = 0; var STATE = { @@ -124,6 +125,8 @@ function isLegalPort(port) { } TcpSocket.prototype.setTimeout = function(msecs, callback) { + var self = this; + if (this._timeout) { clearTimeout(this._timeout); this._timeout = null; @@ -137,7 +140,8 @@ TcpSocket.prototype.setTimeout = function(msecs, callback) { var self = this; this._timeout = setTimeout(msecs, function() { self.emit('timeout'); - this._timeout = null; + self._timeout = null; + self.destroy(); }); } }; @@ -191,11 +195,13 @@ TcpSocket.prototype.end = function(data, encoding) { }; TcpSocket.prototype.destroy = function() { - this._destroyed = true; - this._debug('destroying'); - this._subscription.remove(); + if (!this._destroyed) { + this._destroyed = true; + this._debug('destroying'); + this._subscription.remove(); - Sockets.destroy(this._id); + Sockets.destroy(this._id, this._debug.bind(this, 'closed')); + } }; TcpSocket.prototype._onEvent = function(info) { @@ -239,8 +245,8 @@ TcpSocket.prototype.write = function(buffer, encoding, callback) { callback = callback || noop; var str; if (typeof buffer === 'string') { - console.warn('socket.WRITE(): interpreting as UTF8'); - str = buffer; + console.warn('socket.WRITE(): encoding as base64'); + str = Base64Str.encode(buffer); } else if (typeof Buffer !== 'undefined' && global.Buffer.isBuffer(buffer)) { encoded = true; str = buffer.toString('base64'); @@ -252,9 +258,14 @@ TcpSocket.prototype.write = function(buffer, encoding, callback) { } Sockets.write(this._id, str, encoded, function(err) { + if (self._timeout) { + clearTimeout(self._timeout); + self._timeout = null; + } + err = normalizeError(err); if (err) { - self._debug('send failed', err); + self._debug('write failed', err); return callback(err); } diff --git a/base64-str.js b/base64-str.js new file mode 100644 index 0000000..ee1a3d7 --- /dev/null +++ b/base64-str.js @@ -0,0 +1,100 @@ +/** + * Source: https://gist.github.com/ncerminara/11257943 + */ +'use strict'; + +(function () { + var Base64Str = { + _keyStr: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', + encode: function(e: string) { + var t = ''; + var n, r, i, s, o, u, a; + var f = 0; + e = Base64Str._utf8_encode(e); + while (f < e.length) { + n = e.charCodeAt(f++); + r = e.charCodeAt(f++); + i = e.charCodeAt(f++); + s = n >> 2; + o = (n & 3) << 4 | r >> 4; + u = (r & 15) << 2 | i >> 6; + a = i & 63; + if (isNaN(r)) { + u = a = 64; + } else if (isNaN(i)) { + a = 64; + } + t = t + this._keyStr.charAt(s) + this._keyStr.charAt(o) + this._keyStr + .charAt(u) + this._keyStr.charAt(a); + } + return t; + }, + decode: function(e: string) { + var t = ''; + var n, r, i; + var s, o, u, a; + var f = 0; + e = e.replace(/[^A-Za-z0-9\+\/\=]/g, ''); + while (f < e.length) { + s = this._keyStr.indexOf(e.charAt(f++)); + o = this._keyStr.indexOf(e.charAt(f++)); + u = this._keyStr.indexOf(e.charAt(f++)); + a = this._keyStr.indexOf(e.charAt(f++)); + n = s << 2 | o >> 4; + r = (o & 15) << 4 | u >> 2; + i = (u & 3) << 6 | a; + t = t + String.fromCharCode(n); + if (u !== 64) { + t = t + String.fromCharCode(r); + } + if (a !== 64) { + t = t + String.fromCharCode(i); + } + } + t = Base64Str._utf8_decode(t); + return t; + }, + _utf8_encode: function(e) { + e = e.replace(/\r\n/g, '\n'); + var t = ''; + for (var n = 0; n < e.length; n++) { + var r = e.charCodeAt(n); + if (r < 128) { + t += String.fromCharCode(r); + } else if (r > 127 && r < 2048) { + t += String.fromCharCode(r >> 6 | 192); + t += String.fromCharCode(r & 63 | 128); + } else { + t += String.fromCharCode(r >> 12 | 224); + t += String.fromCharCode(r >> 6 & 63 | 128); + t += String.fromCharCode(r & 63 | 128); + } + } + return t; + }, + _utf8_decode: function(e) { + var t = ''; + var n = 0; + var r = 0, /*c1 = 0, */c2 = 0; + while (n < e.length) { + r = e.charCodeAt(n); + if (r < 128) { + t += String.fromCharCode(r); + n++; + } else if (r > 191 && r < 224) { + c2 = e.charCodeAt(n + 1); + t += String.fromCharCode((r & 31) << 6 | c2 & 63); + n += 2; + } else { + c2 = e.charCodeAt(n + 1); + var c3 = e.charCodeAt(n + 2); + t += String.fromCharCode((r & 15) << 12 | (c2 & 63) << 6 | c3 & 63); + n += 3; + } + } + return t; + } + }; + + module.exports = Base64Str; +}()); diff --git a/ios/TcpSocketClient.h b/ios/TcpSocketClient.h index d8ecfb6..c813c5f 100644 --- a/ios/TcpSocketClient.h +++ b/ios/TcpSocketClient.h @@ -38,9 +38,7 @@ typedef enum RCTTCPError RCTTCPError; @interface TcpSocketClient : NSObject -@property (nonatomic, retain) NSString * id; -@property (nonatomic, retain) NSString * host; -@property (nonatomic) u_int16_t port; +@property (nonatomic, retain) NSNumber * id; ///--------------------------------------------------------------------------------------- /// @name Class Methods @@ -53,7 +51,7 @@ typedef enum RCTTCPError RCTTCPError; * @return New RCTTCPClient */ -+ (id)socketClientWithConfig:(id) delegate; ++ (id)socketClientWithId:(NSNumber *)clientID andConfig:(id) delegate; ///--------------------------------------------------------------------------------------- /// @name Instance Methods diff --git a/ios/TcpSocketClient.m b/ios/TcpSocketClient.m index bcfd5d2..bed82f0 100644 --- a/ios/TcpSocketClient.m +++ b/ios/TcpSocketClient.m @@ -23,21 +23,22 @@ NSString *const RCTTCPErrorDomain = @"RCTTCPErrorDomain"; long _sendTag; } -- (id)initWithConfig:(id) aDelegate; +- (id)initWithClientId:(NSNumber *)clientID andConfig:(id) aDelegate; @end @implementation TcpSocketClient -+ (id)socketClientWithConfig:(id)delegate ++ (id)socketClientWithId:(nonnull NSNumber *)clientID andConfig:(id)delegate { - return [[[self class] alloc] initWithConfig:delegate]; + return [[[self class] alloc] initWithClientId:clientID andConfig:delegate]; } -- (id)initWithConfig:(id) aDelegate +- (id)initWithClientId:(NSNumber *)clientID andConfig:(id) aDelegate { self = [super init]; if (self) { + _id = clientID; _clientDelegate = aDelegate; _pendingSends = [NSMutableDictionary dictionary]; } @@ -104,7 +105,7 @@ NSString *const RCTTCPErrorDomain = @"RCTTCPErrorDomain"; - (void)end { - [_tcpSocket disconnectAfterReadingAndWriting]; + [_tcpSocket disconnectAfterWriting]; } - (void)destroy @@ -121,6 +122,9 @@ NSString *const RCTTCPErrorDomain = @"RCTTCPErrorDomain"; - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port { + if (!_clientDelegate) return; + [_clientDelegate onConnect:self]; + [sock readDataWithTimeout:-1 tag:-1]; } diff --git a/ios/TcpSockets.m b/ios/TcpSockets.m index db9bc59..911dabf 100644 --- a/ios/TcpSockets.m +++ b/ios/TcpSockets.m @@ -54,7 +54,7 @@ RCT_EXPORT_METHOD(createSocket:(nonnull NSNumber*)cId) return; } - client = [TcpSocketClient socketClientWithConfig:self]; + client = [TcpSocketClient socketClientWithId:cId andConfig:self]; [_clients setObject:client forKey:cId]; } @@ -75,21 +75,15 @@ RCT_EXPORT_METHOD(connect:(nonnull NSNumber*)cId } RCT_EXPORT_METHOD(write:(nonnull NSNumber*)cId - string:(NSString *)string + string:(NSString *)base64String encoded:(BOOL)encoded callback:(RCTResponseSenderBlock)callback) { TcpSocketClient* client = [TcpSockets findClient:cId callback:callback]; if (!client) return; - NSData *data; - if (encoded) { - // iOS7+ - // TODO: use https://github.com/nicklockwood/Base64 for compatibility with earlier iOS versions - data = [[NSData alloc] initWithBase64EncodedString:string options:0]; - } else { - data = [string dataUsingEncoding:[NSString defaultCStringEncoding]]; - } - + // iOS7+ + // TODO: use https://github.com/nicklockwood/Base64 for compatibility with earlier iOS versions + NSData *data = [[NSData alloc] initWithBase64EncodedString:base64String options:0]; [client writeData:data callback:callback]; } @@ -98,15 +92,14 @@ RCT_EXPORT_METHOD(end:(nonnull NSNumber*)cId [TcpSockets endClient:cId callback:callback]; } -RCT_EXPORT_METHOD(destroy:(nonnull NSNumber*)cId) { - [TcpSockets destroyClient:cId]; +RCT_EXPORT_METHOD(destroy:(nonnull NSNumber*)cId + callback:(RCTResponseSenderBlock)callback) { + [TcpSockets destroyClient:cId callback:callback]; } - (void) onConnect:(TcpSocketClient*) client { - NSMutableDictionary *_clients = [TcpSockets clients]; - NSNumber *clientID = [[_clients allKeysForObject:client] objectAtIndex:0]; - [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"tcp-%@-event", clientID] + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"tcp-%@-event", client.id] body:@{ @"event": @"connect" }]; } @@ -121,21 +114,20 @@ RCT_EXPORT_METHOD(destroy:(nonnull NSNumber*)cId) { - (void) onClose:(TcpSocketClient*) client withError:(NSError *)err { - [self onError:client withError:err]; + if (err) { + [self onError:client withError:err]; + } + + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"tcp-%@-event", client.id] + body:@{ @"event": @"close", @"data": err == nil ? @NO : @YES }]; NSMutableDictionary *_clients = [TcpSockets clients]; - NSNumber *clientID = [[_clients allKeysForObject:client] objectAtIndex:0]; - - [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"tcp-%@-event", clientID] - body:@{ @"event": @"close", @"data": err == nil ? @NO : @YES }]; + [_clients removeObjectForKey:client.id]; } - (void)onError:(TcpSocketClient*) client withError:(NSError *)err { - NSMutableDictionary *_clients = [TcpSockets clients]; - NSNumber *clientID = [[_clients allKeysForObject:client] objectAtIndex:0]; - NSString* msg = [[err userInfo] valueForKey:@"NSLocalizedFailureReason"]; - [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"tcp-%@-event", clientID] + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"tcp-%@-event", client.id] body:@{ @"event": @"error", @"data": @[msg] }]; } @@ -147,8 +139,7 @@ RCT_EXPORT_METHOD(destroy:(nonnull NSNumber*)cId) { if (!client) { if (!callback) { RCTLogError(@"%@.missing callback parameter.", [self class]); - } - else { + } else { callback(@[[NSString stringWithFormat:@"no client found with id %@", cId]]); } @@ -161,17 +152,16 @@ RCT_EXPORT_METHOD(destroy:(nonnull NSNumber*)cId) { +(void) endClient:(nonnull NSNumber*)cId callback:(RCTResponseSenderBlock)callback { - NSMutableDictionary *_clients = [TcpSockets clients]; TcpSocketClient* client = [TcpSockets findClient:cId callback:callback]; if (!client) return; [client end]; - [_clients removeObjectForKey:cId]; if (callback) callback(@[]); } +(void) destroyClient:(nonnull NSNumber*)cId + callback:(RCTResponseSenderBlock)callback { NSMutableDictionary *_clients = [TcpSockets clients]; TcpSocketClient* client = [TcpSockets findClient:cId callback:nil]; diff --git a/ios/TcpSockets.xcodeproj/project.pbxproj b/ios/TcpSockets.xcodeproj/project.pbxproj index a4c5eaa..bf7110a 100644 --- a/ios/TcpSockets.xcodeproj/project.pbxproj +++ b/ios/TcpSockets.xcodeproj/project.pbxproj @@ -105,7 +105,7 @@ 58B511D31A9E6C8500147676 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0610; + LastUpgradeCheck = 0720; ORGANIZATIONNAME = "Tradle, Inc."; TargetAttributes = { 58B511DA1A9E6C8500147676 = { @@ -179,6 +179,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; diff --git a/package.json b/package.json index d3f8510..d819d45 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-tcp", - "version": "1.1.1", + "version": "0.0.1", "description": "node's dgram API for react-native", "main": "TcpSockets.js", "scripts": {