{"id":22489742,"url":"https://github.com/local-first-web/relay","last_synced_at":"2025-08-02T22:31:06.491Z","repository":{"id":38195128,"uuid":"322261544","full_name":"local-first-web/relay","owner":"local-first-web","description":"A tiny relay server that bridges two WebSocket connections, allowing the clients to talk directly to each other. (Formerly known as 🐟 Cevitxe Signal Server.)","archived":false,"fork":false,"pushed_at":"2024-03-20T17:04:37.000Z","size":2741,"stargazers_count":103,"open_issues_count":1,"forks_count":15,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-11-11T19:29:13.787Z","etag":null,"topics":["cevitxe","decentralized-applications","distributed-system","distributed-systems","local-first","peer-to-peer","relay-server","taco"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/local-first-web.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2020-12-17T10:36:19.000Z","updated_at":"2024-11-01T06:55:24.000Z","dependencies_parsed_at":"2024-03-20T18:35:40.465Z","dependency_job_id":"f7a5bce3-cca1-4824-8023-ad502812ebe6","html_url":"https://github.com/local-first-web/relay","commit_stats":{"total_commits":88,"total_committers":4,"mean_commits":22.0,"dds":0.05681818181818177,"last_synced_commit":"c29d3dfead775068a2fb85ac08a6f826f8ca6eba"},"previous_names":[],"tags_count":34,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/local-first-web%2Frelay","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/local-first-web%2Frelay/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/local-first-web%2Frelay/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/local-first-web%2Frelay/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/local-first-web","download_url":"https://codeload.github.com/local-first-web/relay/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228500445,"owners_count":17930069,"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","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":["cevitxe","decentralized-applications","distributed-system","distributed-systems","local-first","peer-to-peer","relay-server","taco"],"created_at":"2024-12-06T17:20:34.377Z","updated_at":"2024-12-06T17:23:00.642Z","avatar_url":"https://github.com/local-first-web.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"\u003cimg src='https://raw.githubusercontent.com/local-first-web/branding/main/svg/relay-h.svg'\nwidth='600' alt=\"@localfirst/relay logo\"/\u003e\n\n`@localfirst/relay` is a tiny service that helps local-first applications connect with peers on\nother devices. It can run in the cloud or on any device with a known address.\n\nDeploy to:\n[Glitch](#deploying-to-glitch) |\n[Heroku](#deploying-to-heroku) |\n[AWS](#deploying-to-aws-elastic-beanstalk) |\n[Google](#deploying-to-google-cloud) |\n[Azure](#deploying-to-azure) |\n[local server](#server)\n\n## Why\n\n\u003cimg src='./images/relay-1.png' width='500' align='center' \u003e\u003c/img\u003e\n\nGetting two end-user devices to communicate with each other over the internet is\n[hard](https://tailscale.com/blog/how-nat-traversal-works/). Most devices don't have stable public\nIP addresses, and they're often behind firewalls that turn away attempts to connect from the\noutside. This is a **connection** problem.\n\nEven within a local network, or in other situations where devices can be reached directly, devices\nthat want to communicate need a way to find each other. This is a problem of **discovery**.\n\n## What\n\nThis little server offers a solution to each of these two problems.\n\n### 1. Discovery\n\nAlice can provide a `documentId` (or several) that she's interested in. (A `documentId`\nis a unique ID for a topic or channel — it could be a GUID, or just a unique string like\n`ambitious-mongoose`.)\n\n[![diagram](./images/relay-introduction.png)](https://raw.githubusercontent.com/local-first-web/relay/master/images/relay-introduction.png)\n\nIf Bob is interested in the same `documentId`, each will receive an `Introduction`\nmessage with the other's peerId. They can then use that information to connect.\n\n### 2. Connection\n\nAlice can request to connect with Bob on a given documentId. If we get matching connection\nrequests from Alice and Bob, we pipe their sockets together.\n\n[![diagram](./images/relay-connection.png)](https://raw.githubusercontent.com/local-first-web/relay/master/images/relay-connection.png)\n\n## How\n\n### Server\n\nFrom this repo, you can run this server as follows:\n\n```bash\npnpm\npnpm start\n```\n\nYou should see something like thsi:\n\n```bash\n\u003e @localfirst/relay@4.0.0 start local-first-web/relay\n\u003e node dist/start.js\n\n🐟 Listening at http://localhost:8080\n```\n\nYou can visit that URL with a web browser to confirm that it's working; you should see something like this:\n\n\u003cimg src='./images/screenshot.png' width='300' align='center' /\u003e\n\n#### Running server from another package\n\nFrom another codebase, you can import the server and run it as follows:\n\n```ts\nimport { Server } from \"@localfirst/relay/Server.js\"\n\nconst DEFAULT_PORT = 8080\nconst port = Number(process.env.PORT) || DEFAULT_PORT\n\nconst server = new Server({ port })\n\nserver.listen()\n```\n\n### Client\n\nThis library includes a lightweight client designed to be used with this server.\n\nThe client keeps track of all peers that the server connects you to, and for each peer it keeps\ntrack of each documentId (aka discoveryKey, aka channel) that you're working with that peer on.\n\n```ts\nimport { Client } from \"@localfirst/relay/Client.js\"\n\nclient = new Client({ peerId: \"alice\", url: \"myrelay.somedomain.com\" })\n  .join(\"ambitious-mongoose\")\n  .on(\"peer-connect\", ({ documentId, peerId, socket }) =\u003e {\n    // `socket` is a WebSocket\n\n    // send a message\n    socket.write(\"Hello! 🎉\")\n\n    // listen for messages\n    socket.addEventListener(\"data\", event =\u003e {\n      const message = event.data\n      console.log(`message from ${peerId} about ${documentId}`, message)\n    })\n  })\n```\n\n## ⚠ Security\n\nThis server makes no security guarantees. Alice and Bob should probably:\n\n1.  **Authenticate** each other, to ensure that \"Alice\" is actually Alice and \"Bob\" is actually Bob.\n2.  **Encrypt** all communications with each other.\n\nThe [@localfirst/auth] library can be used with this relay service. It provides peer-to-peer\nauthentication and end-to-end encryption, and allows you to treat this relay (and the rest of the\nnetwork) as untrusted.\n\n## Server API\n\n\u003e The following documentation might be of interest to anyone working on the @localfirst/relay\n\u003e `Client`, or replacing it with a new client. You don't need to know any of this to interact with\n\u003e this server if you're using the included client.\n\nThis server has two WebSocket endpoints: `/introduction` and `/connection`.\n\nIn the following examples, Alice is the local peer and Bob is a remote peer. We're using `alice` and `bob` as their `peerId`s; in practice, typically these would be GUIDs that uniquely identify their devices.\n\n#### `/introduction/:localPeerId`\n\n- `:localPeerId` is the local peer's unique `peerId`.\n\nAlice connects to this endpoint, e.g. `wss://myrelay.somedomain.com/introduction/alice`.\n\nOnce a WebSocket connection has been made, Alice sends an introduction request containing one or more `documentId`s that she has or is interested in:\n\n```ts\n{\n  type: 'Join',\n  documentIds: ['ambitious-mongoose', 'frivolous-platypus'], // documents Alice has or is interested in\n}\n```\n\nIf Bob is connected to the same server and interested in one or more of the same documents IDs, the\nserver sends Alice an introduction message:\n\n```ts\n{\n  type: 'Introduction',\n  peerId: 'bob', // Bob's peerId\n  documentIds: ['ambitious-mongoose'] // documents we're both interested in\n}\n```\n\nAlice can now use this information to request a connection to this peer via the `connection` endpoint:\n\n#### `/connection/:localPeerId/:remotePeerId/:documentId`\n\nOnce Alice has Bob's `peerId`, she makes a new connection to this endpoint, e.g.\n`wss://myrelay.somedomain.com/connection/alice/bob/ambitious-mongoose`.\n\n- `:localPeerId` is the local peer's unique `peerId`.\n- `:remotePeerId` is the remote peer's unique `peerId`.\n- `:documentId` is the document ID.\n\nIf and when Bob makes a reciprocal connection by connecting to\n`wss://myrelay.somedomain.com/connection/bob/alice/ambitious-mongoose`, the server pipes their\nsockets together and leaves them to talk.\n\nThe client and server don't communicate with each other via the `connection` endpoint; it's purely a\nrelay between two peers.\n\n## Deployment\n\n### Deploying to Glitch\n\nYou can deploy this relay to [Glitch](https://glitch.com) by clicking this button:\n\n[![Remix on Glitch](https://cdn.glitch.com/2703baf2-b643-4da7-ab91-7ee2a2d00b5b%2Fremix-button.svg)](https://glitch.com/edit/#!/import/github/local-first-web/relay)\n\nAlternatively, you can remix the [**local-first-relay**](https://glitch.com/edit/#!/local-first-relay) project.\n\n### Deploying to Heroku\n\nThis server can be deployed to [Heroku](https://heroku.com). By design, it should only ever run with a single dyno. You can deploy it by clicking on this button:\n\n[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)\n\nOr, you can install using the [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) as follows:\n\n```bash\nheroku create\ngit push heroku main\nheroku open\n```\n\n### Deploying to AWS Elastic Beanstalk\n\nInstall using the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv1.html):\n\n```bash\neb init\neb create\neb open\n```\n\n### Deploying to Google Cloud\n\nInstall using the [Google Cloud SDK](https://cloud.google.com/sdk/docs/):\n\n```bash\ngcloud projects create my-local-first-relay --set-as-default\ngcloud app create\ngcloud app deploy\ngcloud app browse\n```\n\n### Deploying to Azure\n\nInstall using the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest):\n\n```bash\naz group create --name my-local-first-relay --location eastus\naz configure --defaults group=my-local-first-relay location=eastus\naz appservice plan create --name my-local-first-relay --sku F1\naz webapp create --name my-local-first-relay --plan my-local-first-relay\naz webapp deployment user set --user-name PEERID --password PASSWORD\naz webapp deployment source config-local-git --name my-local-first-relay\ngit remote add azure https://PEERID@my-local-first-relay.scm.azurewebsites.net/my-local-first-relay.git\ngit push azure main\naz webapp browse --name my-local-first-relay\n```\n\n### AWS Lambda, Azure Functions, Vercel, Serverless, Cloudwatch Workers, etc.\n\nSince true serverless functions are stateless and only spun up on demand, they're not a good fit for\nthis server, which needs to remember information about connected peers and maintain a stable\nwebsocket connection with each one.\n\n## License\n\nMIT\n\n## Prior art\n\nInspired by https://github.com/orionz/discovery-cloud-server\n\nFormerly known as 🐟 Cevitxe Signal Server. (Cevitxe is now [@localfirst/state])\n\n[@localfirst/state]: https://github.com/local-first-web/state\n[@localfirst/auth]: https://github.com/local-first-web/auth\n[@localfirst/relay-client]: ./packages/client/\n[server tests]: ./packages/relay/src/Server.test.ts\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flocal-first-web%2Frelay","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flocal-first-web%2Frelay","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flocal-first-web%2Frelay/lists"}