Paul Revere is a lightweight wrapper for server-client WebSocket communication. It uses schemapack to encode buffers from JavaScript objects and delivers a smaller payload and faster parsing than traditional JSON messaging. On the browser, it wraps a vanilla WebSocket object, and on the server it wraps the ws package.
Paul Revere also accepts pub/sub adapters to allows for horizontal server scaling.
Paul Revere can be used with any Node server, though this example shows Express.
const express = require('express'),
PaulRevere = require('paul-revere'),
schemas = require('../shared/schemas'); // same module as client
/**
* Example schemas.js
* Follow schema rules found at https://github.com/phretaddin/schemapack
exports.chat = {
payload: {
message: 'string',
user: 'string'
},
meta: {
timestamp: 'string'
}
};
*/
const app = express();
// Get Node server instance
const server = app.listen(3000, () => app.log.info('Paul Revere App listening on port 3000'));
// Pass your schemas and a Node server to start a Paul Revere WebSocket server
const paul = new PaulRevere(schemas, {server});
// Bind a client connection handler
paul.onConnection(client => {
// The server exposes your schemas to broadcast messages to all clients
// Pass a client as the second argument to exclude it from the broadcast
paul.chat.broadcast({
payload: {
message: 'New user joined!',
user: 'ChatBot'
},
meta: {
timestamp: String(Date.now())
}
}, client); // Don't send this message to the client that just joined
// Each client has its own schema instance to send messages directly to that client...
client.chat.send({
payload: {
message: 'Welcome to Chat!',
user: 'ChatBot'
},
meta: {
timestamp: String(Date.now())
}
});
// And also listen for messages from that client
client.chat.onMessage(message => {
// Broadcasting the message to all clients simplifies front end rendering and listeners
// and is fast enough for non-optimistic updates
paul.chat.broadcast(message);
});
});
Paul Revere is supported on all browsers that support native WebSockets. http://caniuse.com/#feat=websockets
import PaulRevere from 'paul-revere';
import schemas from '../shared/schemas'; // same module as server
// Pass your schemas and a WebSocket address to connect to a Paul Revere server
const paul = new PaulRevere(schemas, {url: 'ws://localhost:3000'}),
// The client exposes your schemas to send messages to the server
paul.chat.send({
payload: {
message: 'Hello!',
user: 'ClientBot'
},
meta: {
timestamp: String(Date.now())
}
});
// And also listen to messages from the server
paul.chat.onMessage(m => {
console.log(m);
});
In order to support horizontal server scaling, Paul Revere servers internally run off a pub/sub model for broadcasting messages. The default is nothing more than a stub function firing callbacks (see below). Custom adapters can be written and passed as the option pubSub
when instantiating a server, and Paul Revere will use that instead. There is an official NATS adapter at paul-revere-nats-adapter.
Paul Revere servers need publicly accessible publish(subject, msg, exclude)
and subscribe(subject, cb)
methods. Nothing else is required to be exposed.
const pubSubAdapter = {
// subject will always be an integer unique to a schema type, msg will always be a plain object, and exclude may be undefined or a string id
publish(subject, msg, exclude) {
// listeners are namespaced with 'paulrevere' just to ensure string keys
// Your subject can be anything, so long as it is unique for every schema
if(!this.listeners[`paulrevere.${subject}`]) return;
this.listeners[`paulrevere.${subject}`].forEach(cb => cb(msg, exclude));
},
// subscriber callbacks expect a plain object msg, and also an exclude string id if passed in the schema.broadcast() method
subscribe(subject, cb) {
if(!this.listeners[`paulrevere.${subject}`]) this.listeners[`paulrevere.${subject}`] = [];
this.listeners[`paulrevere.${subject}`].push(cb);
},
listeners: {}
};
const paul = new PaulRevere(schemas, {server, pubSub: pubSubAdapter});