Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions __tests__/allowHalfOpen.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { expect, test, jest } from '@jest/globals';
import net from '../src/index';
import { nativeEventEmitter } from '../src/Globals';
import { NativeModules } from 'react-native';

const Sockets = NativeModules.TcpSockets;

jest.mock('../src/Globals', () => {
const { EventEmitter } = require('events');
const emitter = new EventEmitter();
const originalAddListener = emitter.addListener.bind(emitter);
// @ts-ignore
emitter.addListener = (event, listener) => {
originalAddListener(event, listener);
return { remove: () => emitter.removeListener(event, listener) };
};

let idCounter = 1000;
return {
__esModule: true,
nativeEventEmitter: emitter,
getNextId: () => idCounter++,
};
});

test('allowHalfOpen: false (default) should call Sockets.end() on end event', () => {
return new Promise((resolve, reject) => {
// Reset mocks
Sockets.end.mockClear();

const server = net.createServer(); // allowHalfOpen default false
// @ts-ignore
const serverId = server._id;
server.listen(12345);

server.on('connection', (socket) => {
socket.on('end', () => {
try {
// When we receive 'end', if allowHalfOpen is false, socket.end() should be called
// which calls Sockets.end(id)
expect(Sockets.end).toHaveBeenCalled();
resolve(undefined);
} catch (e) {
reject(e);
}
});
});

// Simulate connection
nativeEventEmitter.emit('connection', {
id: serverId,
info: {
id: 456,
connection: {
localAddress: '127.0.0.1',
localPort: 12345,
remoteAddress: '127.0.0.1',
remotePort: 54321,
remoteFamily: 'IPv4',
},
},
});

// Simulate 'end' event from native for socket 456
nativeEventEmitter.emit('end', { id: 456 });
});
});

test('allowHalfOpen: true should NOT call Sockets.end() on end event', () => {
return new Promise((resolve, reject) => {
// Reset mocks
Sockets.end.mockClear();

const server = net.createServer({ allowHalfOpen: true });
// @ts-ignore
const serverId = server._id;
server.listen(12346);

server.on('connection', (socket) => {
socket.on('end', () => {
try {
// When we receive 'end', if allowHalfOpen is true, socket.end() should NOT be called
expect(Sockets.end).not.toHaveBeenCalled();
resolve(undefined);
} catch (e) {
reject(e);
}
});
});

// Simulate connection
nativeEventEmitter.emit('connection', {
id: serverId,
info: {
id: 457,
connection: {
localAddress: '127.0.0.1',
localPort: 12346,
remoteAddress: '127.0.0.1',
remotePort: 54321,
remoteFamily: 'IPv4',
},
},
});

// Simulate 'end' event from native for socket 457
nativeEventEmitter.emit('end', { id: 457 });
});
});
53 changes: 53 additions & 0 deletions __tests__/server_options.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { expect, test, jest } from '@jest/globals';

jest.mock('../src/Globals', () => {
const { EventEmitter } = require('events');
const emitter = new EventEmitter();
const originalAddListener = emitter.addListener.bind(emitter);
// @ts-ignore
emitter.addListener = (event, listener) => {
originalAddListener(event, listener);
return { remove: () => emitter.removeListener(event, listener) };
};
return {
__esModule: true,
nativeEventEmitter: emitter,
getNextId: () => 123,
};
});

import net from '../src/index';
import { nativeEventEmitter } from '../src/Globals';

