Mercure Hub & Publisher implemented in Node.js.
Note: this npm package has been transfered for a new project by the initial owner, which serves a totally different purpose. This new version is an implementation of the Mercure protocol. The previous mercure
package had 1 release (0.0.1
) and served as a file downloader. You can still access it: https://www.npmjs.com/package/mercure/v/0.0.1. Please make sure to lock this version in your package.json
file, as the new versions will begin at 0.0.2
and will keep following the semver versioning.
- CORS
- Hearthbeat mechanism (dunglas/mercure#53)
- Docker image (iso with official image)
- Prometheus metrics exporter:
- Subscribers count
- Events count / size (in Bytes), per publisher
- Publishers IPs
- Instances count
hub.on('connect')
listeners- Events database
- Export authorization.js mechanism
- Discovery helpers
- Handle
Forwarded
andX-Forwarded-For
headers (related issue) - Provide a Socket.io adapter (see this thread)
- Allow the dev to pass an URL in the
Publisher
contructor Publisher
: allow the user to specify a JWT key and the claims instead of a JWTPublisher
: getters likeget host()
,port
,protocol
...- Increase code quality score
- JSDoc
- Logging
- Unit tests
- Find a way to clear Redis if the process gets interrupted
- Benchmarks
This is a beta version. This has not fully been tested in production yet.
This implementation does not reflect the latest specification since they got changed. I don't recommend to use this module.
- node.js >= 11.7.0
- Redis (optional)
- 100% implementation of the protocol
- Events asymmetric encryption
- Easy integration to any existing app using
http.Server
orexpress
- Redis-based clustering support
- Inventory of all open connections stored in Redis, per node process
- Kill switch
- Implement as a lambda function ?
npm install mercure --save
This library provides 3 components: a Hub
, a Server
and a Publisher
:
The Hub
class is the core component that uses a simple http.Server
instance. An existing instance can be provided to the Hub
, thus the Hub will use it instead of creating a new one.
Use case: implanting the hub on an existing http.Server
app, without the need to handle external publishers (only the app does the publishing).
It handles:
- the SSE connections
- the events database
- the authorization mechanism
- events related to the Hub activity
const http = require('http');
const { Hub } = require('mercure');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('200');
});
const hub = new Hub(server, {
jwtKey: '!UnsecureChangeMe!',
path: '/.well-known/mercure',
});
hub.listen(3000);
The Server
is built upon the Hub
component. It creates a new Express instance and allows external publishers to POST
an event to the hub.
Use case: implanting he hub on an new application that is meant to accept external publishers, with no other HTTP server ... or one listening on a different port.
It handles everything the Hub
does, plus:
- a freshly created Express instance, built upon the Hub's
http.Server
(middlewares can be applied to enhance security) - external publishers (POST requests)
const { Server } = require('mercure');
const server = new Server({
jwtKey: '!UnsecureChangeMe!',
path: '/.well-known/mercure',
});
server.listen(3000);
Because the Server leverages Express, it is possible to add middlewares in front of the internal Hub middleware:
const compression = require('compression');
class SecuredHubServer extends Server {
configure() {
this.app.use(compression());
}
}
const server = new SecuredHubServer(...);
It can be created in different ways:
- using an existing
Hub
instance (when the app is meant to be THE ONLY publisher) - using an existing
Server
instance (when the app is meant to be a publisher) - using configuration:
host
,port
... (when the publisher and the hub are distant)
It handles:
- Message publication to the Hub
- Message encryption (optional)
const { Publisher } = require('mercure');
const publisher = new Publisher({
protocol: 'https', // or 'http', but please don't.
host: 'example.com',
port: 3000,
path: '/.well-known/mercure',
jwt: 'PUBLISHER_JWT',
});
// Payload to send to the subscribers.
const data = {
'@id': 'http://localhost:3000/books/666.jsonld',
hello: 'world',
};
await publisher.publish(
['https://example.com:3000/books/666.jsonld'], // Topics.
JSON.stringify(data),
);
API docs can be found in the docs/API.md file.
In certain cases, the Mercure hub can be hosted by a third-party host. You don't really want them to "sniff" all your cleartext messages. To make the Publisher => Hub => Subscriber flow fully encrypted, it is required that the Publisher sends encrypted data.
To achieve this, the Publisher#useEncryption()
method will activate messages encryption. Thus, the Hub will not be able to access your private datas:
const crypto = require('crypto');
const util = require('util');
const publisher = new Publisher({
// ...
});
const data = { message: 'TOP SECRET DATAS' };
const { privateKey } = await util.promisify(crypto.generateKeyPair)('rsa', {
modulusLength: 4096,
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
},
});
// Start encrypting the events.
await publisher.useEncryption({
rsaPrivateKey: privateKey,
});
// Will send encrypted datas.
await publisher.publish(
[...], // Topics.
JSON.stringify(data),
);
Decrypting:
const jose = require('node-jose');
const encryptedData = 'ENCRYPTED DATA';
const decrypted = await jose.JWE.createDecrypt(publisher.keystore).decrypt(encryptedData);
console.log(decrypted.plaintext.toString());
In case the hub must urgently close all connections (e.g.: in case of compromission of the JWT key), a kill switch is available:
await hub.killSwitch();
The new JWT Key will be output to stdout.
GNU GENERAL PUBLIC LICENSE v3.