Comparing version 1.3.2 to 1.3.3
487
index.js
'use strict'; // always strict mode | ||
// Import dependencies | ||
const EventEmitter = require ('events').EventEmitter; | ||
const uuid = require ('uuid'); | ||
const ipc = require ('node-ipc'); | ||
const nacl = require ('tweetnacl'); | ||
const EventEmitter = require ('events').EventEmitter; | ||
const uuid = require ('uuid'); | ||
const SocketIo = require ('socket.io'); | ||
const SocketIoClient = require ('socket.io-client'); | ||
const nacl = require ('tweetnacl'); | ||
// Create RPC events class | ||
class RpcEvents extends EventEmitter { | ||
constructor (stream) { | ||
super (); | ||
constructor (stream) { | ||
super (); | ||
this._stream = stream; | ||
this._listen (); | ||
} | ||
// Store stream variable as property and listen for events | ||
this._stream = stream; | ||
this._listen (); | ||
} | ||
send (id, ...params) { | ||
// Emit event to remote | ||
this._stream.send ('evt', { | ||
params : params, | ||
id : id, | ||
}); | ||
} | ||
send (id, ...params) { | ||
// Emit event to remote | ||
this._stream.send ('evt', { | ||
params : params, | ||
id : id, | ||
}); | ||
} | ||
_listen () { | ||
// Listen for remote user events | ||
this._stream.on ('evt', (data) => { | ||
// Rebroadcast to listeners on self | ||
this.emit (data.id, ...data.params); | ||
}); | ||
} | ||
_listen () { | ||
// Listen for remote user events | ||
this._stream.on ('evt', (data) => { | ||
// Rebroadcast to listeners on self | ||
this.emit (data.id, ...data.params); | ||
}); | ||
} | ||
} | ||
@@ -37,287 +39,278 @@ | ||
class Rpc extends EventEmitter { | ||
constructor (stream, child) { | ||
// Induce spawning of parent/super event emitter | ||
super (); | ||
constructor (stream, child) { | ||
// Induce spawning of parent/super event emitter | ||
super (); | ||
// Set own ID for identification | ||
this.id = uuid (); | ||
// Set own ID for identification | ||
this.id = uuid (); | ||
// Set mock for accessing methods | ||
this.class = this._genClass (); | ||
// Set mock for accessing methods | ||
this.class = this._genClass (); | ||
// Set child for handling plain events | ||
this.events = new RpcEvents (stream); | ||
// Set child for handling plain events | ||
this.events = new RpcEvents (stream); | ||
// Set private variables | ||
this._stream = stream; | ||
this._child = child; | ||
// Give child class instance a copy of this/RPC | ||
if (child._handleRpc != null) child._handleRpc (this); | ||
// Listen for events if have child class | ||
if (child != null) this._listen (); | ||
} | ||
// Set private variables | ||
this._stream = stream; | ||
this._child = child; | ||
_genClass () { | ||
// Create handler for proxy class | ||
let proxyHandler = { | ||
// Handle get on proxy class | ||
get: (target, property) => { | ||
// Return function which takes all trailing params | ||
return (async (...params) => { | ||
// Issue remote call with property name and recieved params | ||
let result = await this._call (property, params); | ||
// Listen for events if have child class | ||
if (child != null) this._listen (); | ||
} | ||
// Check if response is error | ||
if (result.isError) { | ||
// Throw if error to emulate native function | ||
throw result.data; | ||
} else { | ||
// Non error, simply return data | ||
return result.data; | ||
} | ||
}); | ||
}, | ||
}; | ||
_genClass () { | ||
// Create handler for proxy class | ||
let proxyHandler = { | ||
// Handle get on proxy class | ||
get: (target, property) => { | ||
// Return function which takes all trailing params | ||
return (async (...params) => { | ||
// Issue remote call with property name and recieved params | ||
let result = await this._call (property, params); | ||
// Create a new `Proxy` to act as our class | ||
let proxy = new Proxy ({}, proxyHandler); | ||
// Check if response is error | ||
if (result.isError) { | ||
// Throw if error to emulate native function | ||
throw result.data; | ||
} else { | ||
// Non error, simply return data | ||
return result.data; | ||
} | ||
}); | ||
}, | ||
}; | ||
// Return the proxy | ||
return proxy; | ||
} | ||
// Create a new `Proxy` to act as our class | ||
let proxy = new Proxy ({}, proxyHandler); | ||
_listen () { | ||
// Listen for function calls | ||
this._stream.on ('fnCall', async (call) => { | ||
// Create response variable on higher scope | ||
let response = null; | ||
// Return the proxy | ||
return proxy; | ||
} | ||
// Find internal handler | ||
let handler = this._child[call.fnId]; | ||
_listen () { | ||
// Listen for function calls | ||
this._stream.on ('fnCall', async (call) => { | ||
// Create response variable on higher scope | ||
let response = null; | ||
// Handle inexistence | ||
if (handler == null) { | ||
this._stream.send ('fnRes.' + call.resId, { | ||
isError: true, | ||
data: "No such method", | ||
}); | ||
// Find internal handler | ||
let handler = this._child[call.fnId].bind(this._child); | ||
return; | ||
} | ||
// Handle inexistence | ||
if (handler == null) { | ||
this._stream.send ('fnRes.' + call.resId, { | ||
isError : true, | ||
data : 'No such method', | ||
}); | ||
// Try getting response from handler or handle error | ||
try { | ||
// Run with applied params and await result | ||
response = await handler (...call.params); | ||
} catch (err) { | ||
// Emit error as response | ||
this._stream.send ('fnRes.' + call.resId, { | ||
isError: true, | ||
data: err, | ||
}); | ||
return; | ||
} | ||
return; | ||
} | ||
// Try getting response from handler or handle error | ||
try { | ||
// Run with applied params and await result | ||
response = await handler (...call.params); | ||
} catch (err) { | ||
// Emit error as response | ||
this._stream.send ('fnRes.' + call.resId, { | ||
isError: true, | ||
data: err, | ||
}); | ||
// Emit success and function return result | ||
this._stream.send ('fnRes.' + call.resId, { | ||
isError: false, | ||
data: response, | ||
}); | ||
}); | ||
} | ||
return; | ||
} | ||
async _call (fnId, params) { | ||
// Generate ID to listen for responses on | ||
let resId = uuid (); | ||
// Emit success and function return result | ||
this._stream.send ('fnRes.' + call.resId, { | ||
isError: false, | ||
data: response, | ||
}); | ||
}); | ||
} | ||
// Emit the remote call event | ||
this._stream.send ('fnCall', { | ||
fnId: fnId, | ||
resId: resId, | ||
params: params, | ||
}); | ||
async _call (fnId, params) { | ||
// Generate ID to listen for responses on | ||
let resId = uuid (); | ||
// Create and await a promise to get function response | ||
let response = await new Promise ((resolve, reject) => { | ||
// Listen for a response on generated response ID | ||
this._stream.once ('fnRes.' + resId, (response) => { | ||
// Resolve promise with recieved data | ||
resolve (response); | ||
}); | ||
}); | ||
// Emit the remote call event | ||
this._stream.send ('fnCall', { | ||
fnId: fnId, | ||
resId: resId, | ||
params: params, | ||
}); | ||
return response; | ||
} | ||
// Create and await a promise to get function response | ||
let response = await new Promise ((resolve, reject) => { | ||
// Listen for a response on generated response ID | ||
this._stream.once ('fnRes.' + resId, (response) => { | ||
// Resolve promise with recieved data | ||
resolve (response); | ||
}); | ||
}); | ||
// Induced by implementations of `Rpc` | ||
_disconnect () { | ||
this.emit ('disconnect'); | ||
} | ||
return response; | ||
} | ||
// Induced by implementations of `Rpc` | ||
_disconnect () { | ||
// Emit disconnect event so users of RPC will be aware | ||
this.emit ('disconnect'); | ||
} | ||
} | ||
class ServerIpcInterface extends EventEmitter { | ||
constructor (socket, cryptKey, onDisconnect) { | ||
super (); | ||
class ServerSocketInterface extends EventEmitter { | ||
constructor (socket, cryptKey, onDisconnect) { | ||
super (); | ||
// Set private class variables | ||
this._socket = socket; | ||
this._onDisconnect = onDisconnect; | ||
this._cryptKey = cryptKey; | ||
// Set private class variables | ||
this._socket = socket; | ||
this._onDisconnect = onDisconnect; | ||
this._cryptKey = cryptKey; | ||
this._listen (); | ||
} | ||
// Listen for IPC data | ||
this._listen (); | ||
} | ||
_listen () { | ||
ipc.server.on ('message', (data, socket) => { | ||
if (socket.id != this._socket.id) { | ||
return // Not for our socket, ignore | ||
} | ||
_listen () { | ||
this._socket.on ('message', (data) => { | ||
// silently ignore bad data | ||
if (data.nonce == null || data.encrypted == null) return; | ||
// silently ignore bad data | ||
if (data.nonce == null || data.encrypted == null) return; | ||
// open nacl secret box | ||
let decrypted = nacl.secretbox.open (Buffer.from (data.encrypted, 'base64'), Buffer.from (data.nonce, 'base64'), this._cryptKey); | ||
// open nacl secret box | ||
let decrypted = nacl.secretbox.open (Buffer.from (data.encrypted, 'base64'), Buffer.from (data.nonce, 'base64'), this._cryptKey); | ||
// silently ignore bad crypt, TODO: handle | ||
if (decrypted == false) return; | ||
// silently ignore bad crypt, TODO: handle | ||
if (decrypted == false) return; | ||
// decode JSON message | ||
let decoded = JSON.parse (Buffer.from (decrypted).toString ()); | ||
// decode JSON message | ||
let decoded = JSON.parse (Buffer.from (decrypted).toString ()); | ||
// emit event | ||
this.emit (decoded.id, decoded.data); | ||
}); | ||
// emit event | ||
this.emit (decoded.id, decoded.data); | ||
}); | ||
this._socket.on ('disconnect', () => { | ||
// Run self ondisconnect to handle server disconnection | ||
this._onDisconnect (); | ||
}); | ||
} | ||
ipc.server.on ('socket.disconnected', (socket) => { | ||
if (socket.id != this._socket.id) { | ||
return // Not for our socket, ignore | ||
} | ||
send (id, data) { | ||
// Encode object to JSON | ||
let encoded = JSON.stringify ({ | ||
'id' : id, | ||
'data' : data | ||
}); | ||
this._onDisconnect (); | ||
}); | ||
} | ||
// Generate a random nonce for cryptography | ||
let nonce = nacl.randomBytes (nacl.box.nonceLength); | ||
send (id, data) { | ||
// Encode object to JSON | ||
let encoded = JSON.stringify ({ | ||
'id' : id, | ||
'data' : data | ||
}); | ||
// Encrypt encoded object using nonce, hardcoded server public key and our secret key | ||
let encrypted = nacl.secretbox (Buffer.from (encoded), nonce, this._cryptKey); | ||
// Generate a random nonce for cryptography | ||
let nonce = nacl.randomBytes (nacl.box.nonceLength); | ||
// Send nonce, from secret key, and encrypted data over IPC | ||
this._socket.emit ('message', { | ||
'nonce' : Buffer.from (nonce).toString ('base64'), | ||
'encrypted' : Buffer.from (encrypted).toString ('base64') | ||
}); | ||
} | ||
} | ||
// Encrypt encoded object using nonce, hardcoded server public key and our secret key | ||
let encrypted = nacl.secretbox (Buffer.from (encoded), nonce, this._cryptKey); | ||
class ServerSocketRpc extends Rpc { | ||
constructor (socket, cryptKey, child) { | ||
// Create event interface from provided ipc socket | ||
let ipcInterface = new ServerSocketInterface (socket, cryptKey, () => { | ||
// Induce self disconnect when socket interface disconnected | ||
this._disconnect (); | ||
}); | ||
// Send nonce, from secret key, and encrypted data over IPC | ||
ipc.server.emit (this._socket, 'message', { | ||
'nonce' : Buffer.from (nonce).toString ('base64'), | ||
'encrypted' : Buffer.from (encrypted).toString ('base64') | ||
}); | ||
} | ||
// Create parent RPC instance using interface as stream | ||
super (ipcInterface, child); | ||
} | ||
} | ||
class ServerIpcRpc extends Rpc { | ||
constructor (socket, cryptKey, child) { | ||
// Create event interface from provided ipc socket | ||
let ipcInterface = new ServerIpcInterface (socket, cryptKey, () => { | ||
// Induce self disconnect when socket interface disconnected | ||
this._disconnect (); | ||
}); | ||
class ServerSocketRpcMaster extends EventEmitter { | ||
constructor(port, cryptKey, child) { | ||
super (); | ||
// Create parent RPC instance using interface as stream | ||
super (ipcInterface, child); | ||
} | ||
} | ||
let socketIo = new SocketIo (); | ||
class ServerIpcRpcMaster extends EventEmitter { | ||
constructor(namespace, cryptKey, child) { | ||
super (); | ||
socketIo.on ('connection', (socket) => { | ||
let clientRpc = new ServerSocketRpc (socket, cryptKey, child); | ||
this.emit ('client', clientRpc); | ||
}); | ||
// Build IPC | ||
ipc.config.id = namespace; | ||
ipc.config.retry = 1500; | ||
ipc.config.silent = true; | ||
// Start IPC server | ||
ipc.serve (); | ||
ipc.server.start (); | ||
ipc.server.on ('connect', (socket) => { | ||
let clientRpc = new ServerIpcRpc (socket, cryptKey, child); | ||
this.emit ('client', clientRpc); | ||
}); | ||
} | ||
socketIo.listen (port); | ||
} | ||
} | ||
class ClientIpcInterface extends EventEmitter { | ||
constructor (namespace, cryptKey) { | ||
// Create parent event emitter | ||
super (); | ||
class ClientSocketInterface extends EventEmitter { | ||
constructor (host, port, cryptKey) { | ||
// Create parent event emitter | ||
super (); | ||
this._namespace = namespace; | ||
this._cryptKey = cryptKey; | ||
let socket = SocketIoClient ('http://' + host + ':' + port); | ||
// build ipc | ||
ipc.config.id = namespace; | ||
ipc.config.retry = 1500; | ||
ipc.config.silent = true; | ||
this._cryptKey = cryptKey; | ||
this._socket = socket; | ||
ipc.connectTo (namespace, () => { | ||
this._listen (); | ||
}); | ||
} | ||
this._listen (); | ||
} | ||
_listen () { | ||
ipc.of[this._namespace].on ('message', (data) => { | ||
// silently ignore bad data | ||
if (data.nonce == null || data.encrypted == null) return; | ||
// open nacl secret box | ||
let decrypted = nacl.secretbox.open (Buffer.from (data.encrypted, 'base64'), Buffer.from (data.nonce, 'base64'), this._cryptKey); | ||
_listen () { | ||
this._socket.on ('message', (data) => { | ||
// silently ignore bad data | ||
if (data.nonce == null || data.encrypted == null) return; | ||
// silently ignore bad crypt, TODO: handle | ||
if (decrypted == null) return; | ||
// open nacl secret box | ||
let decrypted = nacl.secretbox.open (Buffer.from (data.encrypted, 'base64'), Buffer.from (data.nonce, 'base64'), this._cryptKey); | ||
// decode JSON message | ||
let decoded = JSON.parse (Buffer.from (decrypted).toString ()); | ||
// silently ignore bad crypt, TODO: handle | ||
if (decrypted == null) return; | ||
// emit event | ||
this.emit (decoded.id, decoded.data); | ||
}); | ||
} | ||
// decode JSON message | ||
let decoded = JSON.parse (Buffer.from (decrypted).toString ()); | ||
send (id, data) { | ||
// Encode object to JSON | ||
let encoded = JSON.stringify ({ | ||
'id' : id, | ||
'data' : data, | ||
}); | ||
// emit event | ||
this.emit (decoded.id, decoded.data); | ||
}); | ||
} | ||
// Generate a random nonce for cryptography | ||
let nonce = nacl.randomBytes (nacl.box.nonceLength) | ||
send (id, data) { | ||
// Encode object to JSON | ||
let encoded = JSON.stringify ({ | ||
'id' : id, | ||
'data' : data, | ||
}); | ||
// Encrypt encoded object using nonce, hardcoded server public key and our secret key | ||
let encrypted = nacl.secretbox (Buffer.from (encoded), nonce, this._cryptKey) | ||
// Generate a random nonce for cryptography | ||
let nonce = nacl.randomBytes (nacl.box.nonceLength) | ||
// Send nonce, from secret key, and encrypted data over IPC | ||
ipc.of[this._namespace].emit ('message', { | ||
'nonce' : Buffer.from (nonce).toString ('base64'), | ||
'encrypted' : Buffer.from (encrypted).toString ('base64') | ||
}); | ||
} | ||
// Encrypt encoded object using nonce, hardcoded server public key and our secret key | ||
let encrypted = nacl.secretbox (Buffer.from (encoded), nonce, this._cryptKey) | ||
// Send nonce, from secret key, and encrypted data over IPC | ||
this._socket.emit ('message', { | ||
'nonce' : Buffer.from (nonce).toString ('base64'), | ||
'encrypted' : Buffer.from (encrypted).toString ('base64') | ||
}); | ||
} | ||
} | ||
class ClientIpcRpc extends Rpc { | ||
constructor (namespace, cryptKey, client) { | ||
// Create client IPC interface for provided namespace | ||
let ipcInterface = new ClientIpcInterface (namespace, cryptKey); | ||
class ClientSocketRpc extends Rpc { | ||
constructor (host, port, cryptKey, client) { | ||
// Create client IPC interface for provided namespace | ||
let socketInterface = new ClientSocketInterface (host, port, cryptKey); | ||
// Create parent RPC module using interface as stream | ||
super (ipcInterface, client); | ||
} | ||
// Create parent RPC module using interface as stream | ||
super (socketInterface, client); | ||
} | ||
} | ||
exports = module.exports = { ClientIpcRpc, ServerIpcRpcMaster }; | ||
// Export classes | ||
exports = module.exports = { Rpc, ClientSocketRpc, ServerSocketRpcMaster }; |
{ | ||
"name": "arc-rpc", | ||
"version": "1.3.2", | ||
"version": "1.3.3", | ||
"description": "Asynchronous Remote Classes make RPC simple", | ||
"main": "index.js", | ||
"dependencies": { | ||
"node-ipc": "^9.0.1", | ||
"socket.io": "^2.0.3", | ||
"socket.io-client": "^2.0.3", | ||
"tweetnacl": "^1.0.0", | ||
@@ -9,0 +10,0 @@ "uuid": "^3.0.1" |
@@ -16,3 +16,3 @@ # Arc RPC | ||
Here is a basic example of using RPC with `node-ipc` | ||
Here is a basic example of using RPC with `socket.io` | ||
@@ -22,6 +22,6 @@ `Server.js` | ||
```js | ||
// Include library for IPC-RPC clients | ||
let ClientIpcRpc = require("arc-rpc").ClientIpcRpc | ||
// Include library for socket RPC clients | ||
let ClientSocketRpc = require("arc-rpc").ClientSocketRpc | ||
// Define `serverIpc` in higher scope for testing purposes | ||
// Define `serverRpc` in higher scope for testing purposes | ||
let serverRpc = null | ||
@@ -32,3 +32,3 @@ | ||
// Example method | ||
clientTest() { | ||
async clientTest() { | ||
console.log("Remotely called by server, calling server method.") | ||
@@ -44,4 +44,4 @@ | ||
// Create RPC to server, on IPC channel testing, predefined encryption key, with an instance of the example client class | ||
serverRpc = new ClientIpcRpc ("testing", Buffer.from ('flbd+mTz8bIWl2DQxFMKHYAA1+PFxpEKmVNsZpFP5xQ=', 'base64'), new ClientClass()) | ||
// Create RPC to server over socket.io socket, predefined encryption key, with an instance of the example client class | ||
serverRpc = new ClientSocketRpc ("127.0.0.1", 9919, Buffer.from ('flbd+mTz8bIWl2DQxFMKHYAA1+PFxpEKmVNsZpFP5xQ=', 'base64'), new ClientClass()) | ||
``` | ||
@@ -52,4 +52,4 @@ | ||
```js | ||
// Include library for IPC-RPC servers | ||
let ServerIpcRpcMaster = require("arc-rpc").ServerIpcRpcMaster | ||
// Include library for socket RPC servers | ||
let ServerSocketRpcMaster = require("arc-rpc").ServerSocketRpcMaster | ||
@@ -59,3 +59,3 @@ | ||
class ServerClass { | ||
serverTest() { | ||
async serverTest() { | ||
console.log("Remotely called by client.") | ||
@@ -65,7 +65,7 @@ } | ||
// Create RPC master/listener, on IPC channel testing, predefined encryption key, with an instance of the example server class | ||
let rpcMaster = new ServerIpcRpcMaster ("testing", Buffer.from ('flbd+mTz8bIWl2DQxFMKHYAA1+PFxpEKmVNsZpFP5xQ=', 'base64'), new ServerClass()) | ||
// Create RPC master/listener, on socket.io connection, predefined encryption key, with an instance of the example server class | ||
let rpcMaster = new ServerSocketRpcMaster (9919, Buffer.from ('flbd+mTz8bIWl2DQxFMKHYAA1+PFxpEKmVNsZpFP5xQ=', 'base64'), new ServerClass()) | ||
// Listen for new clients | ||
rpcMaster.on("client", (clientRpc) => { | ||
rpcMaster.on("client", async (clientRpc) => { | ||
console.log("Got new client, remotely calling client test.") | ||
@@ -81,8 +81,4 @@ | ||
## Why are you encrypting IPC streams?? | ||
This is made for a specific project, soon the node-ipc connections will be replaced with net sockets, or be replaced with a net library. I know, it's silly this way. | ||
## Basic documentation | ||
I'll get to completing this later |
Sorry, the diff of this file is not supported yet
11481
4
249
76
+ Addedsocket.io@^2.0.3
+ Addedsocket.io-client@^2.0.3
+ Addedaccepts@1.3.8(transitive)
+ Addedafter@0.8.2(transitive)
+ Addedarraybuffer.slice@0.0.7(transitive)
+ Addedbacko2@1.0.2(transitive)
+ Addedbase64-arraybuffer@0.1.4(transitive)
+ Addedbase64id@2.0.0(transitive)
+ Addedblob@0.0.5(transitive)
+ Addedcomponent-bind@1.0.0(transitive)
+ Addedcomponent-emitter@1.2.11.3.1(transitive)
+ Addedcomponent-inherit@0.0.3(transitive)
+ Addedcookie@0.4.2(transitive)
+ Addeddebug@3.1.04.1.1(transitive)
+ Addedengine.io@3.6.2(transitive)
+ Addedengine.io-client@3.5.4(transitive)
+ Addedengine.io-parser@2.2.1(transitive)
+ Addedhas-binary2@1.0.3(transitive)
+ Addedhas-cors@1.1.0(transitive)
+ Addedindexof@0.0.1(transitive)
+ Addedisarray@2.0.1(transitive)
+ Addedmime-db@1.52.0(transitive)
+ Addedmime-types@2.1.35(transitive)
+ Addedms@2.0.02.1.3(transitive)
+ Addednegotiator@0.6.3(transitive)
+ Addedparseqs@0.0.6(transitive)
+ Addedparseuri@0.0.6(transitive)
+ Addedsocket.io@2.5.1(transitive)
+ Addedsocket.io-adapter@1.1.2(transitive)
+ Addedsocket.io-client@2.5.0(transitive)
+ Addedsocket.io-parser@3.3.43.4.3(transitive)
+ Addedto-array@0.1.4(transitive)
+ Addedws@7.5.10(transitive)
+ Addedxmlhttprequest-ssl@1.6.3(transitive)
+ Addedyeast@0.1.2(transitive)
- Removednode-ipc@^9.0.1
- Removedeasy-stack@1.0.1(transitive)
- Removedevent-pubsub@4.3.0(transitive)
- Removedjs-message@1.0.7(transitive)
- Removedjs-queue@2.0.2(transitive)
- Removednode-ipc@9.2.1(transitive)