passage-rpc
v1.1.13
Published
Client and server side JSON-RPC websockets
Downloads
17
Readme
Passage RPC
Client and server side JSON-RPC 2.0 websockets
This is a websocket subprotocol implementation designed for remote procedure calls and server responses.
http://www.jsonrpc.org/specification
Installation from NPM
Install the package.
npm i passage-rpc --save
Import it into your server side script.
const Passage = require('passage-rpc');
Import it into your client side script.
import Passage from 'passage-rpc';
Installation using IIFE
Download and include the library onto your page, a minified version can be found in the /dist
directory.
<script src="/javascripts/passage-rpc.min.js"></script>
Setup
Create a new instance of Passage
providing a uri and set of options.
const options = {
reconnect: true,
};
const passage = new Passage('wss://example.com', options);
passage.on('rpc.open', () => {
console.log('connected!');
});
Options
requestTimeout <default: 6000>
The amount of time the server can take responding to requests before a timeout.
reconnect <default: false>
Whether the client should attempt to reconnect when disconnected.
reconnectTimeout <default: 2000>
The amount of time to wait between reconnection attempts.
reconnectTries <default: 60>
Maximum number of reconnection attempts.
persistent <default: false>
Whether the library should keep trying to reconnect forever. The library will first use the reconnect feature, and if it runs out of tries will use this feature.
persistentTimeout <default: 10 * 60 * 1000>
The amount of time to wait between each persistent reconnection attempt.
Events
When the server sends a notification to your application, it triggers an event. This library uses node's EventEmitter therefore events can be listened for in a common way.
| method | description | params |
| - | - | - |
| rpc.message
| Message was received. | data |
| rpc.open
| Connection established. | |
| rpc.close
| Connection closed. | reconnecting |
| rpc.error
| Error has occurred. | Error |
passage.on('myapp.welcome', (params) => {
console.log(params);
});
On rpc.close
a parameter is passed representing whether the module intends to reconnect. This will only be true if the reconnect
option has been set to true, and the maximum number of reconnectTries
has not been met. The persistent
option will not affect this.
Instance
close (code?: number, reason?: string) => void
Closes the connection using the optional code and reason given.
connect () => void
This will close the connection, then reconnect.
readyState
The ready state of the connection, useful to compare against named ready states available on the constructor.
| name | value | location |
| - | - | - |
| CONNECTING | 0 | Passage.CONNECTING
|
| OPEN | 1 | Passage.OPEN
|
| CLOSING | 2 | Passage.CLOSING
|
| CLOSED | 3 | Passage.CLOSED
|
if (passage.readyState !== Passage.OPEN) {
console.log('Not connected');
}
send (method: string, [params: any], callback?: (error: Error, result?: any) => void, timeout?: number) => void
If a callback is provided, then the server will respond once it has finished processing. It may return an error or a result once completed but not both. Params will be available for consumption on the server. If a timeout is provided it will override the default requestTimeout
from options.
passage.send('myapp.hello');
passage.send('myapp.hello', (error, response) => {
if (error) throw error;
console.log(response);
});
passage.send('myapp.hello', { my: 'params' });
passage.send('myapp.hello', { my: 'params' }, (error, response) => {
if (error) throw error;
console.log(response);
});
Note: If a callback is not provided and the connection is not available this method will throw an error.
Sending more than one request at the same time
JSON-RPC supports sending an array of messages. To do this the library exposes helper methods for you to use. A full example sending multiple messages can be seen below.
const callback = (error, response) => {
console.log(response);
};
const messages = [
passage.buildMessage('myapp.request', callback),
passage.buildMessage('myapp.request', { code: 'the stork swims at midnight' }),
passage.buildMessage('myapp.alert', 'important message')
];
const payload = JSON.stringify(messages);
passage.connection.send(payload);
buildMessage (method: string, [params: any], callback?: (error: Error, result?: any) => void, timeout?: number) => Object
This creates a simple object for consumption by the server. It takes the same values as the send
method, however does not stringify or send the message. If a callback is provided it will timeout, if you use this function you should send your payload soon.
expectResponse (callback: (error: Error, result?: any) => void, timeout?: number) => number
Returns a number representing a message id. The callback will timeout if a response containing the message id is not received in time.
Server setup
The server implementation is built on the npm ws library and shares several similarities with it. There are a few additional options and events utilised for JSON-RPC.
const Passage = require('passage-rpc');
const options = {
port: 8000,
heartrate: 30000,
methods: {
'myapp.hello': () => 'hi';
}
};
const server = new Passage.Server(options);
server.on('rpc.listening', () => {
console.log('Listening on port: ' + port);
});
heartrate <default: 30000>
Periodically each of the connected clients will be pinged terminating ones for which there is no response. Checking every 30 seconds is a good default but you might wish to adjust this.
methods <default: {}>
The methods parameter is a dictionary of procedures your server listens to from the client. In this case if a client sends myapp.hello
the server will run the associated function and respond with "hi"
. You may return an Error
instead, or even nothing at all.
Whether the server responds is dependent on the client. If the client is not waiting for a response the server will not send one. Every method is expected to be in the following format.
(params: any, client: ConnectedClient) => any
Must return a Promise
if you are doing something which is asyncronous.
For example:
const methods = {
'myapp.findUser': async (userId) => {
const user = await findUser(userId);
return { user };
}
};
Errors
When being returned from the server, it should be a Error
instance. If your method is a Promise
it is acceptable to reject. The following attributes are transmitted across the network, message
name
code
and data
. The data
attribute contains any additional information you would like to include but must be stringifiable into JSON.
There are some errors the library may return itself in callbacks.
| name | code | message |
| - | - | - |
| Timeout
| 408 | Timeout |
| ServiceUnavailable
| 503 | Service unavailable |
| ParseError
| -32700 | Parse error |
| InvalidRequest
| -32600 | Invalid request |
| MethodNotFound
| -32601 | Method not found |
Server events
Events on the server are handled differently than on the client in most cases, but there are important ones.
| method | description | params |
| - | - | - |
| rpc.listening
| Server is listening. | |
| rpc.connection
| Connection established. | ConnectedClient, req object |
| rpc.error
| Error has occurred. | Error |
Server instance
close (callback?: () => void) => void
Closes the server then runs the callback.
clients
Set of connected clients.
ConnectedClient instance
The rpc.connection
event offers a connected client instance, and a req
object. The connected client has its own events separate from the server.
| method | description | params |
| - | - | - |
| rpc.message
| Message was received. | data |
| rpc.close
| Connection closed. | |
| rpc.error
| Error has occurred. | Error |
close (code?: number, reason?: string) => void
Closes the connection using the optional code and reason given.
readyState
The ready state of the connection.
send (method: string, [params: any], callback?: (error: Error) => void) => void
Send a notification to the connected client. The server cannot expect a response from the client with the current implementation. The callback is only to let you know that the message was sent successfully.
client.send('myapp.welcome');
client.send('myapp.welcome', { my: 'params' });
client.send('myapp.welcome', (error) => {
if (!error) console.log('Notification sent');
});
client.send('myapp.welcome', { my: 'params' }, (error) => {
if (!error) console.log('Notification sent');
});
Note: If a callback is not provided and the connection is not available this method will throw an error.
Sending more than one notification at the same time
A full example from the server can be seen below.
const messages = [
client.buildMessage('myapp.notify'),
client.buildMessage('myapp.notify', { friends: 'forevah' }),
client.buildMessage('myapp.alert'),
];
const payload = JSON.stringify(messages);
client.connection.send(payload, (error) => {
if (!error) console.log('Notifications sent');
});
buildMessage (method: string, params?: any) => Object
This creates a simple object for consumption by the client. It takes nearly the same values as the send
method, however does not stringify or send the message and does not accept a callback.
Example
Server
const Passage = require('passage-rpc');
const port = 8080;
const methods = {
'myapp.cats.list': async () => {
const cats = await getCats();
return { cats };
}
};
const server = new Passage.Server({ port, methods });
server.on('rpc.listening', () => {
console.log('Server listening on port: ' + port);
});
server.on('rpc.connection', (client) => {
setTimeout(() => {
client.send('myapp.hi', { message: 'Connected 10 seconds ago.' });
}, 10000);
});
Client
const Passage = require('passage-rpc');
const passage = new Passage('ws://localhost:8080');
function processResponse (error, response) {
if (error) throw error;
console.log(`Returned ${response.cats.length} cat(s).`);
}
passage.on('myapp.hi', (params) => {
console.log(params.message);
passage.send('myapp.cats.list', processResponse);
});