{"id":16900266,"url":"https://github.com/ilshidur/node-mercure","last_synced_at":"2025-08-31T17:33:42.184Z","repository":{"id":34130329,"uuid":"163519150","full_name":"Ilshidur/node-mercure","owner":"Ilshidur","description":"📳 Mercure Hub \u0026 Publisher implemented in node.js.","archived":false,"fork":false,"pushed_at":"2025-07-17T09:59:58.000Z","size":219,"stargazers_count":34,"open_issues_count":47,"forks_count":6,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-09T10:47:48.520Z","etag":null,"topics":["live-updates","mercure","push","server-sent-events","subscription"],"latest_commit_sha":null,"homepage":"https://mercure.rocks","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Ilshidur.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-12-29T14:56:29.000Z","updated_at":"2025-01-08T10:56:19.000Z","dependencies_parsed_at":"2023-01-15T04:47:25.046Z","dependency_job_id":"8833479a-5b9d-40ab-a367-1f06b4481ea8","html_url":"https://github.com/Ilshidur/node-mercure","commit_stats":{"total_commits":46,"total_committers":6,"mean_commits":7.666666666666667,"dds":0.5652173913043479,"last_synced_commit":"143cf924391b3bc5f39c88aabd539a2ba948c16c"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/Ilshidur/node-mercure","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ilshidur%2Fnode-mercure","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ilshidur%2Fnode-mercure/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ilshidur%2Fnode-mercure/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ilshidur%2Fnode-mercure/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Ilshidur","download_url":"https://codeload.github.com/Ilshidur/node-mercure/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ilshidur%2Fnode-mercure/sbom","scorecard":{"id":65803,"data":{"date":"2025-08-11","repo":{"name":"github.com/Ilshidur/node-mercure","commit":"143cf924391b3bc5f39c88aabd539a2ba948c16c"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Code-Review","score":0,"reason":"Found 1/12 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: GNU General Public License v3.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 19 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"36 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-cph5-m8f7-6c5x","Warn: Project is vulnerable to: GHSA-wf5p-g6vw-rhxx","Warn: Project is vulnerable to: GHSA-jr5f-v2jv-69x6","Warn: Project is vulnerable to: GHSA-qwcr-r2fm-qrc7","Warn: Project is vulnerable to: GHSA-pxg6-pf52-xh8x","Warn: Project is vulnerable to: GHSA-6h5x-7c5m-7cr7","Warn: Project is vulnerable to: GHSA-rv95-896h-c2vc","Warn: Project is vulnerable to: GHSA-qw6h-vgh9-j6wx","Warn: Project is vulnerable to: GHSA-74fj-2j2h-c42q","Warn: Project is vulnerable to: GHSA-pw2r-vq6v-hr8c","Warn: Project is vulnerable to: GHSA-jchw-25xp-jwwc","Warn: Project is vulnerable to: GHSA-cxjh-pqwp-8mfp","Warn: Project is vulnerable to: GHSA-8cf7-32gw-wr33","Warn: Project is vulnerable to: GHSA-hjrf-2m68-5959","Warn: Project is vulnerable to: GHSA-qwph-4952-7xr6","Warn: Project is vulnerable to: GHSA-5rrq-pxf6-6jx5","Warn: Project is vulnerable to: GHSA-8fr3-hfg3-gpgp","Warn: Project is vulnerable to: GHSA-gf8q-jrpm-jvxq","Warn: Project is vulnerable to: GHSA-2r2c-g63r-vccr","Warn: Project is vulnerable to: GHSA-cfm4-qjh2-4765","Warn: Project is vulnerable to: GHSA-x4jg-mjrx-434g","Warn: Project is vulnerable to: GHSA-5h4j-qrvg-9xhw","Warn: Project is vulnerable to: GHSA-9wv6-86v2-598j","Warn: Project is vulnerable to: GHSA-rhx6-c78j-4q9w","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-35q2-47q7-3pc3","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-m6fv-jmcg-4jfg","Warn: Project is vulnerable to: GHSA-cm22-4g7w-348p","Warn: Project is vulnerable to: GHSA-46c4-8wrp-j99v","Warn: Project is vulnerable to: GHSA-9m6j-fcg5-2442","Warn: Project is vulnerable to: GHSA-hh27-ffr2-f2jc","Warn: Project is vulnerable to: GHSA-rqff-837h-mm52","Warn: Project is vulnerable to: GHSA-8v38-pw62-9cw2","Warn: Project is vulnerable to: GHSA-hgjh-723h-mx2j","Warn: Project is vulnerable to: GHSA-jf5r-8hm2-f872"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-15T02:34:48.115Z","repository_id":34130329,"created_at":"2025-08-15T02:34:48.115Z","updated_at":"2025-08-15T02:34:48.115Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270816193,"owners_count":24650752,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-17T02:00:09.016Z","response_time":129,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["live-updates","mercure","push","server-sent-events","subscription"],"created_at":"2024-10-13T17:52:28.255Z","updated_at":"2025-08-17T07:02:29.214Z","avatar_url":"https://github.com/Ilshidur.png","language":"JavaScript","readme":"# mercure\n\n[Mercure](https://github.com/dunglas/mercure) Hub \u0026 Publisher implemented in Node.js.\n\n![stability-beta](https://img.shields.io/badge/stability-beta-green.svg)\n[![Build Status][build-badge]][build-url]\n\n[![npm version][version-badge]][version-url]\n[![Known Vulnerabilities][vulnerabilities-badge]][vulnerabilities-url]\n[![dependency status][dependency-badge]][dependency-url]\n[![devdependency status][devdependency-badge]][devdependency-url]\n[![downloads][downloads-badge]][downloads-url]\n[![Code Climate][maintainability-badge]][maintainability-url]\n\n[![NPM][npm-stats-badge]][npm-stats-url]\n\n*Note: this npm package has been **transfered** for a new project by the [initial owner](https://www.npmjs.com/~francois), which serves a totally different purpose. This new version is an implementation of the [Mercure protocol](https://github.com/dunglas/mercure). 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](https://semver.org).*\n\n## TODOs\n\n* **CORS**\n* Hearthbeat mechanism (https://github.com/dunglas/mercure/pull/53)\n* Docker image (iso with official image)\n* Prometheus metrics exporter:\n  * Subscribers count\n  * Events count / size (in Bytes), per publisher\n  * Publishers IPs\n  * Instances count\n* `hub.on('connect')` listeners\n* Events database\n* Export authorization.js mechanism\n* Discovery helpers\n* Handle `Forwarded` and `X-Forwarded-For` headers ([related issue](https://github.com/dunglas/mercure/issues/114))\n* Provide a Socket.io adapter ([see this thread](https://github.com/socketio/socket.io-adapter))\n* Allow the dev to pass an URL in the `Publisher` contructor\n* `Publisher`: allow the user to specify a JWT key and the claims instead of a JWT\n* `Publisher`: getters like `get host()`, `port`, `protocol`...\n* Increase code quality score\n* JSDoc\n* Logging\n* Unit tests\n* Find a way to clear Redis if the process gets interrupted\n* Benchmarks\n\n## State\n\nThis is a **beta version**. This has not fully been tested in production yet.\n\nThis implementation does not reflect the [latest specification](https://github.com/dunglas/mercure/pull/288) since they got changed. I don't recommend to use this module.\n\n## Requirements\n\n* node.js **\u003e= 11.7.0**\n* Redis (optional)\n\n## Features\n\n* 100% implementation of the protocol\n* Events asymmetric encryption\n* Easy integration to any existing app using `http.Server` or `express`\n* Redis-based clustering support\n* Inventory of all open connections stored in Redis, per node process\n* Kill switch\n\n## Future improvements\n\n* Implement as a lambda function ?\n\n## Installation\n\n```bash\nnpm install mercure --save\n```\n\n## Usage\n\nThis library provides 3 components: a `Hub`, a `Server` and a `Publisher`:\n\n![Classes preview](classes-preview.jpg \"Classes preview\")\n\n### Simple hub\n\n\u003e -\u003e *[Documentation](docs/API.md#hub)*\n\nThe `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.\n\n**Use case:** implanting the hub on an existing `http.Server` app, without the need to handle external publishers (only the app does the publishing).\n\nIt handles:\n\n* the SSE connections\n* the events database\n* the authorization mechanism\n* events related to the Hub activity\n\n```javascript\nconst http = require('http');\nconst { Hub } = require('mercure');\n\nconst server = http.createServer((req, res) =\u003e {\n  res.writeHead(200, { 'Content-Type': 'text/plain' });\n  res.end('200');\n});\n\nconst hub = new Hub(server, {\n  jwtKey: '!UnsecureChangeMe!',\n  path: '/.well-known/mercure',\n});\n\nhub.listen(3000);\n```\n\n### Hub server\n\n\u003e -\u003e *[Documentation](docs/API.md#server)*\n\nThe `Server` is built upon the `Hub` component. It creates a new Express instance and allows external publishers to `POST` an event to the hub.\n\n**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.\n\nIt handles **everything the `Hub` does**, plus:\n\n* a freshly created Express instance, built upon the Hub's `http.Server` (middlewares can be applied to enhance security)\n* external publishers (POST requests)\n\n```javascript\nconst { Server } = require('mercure');\n\nconst server = new Server({\n  jwtKey: '!UnsecureChangeMe!',\n  path: '/.well-known/mercure',\n});\n\nserver.listen(3000);\n```\n\nBecause the Server leverages Express, it is possible to add middlewares in front of the internal Hub middleware:\n\n```javascript\nconst compression = require('compression');\n\nclass SecuredHubServer extends Server {\n  configure() {\n    this.app.use(compression());\n  }\n}\n\nconst server = new SecuredHubServer(...);\n```\n\n### Publisher\n\n\u003e -\u003e *[Documentation](docs/API.md#publisher)*\n\nIt can be created in different ways:\n\n* using an existing `Hub` instance (when the app is meant to be THE ONLY publisher)\n* using an existing `Server` instance (when the app is meant to be a publisher)\n* using configuration: `host`, `port`... (when the publisher and the hub are distant)\n\nIt handles:\n\n* Message publication to the Hub\n* Message encryption *(optional)*\n\n```javascript\nconst { Publisher } = require('mercure');\n\nconst publisher = new Publisher({\n  protocol: 'https', // or 'http', but please don't.\n  host: 'example.com',\n  port: 3000,\n  path: '/.well-known/mercure',\n  jwt: 'PUBLISHER_JWT',\n});\n\n// Payload to send to the subscribers.\nconst data = {\n  '@id': 'http://localhost:3000/books/666.jsonld',\n  hello: 'world',\n};\n\nawait publisher.publish(\n  ['https://example.com:3000/books/666.jsonld'], // Topics.\n  JSON.stringify(data),\n);\n```\n\n## API\n\nAPI docs can be found [in the docs/API.md file](docs/API.md).\n\n## Encrypting the datas\n\nIn 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 =\u003e Hub =\u003e Subscriber flow fully encrypted, it is required that the Publisher sends encrypted data.\n\nTo achieve this, the `Publisher#useEncryption()` method will activate messages encryption. Thus, the Hub will not be able to access your private datas:\n\n```javascript\nconst crypto = require('crypto');\nconst util = require('util');\n\nconst publisher = new Publisher({\n  // ...\n});\n\nconst data = { message: 'TOP SECRET DATAS' };\nconst { privateKey } = await util.promisify(crypto.generateKeyPair)('rsa', {\n  modulusLength: 4096,\n  privateKeyEncoding: {\n    type: 'pkcs8',\n    format: 'pem',\n  },\n});\n\n// Start encrypting the events.\nawait publisher.useEncryption({\n  rsaPrivateKey: privateKey,\n});\n\n// Will send encrypted datas.\nawait publisher.publish(\n  [...], // Topics.\n  JSON.stringify(data),\n);\n```\n\nDecrypting:\n\n```javascript\nconst jose = require('node-jose');\n\nconst encryptedData = 'ENCRYPTED DATA';\nconst decrypted = await jose.JWE.createDecrypt(publisher.keystore).decrypt(encryptedData);\n\nconsole.log(decrypted.plaintext.toString());\n```\n\n## Kill switch\n\nIn case the hub must urgently close all connections (e.g.: in case of compromission of the JWT key), a kill switch is available:\n\n```javascript\nawait hub.killSwitch();\n```\n\nThe new JWT Key will be output to stdout.\n\n## License\n\nGNU GENERAL PUBLIC LICENSE v3.\n\n[build-badge]: https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2FIlshidur%2Fnode-mercure%2Fbadge\u0026style=flat\n[build-url]: https://actions-badge.atrox.dev/Ilshidur/node-mercure/goto\n[version-badge]: https://img.shields.io/npm/v/mercure.svg\n[version-url]: https://www.npmjs.com/package/mercure\n[vulnerabilities-badge]: https://snyk.io/test/npm/mercure/badge.svg\n[vulnerabilities-url]: https://snyk.io/test/npm/mercure\n[dependency-badge]: https://david-dm.org/ilshidur/mercure.svg\n[dependency-url]: https://david-dm.org/ilshidur/mercure\n[devdependency-badge]: https://david-dm.org/ilshidur/mercure/dev-status.svg\n[devdependency-url]: https://david-dm.org/ilshidur/mercure#info=devDependencies\n[downloads-badge]: https://img.shields.io/npm/dt/mercure.svg\n[downloads-url]: https://www.npmjs.com/package/mercure\n[maintainability-badge]: https://api.codeclimate.com/v1/badges/92ad8661f7de98e13f0f/maintainability\n[maintainability-url]: https://codeclimate.com/github/Ilshidur/node-mercure/maintainability\n[npm-stats-badge]: https://nodei.co/npm/mercure.png?downloads=true\u0026downloadRank=true\n[npm-stats-url]: https://nodei.co/npm/mercure\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Filshidur%2Fnode-mercure","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Filshidur%2Fnode-mercure","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Filshidur%2Fnode-mercure/lists"}