ipc-link-core
Advanced tools
Comparing version 0.1.1 to 0.2.0
{ | ||
"name": "ipc-link-core", | ||
"version": "0.1.1", | ||
"version": "0.2.0", | ||
"description": "IPC Utilities", | ||
"main": "src/index", | ||
"scripts": { | ||
"lint": "eslint --fix src", | ||
"test": "eslint src" | ||
"lint": "eslint --fix src test", | ||
"test": "eslint src test" | ||
}, | ||
@@ -10,0 +10,0 @@ "repository": { |
@@ -7,2 +7,3 @@ const { EventEmitter } = require('events'); | ||
const kIdentify = Symbol('IPC-Identify'); | ||
const bufferNull = Buffer.from('null'); | ||
@@ -159,4 +160,5 @@ class Node extends EventEmitter { | ||
const id = Node.createID(); | ||
const message = Node.packMessage(id, data, receptive); | ||
const message = Node._packMessage(id, data, receptive); | ||
socket.write(message); | ||
socket.write('\n'); | ||
@@ -230,2 +232,6 @@ if (!receptive) return resolve(undefined); | ||
/** | ||
* Disconnect from a socket, this will also reject all messages | ||
* @param {string} name The label name of the socket to disconnect | ||
*/ | ||
disconnectFrom(name) { | ||
@@ -271,3 +277,12 @@ const nodeSocket = this.sockets.get(name); | ||
this.emit('raw', name, socket, buffer); | ||
const [id, receptive, data] = Node.unPackMessage(buffer); | ||
this._unPackMessage(name, socket, buffer); | ||
} | ||
/** | ||
* Handle a parsed message | ||
* @param {string} name The label name of the socket | ||
* @param {net.Socket} socket The Socket that sent this message | ||
* @param {Object<string, *>} parsedData The parsed message data | ||
*/ | ||
_handleMessage(name, socket, { id, receptive, data }) { | ||
if (this._queue.has(id)) { | ||
@@ -278,7 +293,7 @@ this._queue.get(id).resolve(data); | ||
if (data === kPing) { | ||
socket.write(Node.packMessage(id, Date.now(), false)); | ||
socket.write(Node._packMessage(id, Date.now(), false)); | ||
return; | ||
} | ||
if (data === kIdentify) { | ||
socket.write(Node.packMessage(id, this.name, false)); | ||
socket.write(Node._packMessage(id, this.name, false)); | ||
return; | ||
@@ -291,3 +306,9 @@ } | ||
receptive: { value: receptive, enumerable: true }, | ||
reply: { value: (content) => receptive ? socket.write(Node.packMessage(id, content, false)) : false } | ||
reply: { | ||
value: (content) => { | ||
if (!receptive) return; | ||
socket.write(Node._packMessage(id, content, false)); | ||
socket.write('\n'); | ||
} | ||
} | ||
}); | ||
@@ -299,20 +320,45 @@ this.emit('message', message); | ||
* Unpack a buffer message for usage | ||
* @param {string} name The label name of the socket | ||
* @param {net.Socket} socket The Socket that sent this message | ||
* @param {Buffer} buffer The buffer to unpack | ||
* @returns {Array<*>} | ||
* @private | ||
*/ | ||
static unPackMessage(buffer) { | ||
const kIndex = buffer.indexOf(kSeparatorHeader); | ||
const [id, type, _receptive] = buffer.toString('utf8', 0, kIndex - 1).split(' '); | ||
const receptive = _receptive === '1'; | ||
if (type === '5') return [id, receptive, kPing]; | ||
if (type === '6') return [id, receptive, kIdentify]; | ||
if (type === '3') return [id, receptive, buffer.slice(kIndex + 2)]; | ||
if (type === '0') return [id, receptive, null]; | ||
_unPackMessage(name, socket, buffer) { | ||
while (buffer.length) { | ||
const headerSeparatorIndex = buffer.indexOf(kSeparatorHeader); | ||
if (headerSeparatorIndex === -1) break; | ||
const kString = buffer.toString('utf8', kIndex + 2); | ||
if (type === '1') return [id, receptive, kString]; | ||
if (type === '2') return [id, receptive, Number(kString)]; | ||
if (type === '4') return [id, receptive, JSON.parse(kString)]; | ||
throw new Error(`Failed to unpack message. Got type ${type}, expected an integer between 0 and 6.`); | ||
const [id, type, _receptive, bodyLength] = buffer.toString('utf8', 0, headerSeparatorIndex - 1).split(' ').map(value => value.trim()); | ||
if (!(type in R_MESSAGE_TYPES)) throw new Error(`Failed to unpack message. Got type ${type}, expected an integer between 0 and 7.`); | ||
const startBodyIndex = headerSeparatorIndex + 2; | ||
const endBodyIndex = startBodyIndex + parseInt(bodyLength, 36); | ||
const body = buffer.slice(startBodyIndex, endBodyIndex); | ||
const pType = R_MESSAGE_TYPES[type]; | ||
const receptive = _receptive === '1'; | ||
if (pType === 'PING') { | ||
this._handleMessage(name, socket, { id, receptive, data: kPing }); | ||
} else if (pType === 'IDENTIFY') { | ||
this._handleMessage(name, socket, { id, receptive, data: kIdentify }); | ||
} else if (pType === 'NULL') { | ||
this._handleMessage(name, socket, { id, receptive, data: null }); | ||
} else if (pType === 'BUFFER') { | ||
this._handleMessage(name, socket, { id, receptive, data: body }); | ||
} else { | ||
const bodyString = body.toString('utf8'); | ||
if (pType === 'STRING') | ||
this._handleMessage(name, socket, { id, receptive, data: bodyString }); | ||
else if (pType === 'NUMBER') | ||
this._handleMessage(name, socket, { id, receptive, data: Number(bodyString) }); | ||
else if (pType === 'OBJECT') | ||
this._handleMessage(name, socket, { id, receptive, data: JSON.parse(bodyString) }); | ||
else if (pType === 'SET') | ||
this._handleMessage(name, socket, { id, receptive, data: new Set(JSON.parse(bodyString)) }); | ||
else if (pType === 'MAP') | ||
this._handleMessage(name, socket, { id, receptive, data: new Map(JSON.parse(bodyString)) }); | ||
} | ||
buffer = buffer.slice(endBodyIndex + 1); | ||
} | ||
} | ||
@@ -328,25 +374,30 @@ | ||
*/ | ||
static packMessage(id, message, receptive = true) { | ||
receptive = Number(receptive); | ||
if (message === kPing) return Buffer.from(`${id} 5 0 | ${Date.now()}`); | ||
if (message === kIdentify) return Buffer.from(`${id} 6 0 | null`); | ||
let type; | ||
const tMessage = typeof message; | ||
if (tMessage === 'string') | ||
return Buffer.from(`${id} 1 ${receptive} | ${message}`); | ||
static _packMessage(id, message, receptive = true) { | ||
receptive = message === kPing || message === kIdentify ? 0 : Number(receptive); | ||
const [type, buffer] = Node._getMessageDetails(message); | ||
return Buffer.concat([Buffer.from(`${id} ${type} ${receptive} ${buffer.length.toString(36)} | `), buffer]); | ||
} | ||
if (tMessage === 'number') | ||
return Buffer.from(`${id} 2 ${receptive} | ${message}`); | ||
/** | ||
* Get the message details | ||
* @param {*} message The message to convert | ||
* @returns {Array<number | Buffer>} | ||
*/ | ||
static _getMessageDetails(message) { | ||
if (message === kPing) return [S_MESSAGE_TYPES.PING, Buffer.from(Date.now())]; | ||
if (message === kIdentify) return [S_MESSAGE_TYPES.IDENTIFY, bufferNull]; | ||
if (message === null) return [S_MESSAGE_TYPES.NULL, bufferNull]; | ||
if (tMessage === 'object') { | ||
if (message === null) | ||
return Buffer.from(`${id} 0 ${receptive} | null`); | ||
if (Buffer.isBuffer(message)) | ||
return Buffer.concat(Buffer.from(`${id} 3 ${receptive} | `), message); | ||
return Buffer.from(`${id} 4 ${receptive} | ${JSON.stringify(message)}`); | ||
switch (typeof message) { | ||
case 'string': return [S_MESSAGE_TYPES.STRING, Buffer.from(message)]; | ||
case 'number': return [S_MESSAGE_TYPES.NUMBER, Buffer.from(message.toString())]; | ||
case 'object': { | ||
if (message instanceof Set) return [S_MESSAGE_TYPES.SET, Buffer.from(JSON.stringify([...message]))]; | ||
if (message instanceof Map) return [S_MESSAGE_TYPES.MAP, Buffer.from(JSON.stringify([...message]))]; | ||
if (Buffer.isBuffer(message)) return [S_MESSAGE_TYPES.BUFFER, message]; | ||
return [S_MESSAGE_TYPES.OBJECT, Buffer.from(JSON.stringify(message))]; | ||
} | ||
default: | ||
return [S_MESSAGE_TYPES.STRING, Buffer.from(String(message))]; | ||
} | ||
return Buffer.from(`${id} 1 ${receptive} | ${type}`); | ||
} | ||
@@ -360,3 +411,4 @@ | ||
static createID() { | ||
return Date.now().toString(36) + String.fromCharCode(((i++ < 26 || (i = 0)) % 26) + 97); | ||
i = i < 26 ? i + 1 : 0; | ||
return Date.now().toString(36) + String.fromCharCode(i + 97); | ||
} | ||
@@ -366,2 +418,26 @@ | ||
const S_MESSAGE_TYPES = Object.freeze({ | ||
NULL: 0, | ||
STRING: 1, | ||
NUMBER: 2, | ||
SET: 3, | ||
MAP: 4, | ||
BUFFER: 5, | ||
OBJECT: 6, | ||
PING: 7, | ||
IDENTIFY: 8 | ||
}); | ||
const R_MESSAGE_TYPES = Object.freeze({ | ||
0: 'NULL', | ||
1: 'STRING', | ||
2: 'NUMBER', | ||
3: 'SET', | ||
4: 'MAP', | ||
5: 'BUFFER', | ||
6: 'OBJECT', | ||
7: 'PING', | ||
8: 'IDENTIFY' | ||
}); | ||
let i = 0; | ||
@@ -368,0 +444,0 @@ |
@@ -5,12 +5,22 @@ // This example must be run before interactive/world, since this serves the | ||
// eslint-disable-next-line no-unused-vars | ||
const node = new Node('hello') | ||
.on('connection', (name, socket) => { | ||
.on('connection', (name) => { | ||
console.log(`Connected to ${name}`); | ||
node.sendTo(socket, 'Hello') | ||
.then(reply => console.log(`Hello ${reply}`)); | ||
}) | ||
.on('listening', console.log.bind(null, 'Listening')) | ||
.on('message', console.log.bind(null, 'Message')) | ||
.on('message', message => { | ||
console.log(`Received data:`, message); | ||
// For World.js test | ||
if (message.data === 'Hello') { | ||
message.reply('world!'); | ||
} else { | ||
setTimeout( | ||
() => message.reply(`Reply!: ${message.data}`), | ||
Math.min(9000, Math.floor(Math.random() * 1000)) | ||
); | ||
} | ||
}) | ||
.on('error', console.error.bind(null, 'Error')) | ||
.on('socketClose', console.log.bind(null, 'Closed Socket:')) | ||
.serve('hello', 8001); |
@@ -1,6 +0,6 @@ | ||
import { EventEmitter } from 'events'; | ||
import { Server, Socket, ListenOptions, SocketConnectOpts } from 'net'; | ||
import { resolve } from 'dns'; | ||
declare module 'ipc-link-core' { | ||
import { EventEmitter } from 'events'; | ||
import { Server, Socket, ListenOptions, SocketConnectOpts } from 'net'; | ||
import { resolve } from 'dns'; | ||
@@ -7,0 +7,0 @@ export class Node extends EventEmitter { |
28028
15
634