websocket-agent
v1.2.1
Published
Server to Server secured comunication based on socket.io websocket
Downloads
2
Readme
WebSocket-Agent
WebSocket-agent is a server to server secured communication tunnel based on socket.io websocket. It allows seamless messaging using Promise semantics and syntax.
install:
javascript npm install websocket-agent --save
Quick example
The module contain both server constructor and client
Server
var AgentServer = require('websocket-agent/server');
var serverInstance = new AgentServer({
protocol: AgentServer.WS,
port: 7788,
secret: "my secure shared secret"
});
serverInstance.onTunnel('someCommand', function (message) {
console.log("Received someCommand!", message);
// return value or promise
return Promise.resolve({message: 'received'});
});
serverInstance.onTunnel({
otherCommand: function (message) {
console.log("Received otherCommand!", message);
throw {code: "MYERRCODE", msg: "My error message"}
}
});
Client
var Agent = require('websocket-agent/client');
// conf can be loaded asynchronously
var conf = Promise.resolve({
secret: "my secure shared secret"
});
Agent
.conf(conf)
.connect('http://localhost:7788')
.on('ready', function () {
console.log("Agent is ready! (connected and verified with master)");
Agent
.tunnel('someCommand', {hello: 'world'}, 10000) // optional timeout in ms
.then(function (response) {
console.log('Server responded to someCommand with:', response) // equals {message: 'received'}
})
.catch(function (err) {
if (err == Agent.TIMEOUT) {
console.error('Tunnel message timeout');
}
else {
console.error('Server returned err', err);
}
});
})
Agent.on('error', function (err) {
console.error("Agent error!", err);
})
Documentation
Usage
The module returns two objects - client and server, though there is no reason to require them both.
*note: the module doesn't use any Promise library and count on the basic Promise global object. If you use and old node/iojs version either use a polyfill or upgrade your engine
var websocket-agent = require('websocket-agent');
// server object
var AgentServer = websocket.AgentServer;
// client object
var Agent = websocket.Agent;
// or require directly
var AgentServer = require('websocket-agent/server');
var Agent = require('websocket-agent/client');
AgentServer (server constructor)
start a server instance that starts to listen immediately
Options:
secret
- {string} secret key to validate incoming messages (required)port
- {number} websocket port. default: ws:80, wss:443protocol
- websocket protocol. default: wswssOptions
- (wss only) {object} additional options to pass to thehttps.createServer
methodprivateKey
- (wss only) {string|Buffer} added to wssOptionscertificate
- (wss only) {string|Buffer} added to wssOptionsagentNameFormat
- {string} agents naming format (defaultA#
) - use#
to indicate an auto incremented digit. multiple#
will be filled with 0.A###
- >A001
debug
- {boolean} be verbose (don't do it to yourself). default: false
var AgentServer = require('websocket-agent/server');
// plain ws
var serverInstance = new AgentServer({
protocol: AgentServer.WS,
port: 7788,
secret: "my secure shared secret"
});
var fs = require('fs')
// secured wss
var serverInstance = new AgentServer({
protocol: AgentServer.WSS,
secret: "my secure shared secret",
port: 7788,
privateKey: fs.readFileSync('key.pem'),
certificate: fs.readFileSync('cert.pem')
});
Connected agent
Every connected agent is represented by an agent object with this format.
the agent is passed to every hook message.
If you want to add data to the object please use agent.data
namespace
*note - if an agent disconnects and reconnects the object will be recreated from scratch
agent = {
name: 'A1',
socket: Object, // reference to the actual socket object
firstTime: true,
data: undefined // reserved namespace for your own use
}
serverInstance.on('agentConnected', function (agent) {
agent.data = {connectedOn: +new Date};
serverInstance
.sendTo(agent, 'myAction', {msg: "Welcome " + agent.name})
.then(function (response) {
})
.catch(function (err) {
})
})
AgentServer#onHook
Respond to an event from an Agent.
- action specific handler
AgentServer#onHook({string} action, {function} handler(message, socket, agent))
- multiple actions
AgentServer#onHook({object} actions) actions = {"action": {function} handler}
- catch all (receives action as first argument)
AgentServer#onHook({function} catchAllHandler(action, message, socket, agent))
The handler follows Promise syntax and sends the eventual return value to the client
Throwing error will stop message processing and will trigger the catch
handler on client side
Call onHook
as many times as you need.
In case of multiple handlers for one command, they will run by declaration order
note: if a generic catch all handler is defined, it will run before any action specific handlers regardless to declaration order
Example:
// catch all syntax
serverInstance.onHook(function (action, message, socket, agent) {
if (action == 'someCommand') {
return Promise.resolve({})
}
})
// same as
serverInstance.onHook('someCommand', function (message, socket, agent) {
return Promise.resolve({})
})
// same as
serverInstance.onHook({
'someCommand': function (message, socket, agent) {
return Promise.resolve({})
}
})
AgentServer#sendTo
Agent.sendTo({object|string} agent, {string} command [, {*} message])
Send command to the given Agent
Example:
serverInstance.on('agentConnected', function (agent) {
serverInstance
.sendTo(agent, 'myAction', {msg: "Welcome " + agent.name})
.then(function (response) {
})
.catch(function (err) {
})
})
// or use agentName (string)
serverInstance
.sendTo('A1', 'myAction', {attached: "payload"})
.then(function (response) {
})
.catch(function (err) {
})
sendTo errors:
- TIMEOUT - message timeout (the Agent may have received the message)
- UNKNOWN_ACTION - the Agent doesn't have any handlers for the given action
- UNKNOWN_AGENT - the Agent doesn't have any handlers for the given action
- AGENT_NOT_CONNECTED - the Agent doesn't have any handlers for the given action
AgentServer#sendAll
Agent.sendAll({string} command [, {*} message])
Send command to all of the connected Agents
Example:
serverInstance
.sendAll('healthCheck', {attached: "payload"})
.then(function (responses) {
// responses = {
// A0001: {}, // response from agent name A0001
// A0002: {}
// }
})
.catch(function (err) {
// at least one failed with
})
sendAll errors:
- All errors from
AgentServer#command
- NO_CONNECTED_AGENTS - message timeout (the Agent may have received the message)
server events
agentConnected
- emitted whenever an agent is connected and verified. agent object is the payloadagentDisconnected
- emitted whenever an agent is disconnected. agent object is the payload
Agent (client constructor)
Create a client instance
Options:
host
- {string} socket.io host to connect to (required).secret
- {string} secret key to validate incoming messages (required)dataStore
- {object} data store for persistent data. (see DataStores section bellow)debug
- {boolean} be verbose (don't do it to yourself). default: false
var Agent = require('websocket-agent/client');
var agent = new Agent({
host: 'http://localhost:7788',
secret: "my secure shared secret",
dataStore: FileStore({file: 'clientStore.json'})
})
agent.on('ready', function () {})
Agent#send
agent.send({string} action [,{object} message [, {number} timeout]])
Send an event request in Promise syntax
Example:
agent
.send('someCommand', {hello: 'world'}, 10000)
.then(function (message) {
// server response message
})
.catch(function (err) {
if (err === Agent.TIMEOUT) {
// request timed out
// if server responds after timeout fired, the response will be ignored
}
else {
// server threw err
}
})
send errors:
- OFFLINE - happens when trying to send tunnel and agent is offline
- TIMEOUT - message timeout (the server may have received the message)
- UNKNOWN_ACTION - the server doesn't have any handlers for the given action
Agent#onHook
Respond to an event from the server.
- action specific handler
Agent#onHook({string} action, {function} handler(message, socket))
- multiple actions
Agent#onHook({object} actions) {"action": {function} handler}
- catch all (receives action as first argument)
Agent#onHook({function} catchAllHandler(action, message, socket))
The handler follows Promise syntax and sends the eventual return value to the client
Throwing error will stop message processing and will trigger the catch
handler on client side
Call on
as many times as you need.
In case of multiple handlers for one command, they will run by declaration order
note: if a generic catch all handler is defined, it will run before any action specific handlers regardless to declaration order
Example:
// catch all syntax
agent.onHook(function (action, message, socket) {
if (action == 'someEventFromServer') {
return Promise.resolve({status: 'Yay!'})
}
})
// same as
agent.onHook('someEventFromServer', function (message, socket) {
return Promise.resolve({status: 'Yay!'})
})
// same as
agent.onHook({
'someEventFromServer': function (message, socket) {
return Promise.resolve({status: 'Yay!'})
}
})
Agent events
ready
- emitted once a connection to the server has been established and a validation message successfully traveled to the server and backconnected
- emitted once when the socket connects to the serverdisconnected
- emitted every time the connection to the server is disconnectedreconnected
- similar to ready event but emitted after a successful reconnection
Error handling
Server:
serverInstance.on('error', function (err) {
console.error(err)
}) ```
General Errors:
- WRONG_SECRET - the provided secret does not match the secret on the server
- DUPLICATE_ID - two agents with the same id tried to connect to the server (only the first one is accepted)
Client:
```javascript
Agent.on('error', function (err) {
switch () {
case Agent.WRONG_SECRET:
default:
case Agent.DUPLICATE_ID:
default:
case Agent.WRONG_SECRET:
default:
console.error(err)
break
}
})
Data Stores
Enable data store to save persistent client names.
data stores must be an object with two methods save
and load
that returns a promise
You can use any backend that you want
here is a simple example for a json file storage:
var fs = require('fs');
var FileStore = function (conf) {
conf = conf || {};
return {
save: function (doc) {
return new Promise(function (resolve, reject) {
fs.writeFile(conf.file || 'fileStore.json', JSON.stringify(doc, null, 4), {flag: 'w+'}, function (err) {
if (err) {
console.error(err);
return reject()
}
resolve()
})
});
},
load: function () {
return new Promise(function (resolve, reject) {
fs.readFile(conf.file || 'fileStore.json', {encoding: 'utf8'}, function (err, content) {
if (err && err.code != 'ENOENT') {
console.error(err);
return reject();
}
resolve(content && JSON.parse(content));
})
});
}
}
};
# Server
var serverInstance = new AgentServer({
port: 7788,
secret: "my secure shared secret",
agentNameFormat: 'A####',
dataStore: FileStore({file: 'serverStore.json'})
});
# Client
var agent = new Agent({
host: 'http://localhost:7788',
secret: "my secure shared secret",
dataStore: FileStore({file: 'clientStore.json'})
});