test('server option pauseOnConnect should pause the socket', () => {
return new Promise((resolve, reject) => {
const server = net.createServer({ pauseOnConnect: true });

server.listen(12345);

server.on('connection', (socket) => {
try {
// Check if socket is paused
// @ts-ignore
expect(socket._paused).toBe(true);
resolve(undefined);
} catch (error) {
reject(error);
}
});

nativeEventEmitter.emit('connection', {
id: 123,
info: {
id: 456,
connection: {
localAddress: '127.0.0.1',
localPort: 12345,
remoteAddress: '127.0.0.1',
remotePort: 54321,
remoteFamily: 'IPv4',
},
},
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ public void onData(int id, byte[] data) {
sendEvent("data", eventParams);
}

public void onEnd(int id) {
WritableMap eventParams = Arguments.createMap();
eventParams.putInt("id", id);
sendEvent("end", eventParams);
}

public void onWritten(int id, int msgId, @Nullable Exception e) {
String error = null;
if (e != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,8 @@ public void run() {
if (bufferCount > 0) {
receiverListener.onData(socketId, Arrays.copyOfRange(buffer, 0, bufferCount));
} else if (bufferCount == -1) {
clientSocket.destroy();
receiverListener.onEnd(socketId);
break;
}
}
} catch (IOException | InterruptedException ioe) {
Expand Down
1 change: 1 addition & 0 deletions ios/TcpSocketClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ typedef enum RCTTCPError RCTTCPError;
- (void)onSecureConnection:(TcpSocketClient *)client
toClient:(NSNumber *)clientID;
- (void)onData:(NSNumber *)clientID data:(NSData *)data;
- (void)onEnd:(NSNumber *)clientID;
- (void)onClose:(TcpSocketClient *)client withError:(NSError *)err;
- (void)onError:(TcpSocketClient *)client withError:(NSError *)err;
- (void)onWrittenData:(TcpSocketClient *)client msgId:(NSNumber *)msgId;
Expand Down
6 changes: 3 additions & 3 deletions ios/TcpSocketClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -579,9 +579,9 @@ - (void)socket:(GCDAsyncSocket *)sock
}

- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock {
// TODO : investigate for half-closed sockets
// for now close the stream completely
[sock disconnect];
if (_clientDelegate) {
[_clientDelegate onEnd:[sock userData]];
}
}

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
Expand Down
14 changes: 12 additions & 2 deletions ios/TcpSockets.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ @implementation TcpSockets {
- (NSArray<NSString *> *)supportedEvents {
return @[
@"connect", @"listening", @"connection", @"secureConnection", @"data",
@"close", @"error", @"written"
@"close", @"error", @"written", @"end"
];
}

Expand Down Expand Up @@ -294,7 +294,17 @@ - (void)onSocketConnection:(TcpSocketClient *)client
- (void)onData:(NSNumber *)clientID data:(NSData *)data {
NSString *base64String = [data base64EncodedStringWithOptions:0];
[self sendEventWithName:@"data"
body:@{@"id" : clientID, @"data" : base64String}];
body:@{
@"id" : clientID,
@"data" : base64String
}];
}

- (void)onEnd:(NSNumber *)clientID {
[self sendEventWithName:@"end"
body:@{
@"id" : clientID
}];
}

- (void)onClose:(NSNumber *)clientID withError:(NSError *)err {
Expand Down
4 changes: 4 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ jest.mock('react-native', () => {
destroy: jest.fn(),
write: jest.fn(),
listen: jest.fn(),
pause: jest.fn(),
resume: jest.fn(),
setKeepAlive: jest.fn(),
setNoDelay: jest.fn(),
},
},
NativeEventEmitter: NativeEventEmitter,
Expand Down
9 changes: 9 additions & 0 deletions src/Server.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export default class Server extends EventEmitter {
this._id = getNextId();
/** @protected @readonly */
this._eventEmitter = nativeEventEmitter;
// console.log('Server eventEmitter:', this._eventEmitter);
/** @private @type {Set<Socket>} */
this._connections = new Set();
/** @private */
Expand Down Expand Up @@ -249,6 +250,14 @@ export default class Server extends EventEmitter {
const keepAliveDelay = this._serverOptions.keepAliveInitialDelay || 0;
newSocket.setKeepAlive(this._serverOptions.keepAlive, keepAliveDelay);
}

if (this._serverOptions.allowHalfOpen !== undefined) {
newSocket.allowHalfOpen = this._serverOptions.allowHalfOpen;
}

if (this._serverOptions.pauseOnConnect) {
newSocket.pause();
}
}

return newSocket;
Expand Down
Loading