stub out some more functionality

This commit is contained in:
Andy Prock 2015-12-14 16:16:45 -08:00
parent 49dd5a926e
commit 1618cfb61f
9 changed files with 1928 additions and 1718 deletions

View File

@ -14,6 +14,7 @@
var inherits = require('inherits');
var EventEmitter = require('events').EventEmitter;
var ipRegex = require('ip-regex');
var {
DeviceEventEmitter,
NativeModules
@ -30,28 +31,30 @@ var STATE = {
module.exports = TcpSocket;
function TcpSocket(options, onopen) {
function TcpSocket(options) {
EventEmitter.call(this);
this._id = instances++;
this._state = STATE.DISCONNECTED;
this._connecting = false;
this._hadError = false;
this._handle = null;
this._parent = null;
this._host = null;
if (typeof options === 'number') {
options = { fd: options }; // Legacy interface.
options = { fd: options };
} else if (options === undefined) {
options = {};
}
if (options.fd) {
throw new Error('file descriptors are unsupoprted at this time.');
}
// these will be set once there is a connection
this.readable = this.writable = false;
this._subscription = DeviceEventEmitter.addListener(
'tcp-' + this._id + '-data', this._onReceive.bind(this)
'tcp-' + this._id + '-event', this._onEvent.bind(this)
);
// ensure compatibility with node's EventEmitter
@ -59,11 +62,7 @@ function TcpSocket(options, onopen) {
this.on = this.addListener.bind(this);
}
if (onopen) {
this.on('open', onopen);
}
Sockets.createSocket(this._id, options); // later
Sockets.createSocket(this._id); // later
}
inherits(TcpSocket, EventEmitter);
@ -79,71 +78,150 @@ TcpSocket.prototype._debug = function() {
TcpSocket.prototype.connect = function(options, callback) {
var self = this;
var port = options.port;
var host = options.host;
if (this._state !== STATE.DISCONNECTED) {
throw new Error('Socket is already bound');
}
if (callback) {
this.once('connected', callback.bind(this));
if (typeof callback === 'function') {
this.once('connected', callback);
}
var host = options.host || 'localhost';
var port = options.port;
var localAddress = options.localAddress;
var localPort = options.localPort;
if (localAddress && !ipRegex({exact: true}).test(localAddress)) {
throw new TypeError('"localAddress" option must be a valid IP: ' + localAddress);
}
if (localPort && typeof localPort !== 'number') {
throw new TypeError('"localPort" option should be a number: ' + localPort);
}
if (typeof port !== 'undefined') {
if (typeof port !== 'number' && typeof port !== 'string') {
throw new TypeError('"port" option should be a number or string: ' + port);
}
if (!isLegalPort(port)) {
throw new RangeError('"port" option should be >= 0 and < 65536: ' + port);
}
}
port |= 0;
this._state = STATE.CONNECTING;
this._connecting = true;
this._debug('connecting, host:', host, 'port:', port);
Sockets.connect(this._id, port, host, function(err, addr) {
err = normalizeError(err);
if (err) {
// questionable: may want to self-destruct and
// force user to create a new socket
self._state = STATE.DISCONNECTED;
self._debug('failed to bind', err);
if (callback) {
callback(err);
}
return self.emit('error', err);
}
self._debug('connected to address:', host, 'port:', port);
// self._host = addr.host;
// self._port = addr.port;
self.writable = self.readable = true;
self._state = STATE.CONNECTED;
self._connecting = false;
self.emit('connected');
});
Sockets.connect(this._id, host, Number(port), options);
};
// Check that the port number is not NaN when coerced to a number,
// is an integer and that it falls within the legal range of port numbers.
function isLegalPort(port) {
if (typeof port === 'string' && port.trim() === '') {
return false;
}
return +port === (port >>> 0) && port >= 0 && port <= 0xFFFF;
}
TcpSocket.prototype.setTimeout = function(msecs, callback) {
if (this._timeout) {
clearTimeout(this._timeout);
this._timeout = null;
}
if (msecs > 0) {
if (callback) {
this.once('timeout', callback);
}
var self = this;
this._timeout = setTimeout(msecs, function() {
self.emit('timeout');
this._timeout = null;
});
}
};
TcpSocket.prototype.setNoDelay = function(noDelay) {
// nothing yet
};
TcpSocket.prototype.end = function() {
TcpSocket.prototype.setEncoding = function(encoding) {
// nothing yet
};
TcpSocket.prototype.setKeepAlive = function(enable, initialDelay) {
// nothing yet
};
TcpSocket.prototype.pause = function() {
// nothing yet
};
TcpSocket.prototype.resume = function() {
// nothing yet
};
TcpSocket.prototype.ref = function() {
// nothing yet
};
TcpSocket.prototype.unref = function() {
// nothing yet
};
TcpSocket.prototype.address = function() {
// nothing yet
};
TcpSocket.prototype.end = function(data, encoding) {
if (this._destroyed) {
return;
}
if (data) {
this.write(data, encoding);
}
this._destroyed = true;
this._debug('closing');
this._subscription.remove();
Sockets.close(this._id, this._debug.bind(this, 'closed'));
this.emit('close');
Sockets.end(this._id, this._debug.bind(this, 'closed'));
};
TcpSocket.prototype.destroy = TcpSocket.prototype.end;
TcpSocket.prototype.destroy = function() {
this._destroyed = true;
this._debug('destroying');
this._subscription.remove();
TcpSocket.prototype._onReceive = function(info) {
this._debug('received', info);
Sockets.destroy(this._id);
};
// from base64 string
var buf = typeof Buffer === 'undefined'
? base64.toByteArray(info.data)
: new global.Buffer(info.data, 'base64');
TcpSocket.prototype._onEvent = function(info) {
this._debug('received', info.event);
this.emit('data', buf);
if (info.event === 'connect') {
self.writable = self.readable = true;
self._state = STATE.CONNECTED;
self._connecting = false;
} else if (info.event === 'data') {
if (this._timeout) {
clearTimeout(this._timeout);
this._timeout = null;
}
// from base64 string
info.data = typeof Buffer === 'undefined'
? base64.toByteArray(info.data)
: new global.Buffer(info.data, 'base64');
} else if (info.event === 'close') {
self._state = STATE.DISCONNECTED;
}
this.emit(info.event, info.data);
};
TcpSocket.prototype.write = function(buffer, encoding, callback) {

View File

@ -4,10 +4,72 @@
*/
'use strict';
var ipRegex = require('ip-regex');
exports.Socket = require('./TcpSocket');
// Target API:
//
// var s = net.connect({port: 80, host: 'google.com'}, function() {
// ...
// });
//
// There are various forms:
//
// connect(options, [cb])
// connect(port, [host], [cb])
// connect(path, [cb]);
//
exports.connect = exports.createConnection = function() {
var args = normalizeConnectArgs(arguments);
exports.Socket._debug('createConnection', args);
var s = new exports.Socket(args[0]);
return exports.Socket.prototype.connect.apply(s, args);
};
// Returns an array [options] or [options, cb]
// It is the same as the argument of Socket.prototype.connect().
function normalizeConnectArgs(args) {
var options = {};
if (args[0] !== null && typeof args[0] === 'object') {
// connect(options, [cb])
options = args[0];
} else if (isPipeName(args[0])) {
// connect(path, [cb]);
options.path = args[0];
} else {
// connect(port, [host], [cb])
options.port = args[0];
if (typeof args[1] === 'string') {
options.host = args[1];
}
}
var cb = args[args.length - 1];
return typeof cb === 'function' ? [options, cb] : [options];
};
exports.createConnection = function(options, callback) {
var tcpSocket = new exports.Socket();
tcpSocket.connect(options, callback);
return tcpSocket;
};
exports.isIP = function(input) {
var result = 0;
if (ipRegex.v4({exact: true}).test(input)) {
result = 4;
} else if (ipRegex.v6({exact: true}).test(input)) {
result = 6;
}
};
exports.isIPv4 = function(input) {
return exports.isIP(input) === 4;
};
exports.isIPv6 = function(input) {
return exports.isIP(input) === 6;
};

View File

@ -10,61 +10,60 @@ var {
global.Buffer = global.Buffer || require('buffer').Buffer;
var telnet = require('./telnet-client');
var connection = new telnet();
var net = require('net');
// require('./test/simple/test-dgram-address')
// require('./test/simple/test-dgram-bind-default-address')
// require('./test/simple/test-dgram-bind-shared-ports')
function randomPort() {
return Math.random() * 60536 | 0 + 5000 // 60536-65536
}
// function randomPort() {
// return Math.random() * 60536 | 0 + 5000 // 60536-65536
// }
var a = net.createConnection({ port: randomPort() }, function(err) {
if (err) throw err
// var params = {
// host: 'towel.blinkenlights.nl',
// port: 23,
// shellPrompt: '/ # ',
// timeout: 1500,
// // removeEcho: 4
// };
var params = {
// host: '10.0.1.207',
port: 23,
timeout: 15000,
passwordPrompt: /Password[: ]*$/i
// removeEcho: 4
};
connection.on('ready', function(prompt) {
connection.exec('ls', function(err, response) {
console.log(response);
});
console.log('connected');
});
connection.on('writedone', function() {
console.log('writedone');
a.on('error', function(err) {
console.log(err);
});
connection.on('loginfailed', function() {
console.log('loginfailed');
var b = net.createConnection({ port: randomPort() }, function(err) {
if (err) throw err
console.log('connected');
});
connection.on('error', function(error) {
console.log(error);
b.on('error', function(err) {
console.log(err);
});
connection.on('timeout', function() {
console.log('socket timeout!');
connection.end();
});
connection.on('close', function() {
console.log('connection closed');
});
connection.connect(params);
// a.on('message', function(data, rinfo) {
// var str = String.fromCharCode.apply(null, new Uint8Array(data));
// console.log('a received', str, rinfo)
// a.close()
// b.close()
// })
//
// b.on('message', function(data, rinfo) {
// var str = String.fromCharCode.apply(null, new Uint8Array(data));
// console.log('b received', str, rinfo)
//
// // echo back
// b.send(data, 0, data.length, aPort, '127.0.0.1', function(err) {
// if (err) throw err
//
// console.log('sent')
// })
// })
//
// b.once('listening', function() {
// var msg = toByteArray('hello')
// a.send(msg, 0, msg.length, bPort, '127.0.0.1', function(err) {
// if (err) throw err
//
// console.log('sent')
// })
// })
var rctsockets = React.createClass({
render: function() {

File diff suppressed because it is too large Load Diff

View File

@ -29,14 +29,17 @@ typedef enum RCTTCPError RCTTCPError;
@protocol SocketClientDelegate <NSObject>
- (void)onConnect:(TcpSocketClient*) client;
- (void)onData:(TcpSocketClient*) client data:(NSData *)data;
- (void)onClose:(TcpSocketClient*) client withError:(NSError *)err;
- (void)onError:(TcpSocketClient*) client withError:(NSError *)err;
@end
@interface TcpSocketClient : NSObject
@property (nonatomic, retain) NSString* id;
@property (nonatomic, retain) NSString* host;
@property (nonatomic, retain) NSString * id;
@property (nonatomic, retain) NSString * host;
@property (nonatomic) u_int16_t port;
///---------------------------------------------------------------------------------------
@ -62,7 +65,7 @@ typedef enum RCTTCPError RCTTCPError;
* @param host ip address
* @return true if bound, false if there was an error
*/
- (BOOL)connect:(u_int16_t) port host:(NSString*) host error:(NSError**)error;
- (BOOL)connect:(NSString *)host port:(int)port withOptions:(NSDictionary *)options error:(NSError **)error;
/**
* write data
@ -71,9 +74,14 @@ typedef enum RCTTCPError RCTTCPError;
- (void)writeData:(NSData*) data callback:(RCTResponseSenderBlock) callback;
/**
* close client
* end client
*/
- (void)close;
- (void)end;
/**
* destroy client
*/
- (void)destroy;
@end

View File

@ -17,12 +17,10 @@ NSString *const RCTTCPErrorDomain = @"RCTTCPErrorDomain";
@interface TcpSocketClient()
{
@private
uint16_t _port;
NSString* _address;
GCDAsyncSocket *_tcpSocket;
id<SocketClientDelegate> _clientDelegate;
NSMutableDictionary* _pendingSends;
long tag;
long _sendTag;
}
- (id)initWithConfig:(id<SocketClientDelegate>) aDelegate;
@ -47,10 +45,9 @@ NSString *const RCTTCPErrorDomain = @"RCTTCPErrorDomain";
return self;
}
- (BOOL) connect:(u_int16_t)port host:(NSString *)host error:(NSError **) error
- (BOOL)connect:(NSString *)host port:(int)port withOptions:(NSDictionary *)options error:(NSError **)error
{
if (_port) {
if (_tcpSocket) {
if (error) {
*error = [self badInvocationError:@"this client's socket is already connected"];
}
@ -58,13 +55,25 @@ NSString *const RCTTCPErrorDomain = @"RCTTCPErrorDomain";
return false;
}
_port = port;
_address = host;
_tcpSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:[self methodQueue]];
BOOL result;
if (_address) {
result = [_tcpSocket connectToHost:_address onPort:_port error:error];
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;
@ -83,23 +92,30 @@ NSString *const RCTTCPErrorDomain = @"RCTTCPErrorDomain";
- (void) writeData:(NSData *)data
callback:(RCTResponseSenderBlock)callback
{
[_tcpSocket writeData:data withTimeout:-1 tag:tag];
[_tcpSocket writeData:data withTimeout:-1 tag:_sendTag];
if (callback) {
[_pendingSends setObject:callback forKey:[NSNumber numberWithLong:tag]];
[_pendingSends setObject:callback forKey:[NSNumber numberWithLong:_sendTag]];
}
tag++;
_sendTag++;
[_tcpSocket readDataWithTimeout:-1 tag:-1];
}
- (void) close
- (void)end
{
[_tcpSocket disconnectAfterReadingAndWriting];
}
- (void)destroy
{
[_tcpSocket disconnect];
}
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
if (!_clientDelegate) return;
[_clientDelegate onData:self data:data];
[sock readDataWithTimeout:-1 tag:-1];
}
@ -108,6 +124,16 @@ NSString *const RCTTCPErrorDomain = @"RCTTCPErrorDomain";
[sock readDataWithTimeout:-1 tag:-1];
}
- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock
{
}
- (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];

View File

@ -40,7 +40,7 @@ RCT_EXPORT_MODULE()
return c;
}
RCT_EXPORT_METHOD(createSocket:(nonnull NSNumber*)cId withOptions:(NSDictionary*)options)
RCT_EXPORT_METHOD(createSocket:(nonnull NSNumber*)cId)
{
NSMutableDictionary* _clients = [TcpSockets clients];
if (!cId) {
@ -59,26 +59,23 @@ RCT_EXPORT_METHOD(createSocket:(nonnull NSNumber*)cId withOptions:(NSDictionary*
}
RCT_EXPORT_METHOD(connect:(nonnull NSNumber*)cId
port:(int)port
host:(NSString *)host
callback:(RCTResponseSenderBlock)callback)
port:(int)port
withOptions:(NSDictionary *)options)
{
TcpSocketClient* client = [TcpSockets findClient:cId callback:callback];
TcpSocketClient* client = [TcpSockets findClient:cId callback:nil];
if (!client) return;
NSError *error = nil;
if (![client connect:port host:host error:&error])
if (![client connect:host port:port withOptions:options error:&error])
{
NSString* msg = [[error userInfo] valueForKey:@"NSLocalizedFailureReason"];
callback(@[msg]);
[self onError:client withError:error];
return;
}
callback(@[[NSNull null], [NSNull null]]);
}
RCT_EXPORT_METHOD(write:(nonnull NSNumber*)cId
string:(NSString*)string
string:(NSString *)string
encoded:(BOOL)encoded
callback:(RCTResponseSenderBlock)callback) {
TcpSocketClient* client = [TcpSockets findClient:cId callback:callback];
@ -96,9 +93,21 @@ RCT_EXPORT_METHOD(write:(nonnull NSNumber*)cId
[client writeData:data callback:callback];
}
RCT_EXPORT_METHOD(close:(nonnull NSNumber*)cId
RCT_EXPORT_METHOD(end:(nonnull NSNumber*)cId
callback:(RCTResponseSenderBlock)callback) {
[TcpSockets closeClient:cId callback:callback];
[TcpSockets endClient:cId callback:callback];
}
RCT_EXPORT_METHOD(destroy:(nonnull NSNumber*)cId) {
[TcpSockets destroyClient:cId];
}
- (void) onConnect:(TcpSocketClient*) client
{
NSMutableDictionary* _clients = [TcpSockets clients];
NSString *clientID = [[_clients allKeysForObject:client] objectAtIndex:0];
[self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"tcp-%@-event", clientID]
body:@{ @"event": @"connect" }];
}
- (void) onData:(TcpSocketClient*) client data:(NSData *)data
@ -106,8 +115,29 @@ RCT_EXPORT_METHOD(close:(nonnull NSNumber*)cId
NSMutableDictionary* _clients = [TcpSockets clients];
NSString *clientID = [[_clients allKeysForObject:client] objectAtIndex:0];
NSString *base64String = [data base64EncodedStringWithOptions:0];
[self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"tcp-%@-data", clientID]
body:@{ @"data": base64String }];
[self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"tcp-%@-event", clientID]
body:@{ @"event": @"data", @"data": base64String }];
}
- (void) onClose:(TcpSocketClient*) client withError:(NSError *)err
{
[self onError:client withError:err];
NSMutableDictionary* _clients = [TcpSockets clients];
NSString *clientID = [[_clients allKeysForObject:client] objectAtIndex:0];
[self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"tcp-%@-event", clientID]
body:@{ @"event": @"close", @"data": err == nil ? @NO : @YES }];
}
- (void)onError:(TcpSocketClient*) client withError:(NSError *)err {
NSMutableDictionary* _clients = [TcpSockets clients];
NSString *clientID = [[_clients allKeysForObject:client] objectAtIndex:0];
NSString* msg = [[err userInfo] valueForKey:@"NSLocalizedFailureReason"];
[self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"tcp-%@-event", clientID]
body:@{ @"event": @"error", @"data": @[msg] }];
}
+(TcpSocketClient*)findClient:(nonnull NSNumber*)cId callback:(RCTResponseSenderBlock)callback
@ -128,23 +158,33 @@ RCT_EXPORT_METHOD(close:(nonnull NSNumber*)cId
return client;
}
+(void) closeClient:(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 close];
[client end];
[_clients removeObjectForKey:cId];
if (callback) callback(@[]);
}
+(void) destroyClient:(nonnull NSNumber*)cId
{
NSMutableDictionary* _clients = [TcpSockets clients];
TcpSocketClient* client = [TcpSockets findClient:cId callback:nil];
if (!client) return;
[client destroy];
[_clients removeObjectForKey:cId];
}
+(void) closeAllSockets {
NSMutableDictionary* _clients = [TcpSockets clients];
for (NSNumber* cId in _clients) {
[TcpSockets closeClient:cId callback:nil];
[TcpSockets endClient:cId callback:nil];
}
}

View File

@ -10,7 +10,6 @@
13BE3DEE1AC21097009241FE /* TcpSockets.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* TcpSockets.m */; };
7350006B1AFF9AB600ED3C82 /* TcpSocketClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 7350006A1AFF9AB600ED3C82 /* TcpSocketClient.m */; };
73D9377D1AFF9EBE00450142 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 73D9377C1AFF9EBE00450142 /* CFNetwork.framework */; };
73D9377F1AFF9F6E00450142 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 73D9377E1AFF9F6E00450142 /* UIKit.framework */; };
96EDB0AC1C10C33B00D41E94 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 96EDB0AB1C10C33B00D41E94 /* GCDAsyncSocket.m */; };
/* End PBXBuildFile section */
@ -33,7 +32,6 @@
735000691AFF9AB600ED3C82 /* TcpSocketClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TcpSocketClient.h; sourceTree = "<group>"; };
7350006A1AFF9AB600ED3C82 /* TcpSocketClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TcpSocketClient.m; sourceTree = "<group>"; };
73D9377C1AFF9EBE00450142 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
73D9377E1AFF9F6E00450142 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
96EDB0AA1C10C33B00D41E94 /* GCDAsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDAsyncSocket.h; sourceTree = "<group>"; };
96EDB0AB1C10C33B00D41E94 /* GCDAsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncSocket.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -43,7 +41,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
73D9377F1AFF9F6E00450142 /* UIKit.framework in Frameworks */,
73D9377D1AFF9EBE00450142 /* CFNetwork.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -62,7 +59,6 @@
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
73D9377E1AFF9F6E00450142 /* UIKit.framework */,
73D9377C1AFF9EBE00450142 /* CFNetwork.framework */,
735000691AFF9AB600ED3C82 /* TcpSocketClient.h */,
7350006A1AFF9AB600ED3C82 /* TcpSocketClient.m */,

View File

@ -36,6 +36,7 @@
"base64-js": "0.0.8",
"events": "^1.0.2",
"inherits": "^2.0.1",
"ip-regex": "^1.0.3",
"util": "^0.10.3"
}
}