Skip to content

Commit

Permalink
lib: emit events about connection messages
Browse files Browse the repository at this point in the history
  • Loading branch information
lundibundi committed Jul 26, 2017
1 parent 4d76172 commit 900917b
Show file tree
Hide file tree
Showing 5 changed files with 259 additions and 6 deletions.
30 changes: 30 additions & 0 deletions lib/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ class Connection extends EventEmitter {
if (error) this.emit('error', error);
};

// Defined in constructor to be used as heartbeat message
// in debug mode events
this._heartbeatMessage = {};

transport.on('packet', this._processPacket.bind(this));
transport.on('close', this._onSocketClose.bind(this));
transport.on('error', this._onSocketError.bind(this));
Expand Down Expand Up @@ -193,6 +197,10 @@ class Connection extends EventEmitter {
_heartbeatCallback(interval) {
this.transport.send('{}');
this.setTimeout(interval, this._heartbeatCallbackInstance);

if (process.env.NODE_ENV !== 'production') {
this.emit('sentPacket', this._heartbeatMessage);
}
}

// Stop sending heartbeat packets
Expand Down Expand Up @@ -269,6 +277,10 @@ class Connection extends EventEmitter {
_send(packet) {
const data = serde.stringify(packet);
this.transport.send(data);

if (process.env.NODE_ENV !== 'production') {
this.emit('sentPacket', packet);
}
}

// Close the connection, optionally sending a final packet
Expand All @@ -280,6 +292,10 @@ class Connection extends EventEmitter {
if (packet) {
const data = serde.stringify(packet);
this.transport.end(data);

if (process.env.NODE_ENV !== 'production') {
this.emit('sentPacket', packet);
}
} else {
this.transport.end();
}
Expand Down Expand Up @@ -307,6 +323,10 @@ class Connection extends EventEmitter {
// packets - array of packets
//
_processPacket(packet) {
if (process.env.NODE_ENV !== 'production') {
this.emit('receivedPacket', packet);
}

const keys = Object.keys(packet);
if (keys.length === 0) return; // heartbeat packet

Expand Down Expand Up @@ -381,6 +401,8 @@ class Connection extends EventEmitter {
this, application, authStrategy, credentials,
this._onSessionCreated.bind(this)
);

this.emit('handshakeRequest', applicationName, authStrategy);
}

// Callback of authentication operation
Expand Down Expand Up @@ -412,6 +434,8 @@ class Connection extends EventEmitter {
const packetId = packet.handshake[0];
const callback = this._callbacks[packetId];

this.emit('handshake', packet.error, packet.ok);

if (!callback) {
this._rejectPacket(packet);
}
Expand Down Expand Up @@ -460,6 +484,8 @@ class Connection extends EventEmitter {
return;
}

this.emit('call', interfaceName, methodName, args);

try {
this.application.callMethod(
this, interfaceName, methodName, args, callback
Expand All @@ -477,6 +503,8 @@ class Connection extends EventEmitter {
const packetId = packet.callback[0];
const callback = this._callbacks[packetId];

this.emit('callback', packet.error, packet.ok);

if (callback) {
delete this._callbacks[packetId];

Expand Down Expand Up @@ -515,6 +543,8 @@ class Connection extends EventEmitter {
const packetId = packet.inspect[0];
const interfaceName = packet.inspect[1];

this.emit('inspect', interfaceName);

const methods = this.application.getMethods(interfaceName);
if (methods) {
this.callback(packetId, null, methods);
Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"remark-lint": "^6.0.0",
"remark-validate-links": "^6.1.0",
"tap": "^10.7.0",
"tsame": "^1.1.2",
"webpack": "^3.1.0"
},
"scripts": {
Expand Down
201 changes: 201 additions & 0 deletions test/node/connection-emit-actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
'use strict';

const test = require('tap');
const net = require('net');

const jstp = require('../..');

require('../tap-oneOf');

const app = require('../fixtures/application');

const application = new jstp.Application(app.name, app.interfaces);
const serverConfig = {
applications: [application],
authPolicy: app.authCallback,
};

let server;
let connection;

test.beforeEach((done) => {
server = jstp.net.createServer(serverConfig);
server.listen(0, () => {
const port = server.address().port;
jstp.net.connect(app.name, null, port, 'localhost', (error, conn) => {
test.assertNot(error, 'must connect to server and perform handshake');
connection = conn;
done();
});
});
});

test.afterEach((done) => {
if (connection) {
connection.close();
connection = null;
}
server.close();
done();
});

test.test('must emit server and client events upon anonymous handshake',
(test) => {
test.plan(7);

const client = {
application: new jstp.Application('jstp', {}),
};

server.once('connect', (serverConnection) => {
serverConnection.on('handshakeRequest',
(applicationName, authStrategy) => {
test.equal(applicationName, app.name,
'application name must match');
test.equal(authStrategy, 'anonymous',
'auth strategy must be anonymous by default');
}
);
});

const port = server.address().port;
const socket = net.connect(port);
socket.on('error', () => {
test.fail('must create socket and connect to server');
});
socket.on('connect', () => {
const transport = new jstp.net.Transport(socket);
const connection = new jstp.Connection(transport, null, client);

connection.on('handshake', (error, ok) => {
test.assertNot(error, 'handshake must not return an error');
test.equal(ok, app.sessionId,
'session id must be equal to the one provided by authCallback');
});

connection.handshake(app.name, null, null, (error) => {
test.assertNot(error, 'handshake must not return an error');
test.equal(connection.username, null, 'username must be null');
test.equal(connection.sessionId, app.sessionId,
'session id must be equal to the one provided by authCallback');
connection.close();
});
});
}
);

test.test('must emit server and client events login authentication strategy',
(test) => {
test.plan(7);

const client = {
application: new jstp.Application('jstp', {}),
};

server.once('connect', (serverConnection) => {
serverConnection.on('handshakeRequest',
(applicationName, authStrategy) => {
test.equal(applicationName, app.name,
'application name must match');
test.equal(authStrategy, 'login',
'authentication strategy must be \'login\'');
}
);
});

const port = server.address().port;
const socket = net.connect(port);
socket.on('error', () => {
test.fail('must create socket and connect to server');
});
socket.on('connect', () => {
const transport = new jstp.net.Transport(socket);
const connection = new jstp.Connection(transport, null, client);

connection.on('handshake', (error, ok) => {
test.assertNot(error, 'handshake must not return an error');
test.equal(ok, app.sessionId,
'session id must be equal to the one provided by authCallback');
});

connection.handshake(app.name, app.login, app.password, (error) => {
test.assertNot(error, 'handshake must not return an error');
test.equal(connection.username, app.login, 'username must match');
test.equal(connection.sessionId, app.sessionId,
'session id must be equal to the one provided by authCallback');
connection.close();
});
});
}
);

test.test('must emit event on call without arguments and with a return value',
(test) => {
test.plan(5);

const iface = 'calculator';
const methodName = 'answer';
const args = [];

server.getClients()[0].on('call',
(actualInterfaceName, actualMethodName, actualArgs) => {
test.equal(actualInterfaceName, iface,
'method interface must match');
test.equal(actualMethodName, methodName,
'method name must be equal to the called one');
test.strictSame(actualArgs, args,
'method arguments must be equal to the passed ones');
}
);

connection.on('callback', (error, ok) => {
test.assertNot(error, 'callMethod must not return an error');
test.strictSame(ok, [42], 'ok contents must match');
});

connection.callMethod(iface, methodName, args);
}
);

test.test('must emit event upon inspect packet', (test) => {
const expectedInterfaces = Object.keys(app.interfaces);
const expectedTests = expectedInterfaces.length;

test.plan(expectedTests);
server.getClients()[0].on('inspect', (interfaceName) => {
test.assert(expectedInterfaces.includes(interfaceName),
'inspect event interface must be one of expected');
});

expectedInterfaces.forEach((iface) => {
connection.inspectInterface(iface);
});
});

test.test('must emit packets in development mode', (test) => {
// 4 packets from call below and 4 from 1 heartbeat
test.plan(8);

const clientSentPackets = [{}, { call: [1, 'calculator'], answer: [] }];
const serverSentPackets = [{}, { callback: [1], ok: [42] }];

addEmitPacketCheck(
test, server.getClients()[0], 'sentPacket', serverSentPackets
);
addEmitPacketCheck(
test, server.getClients()[0], 'receivedPacket', clientSentPackets
);
addEmitPacketCheck(test, connection, 'sentPacket', clientSentPackets);
addEmitPacketCheck(test, connection, 'receivedPacket', serverSentPackets);

connection.callMethod('calculator', 'answer', []);
connection.startHeartbeat(100);
});

function addEmitPacketCheck(test, connection, event, allowedPackets) {
connection.on(event, (packet) => {
test.oneOf(packet, allowedPackets,
'must emit one of the specified packets');
});
}

21 changes: 21 additions & 0 deletions test/tap-oneOf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict';

const tap = require('tap');
const tsame = require('tsame');

tap.Test.prototype.addAssert('oneOf', 2,
function(found, allowed, message, extra) {
message = message || 'must be one of allowed';

extra.found = found;
extra.pattern = allowed;

for (const obj of allowed) {
if (tsame.strict(found, obj)) {
return this.pass(message, extra);
}
}

this.fail(message, extra);
}
);

0 comments on commit 900917b

Please sign in to comment.