diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 088d15370..16849ffa9 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,7 +8,3 @@ updates: commit-message: prefix: "fix(deps):" prefix-development: "chore(deps):" -- package-ecosystem: bundler - directory: "/docs" - schedule: - interval: weekly diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 953fb5a0f..3723ab0f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,6 @@ jobs: needs: ci uses: relaycorp/shared-workflows/.github/workflows/nodejs-lib-release.yml@main with: - jekyll_docs: true + api_docs: true secrets: npm_token: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 1be489c96..60eacb675 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,3 @@ build test src/**.js coverage - -docs/.bundle -docs/.jekyll-cache -docs/.sass-cache -docs/_site -docs/vendor diff --git a/.npmignore b/.npmignore index 0fc4ae4ea..cd8c3f177 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,4 @@ src -docs/ -build/docs *.log jest.*.js diff --git a/README.md b/README.md index c745b990c..7f9dac017 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,30 @@ # relaynet-core-js -[![Known Vulnerabilities](https://snyk.io//test/github/relaycorp/relaynet-core-js/badge.svg?targetFile=package.json)](https://snyk.io//test/github/relaycorp/relaynet-core-js?targetFile=package.json) -[![npm](https://img.shields.io/npm/v/@relaycorp/relaynet-core)](https://www.npmjs.com/package/@relaycorp/relaynet-core) +This library implements the core of [Awala](https://awala.network) and is meant to be used by anyone using the network from a Node.js application. [Read the docs online](https://docs.relaycorp.tech/relaynet-core-js/). -JavaScript library for the core of Relaynet. [Read the docs online](https://docs.relaycorp.tech/relaynet-core-js/). +Please note that this documentation is mostly incomplete because the interface exposed by this library is changing rapidly as of this writing. + +## Install + +`@relaycorp/relaynet-core` requires Node.js v12 or newer, and the latest stable release can be installed as follows: + +``` +npm install --save @relaycorp/relaynet-core +``` + +## Specs supported + +This library supports the following Awala specs: + +- [RS-000 (Awala Core)](https://specs.awala.network/RS-000). +- [RS-001 (RAMF v1)](https://specs.awala.network/RS-001). +- [RS-002 (Awala PKI)](https://specs.awala.network/RS-002). +- [RS-003 (Awala Channel Session Protocol)](https://specs.awala.network/RS-003). +- [RS-018 (Awala Cryptographic Algorithms, Version 1)](https://specs.awala.network/RS-018). In addition to the required algorithms, the following are also supported: + - Hashing functions: SHA-384 and SHA-512. + - Ciphers: AES-192 and AES-256. + - ECDH curves: P-384 and P-521. + +## Updates + +Releases are automatically published on GitHub and NPM, and the [changelog can be found on GitHub](https://github.com/relaycorp/relaynet-core-js/releases). This project uses [semantic versioning](https://semver.org/). diff --git a/docs/Gemfile b/docs/Gemfile deleted file mode 100644 index b8ed08566..000000000 --- a/docs/Gemfile +++ /dev/null @@ -1,4 +0,0 @@ -source "https://rubygems.org" -ruby RUBY_VERSION - -gem "github-pages", group: :jekyll_plugins diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock deleted file mode 100644 index f28bb40fc..000000000 --- a/docs/Gemfile.lock +++ /dev/null @@ -1,285 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - activesupport (6.0.4.4) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) - coffee-script (2.4.1) - coffee-script-source - execjs - coffee-script-source (1.11.1) - colorator (1.1.0) - commonmarker (0.17.13) - ruby-enum (~> 0.5) - concurrent-ruby (1.1.9) - dnsruby (1.61.7) - simpleidn (~> 0.1) - em-websocket (0.5.3) - eventmachine (>= 0.12.9) - http_parser.rb (~> 0) - ethon (0.15.0) - ffi (>= 1.15.0) - eventmachine (1.2.7) - execjs (2.8.1) - faraday (1.8.0) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0.1) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.1) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - multipart-post (>= 1.2, < 3) - ruby2_keywords (>= 0.0.4) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) - faraday-excon (1.1.0) - faraday-httpclient (1.0.1) - faraday-net_http (1.0.1) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - ffi (1.15.4) - forwardable-extended (2.6.0) - gemoji (3.0.1) - github-pages (223) - github-pages-health-check (= 1.17.9) - jekyll (= 3.9.0) - jekyll-avatar (= 0.7.0) - jekyll-coffeescript (= 1.1.1) - jekyll-commonmark-ghpages (= 0.1.6) - jekyll-default-layout (= 0.1.4) - jekyll-feed (= 0.15.1) - jekyll-gist (= 1.5.0) - jekyll-github-metadata (= 2.13.0) - jekyll-include-cache (= 0.2.1) - jekyll-mentions (= 1.6.0) - jekyll-optional-front-matter (= 0.3.2) - jekyll-paginate (= 1.1.0) - jekyll-readme-index (= 0.3.0) - jekyll-redirect-from (= 0.16.0) - jekyll-relative-links (= 0.6.1) - jekyll-remote-theme (= 0.4.3) - jekyll-sass-converter (= 1.5.2) - jekyll-seo-tag (= 2.7.1) - jekyll-sitemap (= 1.4.0) - jekyll-swiss (= 1.0.0) - jekyll-theme-architect (= 0.2.0) - jekyll-theme-cayman (= 0.2.0) - jekyll-theme-dinky (= 0.2.0) - jekyll-theme-hacker (= 0.2.0) - jekyll-theme-leap-day (= 0.2.0) - jekyll-theme-merlot (= 0.2.0) - jekyll-theme-midnight (= 0.2.0) - jekyll-theme-minimal (= 0.2.0) - jekyll-theme-modernist (= 0.2.0) - jekyll-theme-primer (= 0.6.0) - jekyll-theme-slate (= 0.2.0) - jekyll-theme-tactile (= 0.2.0) - jekyll-theme-time-machine (= 0.2.0) - jekyll-titles-from-headings (= 0.5.3) - jemoji (= 0.12.0) - kramdown (= 2.3.1) - kramdown-parser-gfm (= 1.1.0) - liquid (= 4.0.3) - mercenary (~> 0.3) - minima (= 2.5.1) - nokogiri (>= 1.12.5, < 2.0) - rouge (= 3.26.0) - terminal-table (~> 1.4) - github-pages-health-check (1.17.9) - addressable (~> 2.3) - dnsruby (~> 1.60) - octokit (~> 4.0) - public_suffix (>= 3.0, < 5.0) - typhoeus (~> 1.3) - html-pipeline (2.14.0) - activesupport (>= 2) - nokogiri (>= 1.4) - http_parser.rb (0.8.0) - i18n (0.9.5) - concurrent-ruby (~> 1.0) - jekyll (3.9.0) - addressable (~> 2.4) - colorator (~> 1.0) - em-websocket (~> 0.5) - i18n (~> 0.7) - jekyll-sass-converter (~> 1.0) - jekyll-watch (~> 2.0) - kramdown (>= 1.17, < 3) - liquid (~> 4.0) - mercenary (~> 0.3.3) - pathutil (~> 0.9) - rouge (>= 1.7, < 4) - safe_yaml (~> 1.0) - jekyll-avatar (0.7.0) - jekyll (>= 3.0, < 5.0) - jekyll-coffeescript (1.1.1) - coffee-script (~> 2.2) - coffee-script-source (~> 1.11.1) - jekyll-commonmark (1.3.1) - commonmarker (~> 0.14) - jekyll (>= 3.7, < 5.0) - jekyll-commonmark-ghpages (0.1.6) - commonmarker (~> 0.17.6) - jekyll-commonmark (~> 1.2) - rouge (>= 2.0, < 4.0) - jekyll-default-layout (0.1.4) - jekyll (~> 3.0) - jekyll-feed (0.15.1) - jekyll (>= 3.7, < 5.0) - jekyll-gist (1.5.0) - octokit (~> 4.2) - jekyll-github-metadata (2.13.0) - jekyll (>= 3.4, < 5.0) - octokit (~> 4.0, != 4.4.0) - jekyll-include-cache (0.2.1) - jekyll (>= 3.7, < 5.0) - jekyll-mentions (1.6.0) - html-pipeline (~> 2.3) - jekyll (>= 3.7, < 5.0) - jekyll-optional-front-matter (0.3.2) - jekyll (>= 3.0, < 5.0) - jekyll-paginate (1.1.0) - jekyll-readme-index (0.3.0) - jekyll (>= 3.0, < 5.0) - jekyll-redirect-from (0.16.0) - jekyll (>= 3.3, < 5.0) - jekyll-relative-links (0.6.1) - jekyll (>= 3.3, < 5.0) - jekyll-remote-theme (0.4.3) - addressable (~> 2.0) - jekyll (>= 3.5, < 5.0) - jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) - rubyzip (>= 1.3.0, < 3.0) - jekyll-sass-converter (1.5.2) - sass (~> 3.4) - jekyll-seo-tag (2.7.1) - jekyll (>= 3.8, < 5.0) - jekyll-sitemap (1.4.0) - jekyll (>= 3.7, < 5.0) - jekyll-swiss (1.0.0) - jekyll-theme-architect (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-cayman (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-dinky (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-hacker (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-leap-day (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-merlot (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-midnight (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-minimal (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-modernist (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-primer (0.6.0) - jekyll (> 3.5, < 5.0) - jekyll-github-metadata (~> 2.9) - jekyll-seo-tag (~> 2.0) - jekyll-theme-slate (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-tactile (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-time-machine (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-titles-from-headings (0.5.3) - jekyll (>= 3.3, < 5.0) - jekyll-watch (2.2.1) - listen (~> 3.0) - jemoji (0.12.0) - gemoji (~> 3.0) - html-pipeline (~> 2.2) - jekyll (>= 3.0, < 5.0) - kramdown (2.3.1) - rexml - kramdown-parser-gfm (1.1.0) - kramdown (~> 2.0) - liquid (4.0.3) - listen (3.7.0) - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) - mercenary (0.3.6) - mini_portile2 (2.6.1) - minima (2.5.1) - jekyll (>= 3.5, < 5.0) - jekyll-feed (~> 0.9) - jekyll-seo-tag (~> 2.1) - minitest (5.15.0) - multipart-post (2.1.1) - nokogiri (1.12.5) - mini_portile2 (~> 2.6.1) - racc (~> 1.4) - octokit (4.21.0) - faraday (>= 0.9) - sawyer (~> 0.8.0, >= 0.5.3) - pathutil (0.16.2) - forwardable-extended (~> 2.6) - public_suffix (4.0.6) - racc (1.6.0) - rb-fsevent (0.11.0) - rb-inotify (0.10.1) - ffi (~> 1.0) - rexml (3.2.5) - rouge (3.26.0) - ruby-enum (0.9.0) - i18n - ruby2_keywords (0.0.5) - rubyzip (2.3.2) - safe_yaml (1.0.5) - sass (3.7.4) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - sawyer (0.8.2) - addressable (>= 2.3.5) - faraday (> 0.8, < 2.0) - simpleidn (0.2.1) - unf (~> 0.1.4) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - thread_safe (0.3.6) - typhoeus (1.4.0) - ethon (>= 0.9.0) - tzinfo (1.2.9) - thread_safe (~> 0.1) - unf (0.1.4) - unf_ext - unf_ext (0.0.8) - unicode-display_width (1.8.0) - zeitwerk (2.5.2) - -PLATFORMS - ruby - -DEPENDENCIES - github-pages - -RUBY VERSION - ruby 2.5.5p157 - -BUNDLED WITH - 2.1.4 diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index 7337bcf33..000000000 --- a/docs/_config.yml +++ /dev/null @@ -1,14 +0,0 @@ -title: Relaynet SDK - -markdown: kramdown - -baseurl: /relaynet-core-js -destination: ../build/docs -remote_theme: pmarsceill/just-the-docs@v0.2.8 - -search_enabled: false - -exclude: - - Gemfile - - Gemfile.lock - - vendor diff --git a/docs/assets/diagrams/protocol-layers.svg b/docs/assets/diagrams/protocol-layers.svg deleted file mode 100644 index 92f2bc24c..000000000 --- a/docs/assets/diagrams/protocol-layers.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/howto-binding.md b/docs/howto-binding.md deleted file mode 100644 index 298b5e08d..000000000 --- a/docs/howto-binding.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Implementing a binding -nav_order: 2 ---- -# Implementing a binding - -If you're implementing a binding, the only runtime dependency you may have on this library may be the [RelaynetError](./api/classes/relayneterror.html) class, which you may want to extend for consistency with this library and to be able to use [VError](https://www.npmjs.com/package/verror). - -You may also use other elements exposed by this library in your tests -- Mostly likely [`Parcel`](./api/classes/parcel.html), [`generateRSAKeyPair`](./api/globals.html#generatersakeypair) and[ `issueNodeCertificate`](./api/globals.html#issuenodecertificate). - -Everything else is likely to be unique to the binding you're implementing. - -If you want to see what a binding implementation in Node.js looks like, have a look at [@relaycorp/relaynet-pohttp](https://github.com/relaycorp/relaynet-pohttp-js). diff --git a/docs/howto-courier.md b/docs/howto-courier.md deleted file mode 100644 index 4de3452e7..000000000 --- a/docs/howto-courier.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Implementing a courier -nav_order: 7 ---- -# Implementing a courier - -TODO: Write up after implementing https://github.com/relaycorp/relaynet-courier so we can reuse code snippets diff --git a/docs/howto-gateway.md b/docs/howto-gateway.md deleted file mode 100644 index c86451f32..000000000 --- a/docs/howto-gateway.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Implementing a gateway -nav_order: 8 ---- -# Implementing a gateway - -TODO: Write up after implementing https://github.com/relaycorp/relaynet-gateway-desktop and/or https://github.com/relaycorp/relaynet-internet-gateway so we can reuse code snippets diff --git a/docs/howto-service.md b/docs/howto-service.md deleted file mode 100644 index 73c77475e..000000000 --- a/docs/howto-service.md +++ /dev/null @@ -1,135 +0,0 @@ ---- -title: Implementing a service -nav_order: 1 ---- - -# Implementing a service - -A Relaynet service is a collection of applications that communicate amongst themselves in a centralized or decentralized manner. As a Relaynet app developer, you can use this library to send and receive messages serialized in any format (e.g., JSON, ProtocolBuffers) without being concerned about the underlying transport method(s) (e.g., sneakernet, Tor). - -If you're unfamiliar with the terminology used in Relaynet, you should start by reading the introduction and the concepts sections from the [core specification](https://specs.relaynet.link/RS-000) to get a high-level understanding of the technology. - -## Generating endpoint key pair - -Before you're able to send or receive parcels, you have to initialize your endpoint by generating an RSA key pair with [`generateRSAKeyPair()`](./api/globals.html#generatersakeypair) and getting the public key certified: - -```javascript -import { - generateRSAKeyPair, - issueNodeCertificate, -} from '@relaycorp/relaynet-core'; - -async function initEndpoint() { - const { privateKey, publicKey } = await generateRSAKeyPair(); - yourFunctionToSecurelyPersistPrivateKey(privateKey); - - // Self-signing certificate here, but it can/should be issued by the gateway - const certificate = await issueNodeCertificate({ - issuerPrivateKey: privateKey, - subjectPublicKey: publicKey, - }); - yourFunctionToShareCertificate(certificate); -} -``` - -The private address of your endpoint will be derived from its public key, so depending on the nature of your service you may want to repeat the process above with each endpoint that you communicate with in order to avoid [fingerprinting](https://en.wikipedia.org/wiki/Device_fingerprint). - -## Exchanging messages - -As shown in the diagram below, each message between two applications is encapsulated as a parcel, so sending and receiving messages involves serializing and deserializing parcels: - -![](assets/diagrams/protocol-layers.svg) - -For example, consider a centralized service whose applications exchange JSON messages such as the one below: - -```json -{ - "message": "Hello world" -} -``` - -That message could be serialized as a parcel as follows: - -```javascript -import { Parcel, ServiceMessage } from '@relaycorp/relaynet-core'; - -async function serializeMessage() { - const serviceMessage = new ServiceMessage( - 'application/vdn+acme.message+json', - Buffer.from(JSON.stringify({ message: 'Hello world' })), - ); - const parcel = new Parcel( - 'rne+https://acme.com', - senderCertificate, - serviceMessage.serialize(), - ); - - return await parcel.serialize(senderPrivateKey, recipientCertificate); -} -``` - -On the other hand, incoming messages could be deserialized (plus verified and decrypted) as follows: - -```javascript -import { Parcel } from '@relaycorp/relaynet-core'; - -async function deserializeParcel(parcelSerialized) { - const parcel = await Parcel.deserialize(parcelSerialized); - // At this point the sender's signature has been verified - console.log('Received parcel from', parcel.senderCertificate.getCommonName()); - if (parcel.recipient !== 'rne+https://acme.com') { - throw new Error('Invalid recipient'); - } - return await parcel.unwrapMessage(recipientPrivateKey); -} -``` - -### With the Channel Session Protocol - -Where possible, you should use the [channel session protocol](https://specs.relaynet.link/RS-003) to exchange messages because that adds perfect forward secrecy, future secrecy and replay attack mitigation. This library hides nearly all the technical details of the protocol, but you're still responsible for securely persisting and retrieving the ephemeral keys until they expire. - -This protocol requires the recipient to generate an ephemeral key pair and share the public component before communication begins, in addition to the longer term key already generated above; e.g.: - -```javascript -import { generateECDHKeyPair } from '@relaycorp/relaynet-core'; - -async function generateInitialKeyPair() { - const { privateKey, publicKey } = await generateECDHKeyPair(); - yourFunctionToSecurelyPersistSessionPrivateKey(privateKey); -} -``` - -Then the examples above to serialize and deserialize parcels can be rewritten as follows: - -```javascript -import { Parcel, ServiceMessage } from '@relaycorp/relaynet-core'; - -async function serializeMessage() { - const serviceMessage = new ServiceMessage( - 'application/vdn+acme.message+json', - Buffer.from(JSON.stringify({ message: 'Hello world' })), - ); - const parcel = new Parcel( - 'rne+https://acme.com', - senderCertificate, - serviceMessage.serialize(), - ); - - const { serialization, dhPrivateKey } = await parcel.serializeWithSession( - senderPrivateKey, - recipientPublicKey, - ); - yourFunctionToSecurelyPersistSessionPrivateKey(dhPrivateKey); - return serialization; -} - -async function deserializeParcel(parcelSerialized) { - const parcel = await Parcel.deserialize(parcelSerialized); - // At this point the sender's signature has been verified - console.log('Received parcel from', parcel.senderCertificate.getCommonName()); - if (parcel.recipient !== 'rne+https://acme.com') { - throw new Error('Invalid recipient'); - } - return await parcel.unwrapMessageWithSession(recipientDhPrivateKey); -} -``` diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 1fd9d4ea6..000000000 --- a/docs/index.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -layout: page -title: Relaynet SDK ---- -# Relaynet SDK - -This library implements the core of [Relaynet](https://relaynet.link/) and is meant to be used by anyone using the network from a Node.js application. - -Please note that this documentation is mostly incomplete because the interface exposed by this library is changing rapidly as of this writing. Also note that the examples in this documentation won't work until a gateway (e.g., [the desktop one](https://github.com/relaycorp/relaynet-gateway-desktop)) has been implemented. We expect the library to reach a stable status and its documentation to be completed by the end of Q3 2020. - -## Install - -`@relaycorp/relaynet-core` requires Node.js v10 or newer, and the latest stable release can be installed as follows: - -``` -npm install --save @relaycorp/relaynet-core -``` - -Development releases use the `dev` tag (`@relaycorp/relaynet-core@dev`). - -## Use - -This library can be used for different purposes, so please refer to the documentation for your specific use case: - -Most people will be interested in [adding Relaynet support to their app](howto-service.md), whether the app is pre-existing or is being built from scratch. - -Relaycorp provides implementations for gateways, couriers and bindings, so if you're contributing to those implementations or for whatever reason you'd like to build your own, please refer to the follow documents: - -- [Implementing a binding](howto-binding.md). -- [Implementing a gateway](howto-gateway.md). -- [Implementing a courier](howto-courier.md). - -TypeScript type declarations are included with this library. [Read API documentation](./api). - -## Specs supported - -This library supports the following Relaynet specs: - -- [RS-000 (Relaynet Core)](https://specs.relaynet.link/RS-000). -- [RS-001 (RAMF v1)](https://specs.relaynet.link/RS-001). -- [RS-002 (Relaynet PKI)](https://specs.relaynet.link/RS-002). -- [RS-003 (Relaynet Channel Session Protocol)](https://specs.relaynet.link/RS-003). -- [RS-018 (Relaynet Cryptographic Algorithms, Version 1)](https://specs.relaynet.link/RS-018). In addition to the required algorithms, the following are also supported: - - Hashing functions: SHA-384 and SHA-512. - - Ciphers: AES-192 and AES-256. - - ECDH curves: P-384 and P-521. - -## Support - -If you have any questions or comments, you can [find us on Gitter](https://gitter.im/relaynet/community) or [create an issue on the GitHub project](https://github.com/relaycorp/relaynet-core-js/issues/new/choose). - -## Updates - -Releases are automatically published on GitHub and NPM, and the [changelog can be found on GitHub](https://github.com/relaycorp/relaynet-core-js/releases). This project uses [semantic versioning](https://semver.org/).