{"id":19178045,"url":"https://github.com/cmdruid/nostr-p2p","last_synced_at":"2025-04-09T21:51:32.476Z","repository":{"id":261524167,"uuid":"884548157","full_name":"cmdruid/nostr-p2p","owner":"cmdruid","description":"Build your own peer-to-peer messaging protocol, transmitted by relays.","archived":false,"fork":false,"pushed_at":"2025-02-22T21:00:41.000Z","size":231,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-09T21:51:26.716Z","etag":null,"topics":["nostr","p2p"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cmdruid.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}},"created_at":"2024-11-07T00:27:39.000Z","updated_at":"2025-03-25T23:34:25.000Z","dependencies_parsed_at":"2024-11-07T02:31:52.957Z","dependency_job_id":"f742d5be-9835-4c24-8438-6d4f542ba3c5","html_url":"https://github.com/cmdruid/nostr-p2p","commit_stats":null,"previous_names":["cmdruid/nostr-p2p"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmdruid%2Fnostr-p2p","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmdruid%2Fnostr-p2p/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmdruid%2Fnostr-p2p/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmdruid%2Fnostr-p2p/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cmdruid","download_url":"https://codeload.github.com/cmdruid/nostr-p2p/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248119402,"owners_count":21050754,"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":["nostr","p2p"],"created_at":"2024-11-09T10:36:28.089Z","updated_at":"2025-04-09T21:51:32.470Z","avatar_url":"https://github.com/cmdruid.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# nostr-p2p\n\nBuild your own peer-to-peer messaging protocol, transmitted by relays.\n\n## Overview\n\nThis project provides a light-weight client for building your own peer-to-peer messaging protocol on-top of the Nostr relay network. The client handles peering, encryption, message routing, and relay management while providing a simple API for developers.\n\n### Features\n\n* **Simplified Messaging**  \n  Provides a `SignedMessage` object for passing structured messages between peers, with support for direct messages, broadcasts, multicasts, and request-response.\n\n* **Event-Driven Routing**  \n  Includes an event-based inbox for handling incoming messages, with emitters for event type, message ID, peer pubkey, and topic tag.\n\n* **Encryption and Validation**  \n  Messages are end-to-end encrypted (AES-256-GCM) between peers, with strict runtime validation (using Zod).\n\n* **Message Receipts**  \n  Detailed promise-based receipts for asynchronous message delivery and response collection.\n\n* **Reference Node**  \n  Implements a generic `NostrNode` class with manageable relay connections, event filters, and timeouts.\n\n## Installation\n\nThe `nostr-p2p` package can be installed in both node and browser environments. It's available through the npm registry for node projects, and via the unpkg CDN for browser applications.\n\n### Node Environment\n\nFor node environments, simply install the package using your preferred package manager:\n\n```bash\nnpm install @cmdcode/nostr-p2p\n```\n\nThen, import the package in your project:\n\n```ts\nimport { NostrNode } from '@cmdcode/nostr-p2p'\n```\n\n### Browser Environment\n\nYou can include the package directly in your HTML file:\n\n```html\n\u003cscript src=\"https://unpkg.com/@cmdcode/nostr-p2p/dist/script.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  const { NostrNode } = window.nostr_p2p\n\u003c/script\u003e\n```\n\nYou can also import it as a module in modern browsers:\n\n```html\n\u003cscript type=\"module\"\u003e\n  import { NostrNode } from 'https://unpkg.com/@cmdcode/nostr-p2p/dist/module.js'\n\u003c/script\u003e\n```\n\n## Basic Usage\n\nThe core component of this library is the `NostrNode` client, which handles all the complexities of peer-to-peer messaging.\n\nEach node can:\n- Connect to multiple relays for redundancy.\n- Send encrypted messages to specific peers.\n- Broadcast messages to multiple peers.\n- Handle request-response patterns.\n- Subscribe to specific message types or peers.\n- Manage message delivery receipts.\n\n### Creating a Node\n\nCreate a basic Nostr node and connect it to a relay:\n\n```typescript\nimport { NostrNode } from '@cmdcode/nostr-p2p'\n\n// List of relays to connect to.\nconst relays = [\n  'wss://relay.nostrdice.com',\n  'wss://relay.snort.social'\n]\n\n// Create a new Nostr node.\nconst seckey = '...' // Your private key.\nconst node   = new NostrNode(relays, seckey)\n\nnode.on('ready', () =\u003e {\n  // Fires when the node is ready to send and receive messages.\n  console.log('connected to:', node.relays)\n})\n\n// Connect to the relays.\nawait node.connect()\n```\n\nYou can configure the node with additional options:\n\n```ts\ninterface NodeOptions {\n  envelope     ?: Partial\u003cEventConfig\u003e  // Event envelope configuration. \n  filter       ?: Partial\u003cEventFilter\u003e  // Event filter configuration.\n  req_timeout  ?: number                // Request timeout.\n  since_offset ?: number                // Time offset for filtering events.\n  start_delay  ?: number                // Delay before starting to listen for events.\n}\n```\n\n### Message Structure\n\nMessages are structured in a basic format, with a message `id`, a topic `tag`, and a `data` payload:\n\n```ts\ninterface MessageTemplate {\n  data : Json    // The message payload, serialized as JSON.\n  id?  : string  // Identifier for tracking specific messages.\n  tag  : string  // A label to categorize and filter messages.\n}\n```\n\nWhen sending a message, you can specify additional options for the delivery:\n\n```ts\ninterface DeliveryOptions {\n  cache?   : Map\u003cstring, PubResponse\u003e  // Cache for tracking publishing status.\n  kind?    : number                    // The kind of event to publish.\n  tags?    : string[][]                // Additional tags to include in the event.\n  timeout? : number                    // The timeout for the delivery response (if any).\n}\n```\n\nDelivered messages also include the original nostr event, stored as `env` (short for envelope):\n\n```ts\ninterface SignedMessage {\n  data : Json         // The message payload as JSON.\n  env  : SignedEvent  // Original signed nostr event.\n  id   : string       // The message identifier.\n  tag  : string       // The message topic.\n}\n```\n\n### Messaging API\n\nThe `NostrNode` client provides several methods for sending and receiving messages.\n\n#### Direct Messages\n\nThe `publish` method encrypts and sends a message to a single peer:\n\n```ts\nconst res : Promise\u003cPubResponse\u003e = node.publish(\n  message  : MessageTemplate,\n  peer_pk  : string,\n  options? : Partial\u003cDeliveryOptions\u003e\n)\n```\n\nThe `PubResponse` object includes the following properties:\n\n```ts\ninterface PubResponse {\n  acks    : string[]  // List of relays that acknowledged the message.\n  fails   : string[]  // List of relays that failed to respond.\n  ok      : boolean   // True if at least one relay acknowledged.\n  peer_pk : string    // The public key of the recipient peer.\n}\n```\n\n#### Broadcasts\n\nThe `broadcast` method sends a message to multiple peers:\n\n```ts\nconst res : Promise\u003cBroadcastResponse\u003e = node.broadcast(\n  message  : MessageTemplate,\n  peers    : string[],\n  options? : Partial\u003cDeliveryOptions\u003e\n)\n```\n\nAll messages share the same message `id` and `tag`, but each recipient receives their own encrypted copy.\n\nThe `BroadcastResponse` object includes the following properties:\n\n```ts\ninterface BroadcastResponse {\n  cache  : Map\u003cstring, PubResponse\u003e  // Map of each peer pubkey to their PubResponse.\n  ok     : boolean                   // True if all responses were successful.\n  peers  : string[]                  // List of peers that received the message.\n}\n```\n\n#### Request and Response\n\nThe `request` method is useful when you expect a response from the peer:\n\n```ts\nconst res : Promise\u003cReqResponse\u003e = node.request(\n  message  : MessageTemplate,\n  peer_pk  : string,\n  options? : Partial\u003cDeliveryOptions\u003e\n)\n```\n\nThe `ReqResponse` object includes the following properties:\n\n```ts\ninterface ReqResponse {\n  pub : PubResponse  // The PubResponse from publishing to the relays.\n  sub : SubResponse  // The SubResponse from listening for the message id.\n}\n```\n\n#### Multi-Peer Requests\n\nThe `multicast` method is useful when you expect a response from multiple peers:\n\n```ts\nconst res : Promise\u003cMulticastResponse\u003e = node.multicast(\n  message  : MessageTemplate,\n  peers    : string[],\n  options? : Partial\u003cDeliveryOptions\u003e\n)\n```\n\nIt sends a request to multiple peers and collects their responses, returning an array of all received responses within the timeout period.\n\nThe `MulticastResponse` object includes the following properties:\n\n```ts\ninterface MulticastResponse {\n  pub : BroadcastResponse  // The BroadcastResponse from publishing to the relays.\n  sub : SubResponse        // The SubResponse from listening for the message id.\n}\n```\n\n#### Custom Subscriptions\n\nThe `subscribe` method allows you to implement a custom message listener with a timeout:\n\n```ts\ninterface SubFilter {\n  id    ?: string    // The message id to listen for.\n  peers ?: string[]  // The peers to listen for.\n  tag   ?: string    // The message tag to listen for.\n}\n\ninterface SubConfig {\n  threshold ?: number  // The number of responses to collect.\n  timeout   ?: number  // The timeout for the subscription.\n}\n\n// Custom subscription\nconst sub : Promise\u003cSubResponse\u003e = node.subscribe (\n  filter  : EventFilter,\n  options : Partial\u003cSubConfig\u003e\n)\n```\nThis method is useful when you need to implement a custom message listener that is not covered by the other methods.\n\nThe `SubResponse` object includes the following properties:\n\n```ts\ntype ResolveReason = 'complete' | 'timeout' | 'threshold'\n\ninterface SubResponse {\n  authors : string[]         // List of authors that published messages matching the filter.\n  inbox   : SignedMessage[]  // List of messages matching the filter. \n  ok      : boolean          // True if at least one message was found.\n  peers   : string[]         // List of peers that published messages matching the filter.\n  reason  : ResolveReason    // The reason the subscription was resolved.\n}\n```\n\n### Event Handling\n\nThe SDK provides an event inbox system for handling incoming messages. You can listen for messages using various filters:\n\n#### On Message ID\n\nFor tracking messages with a specific ID, such as responses to requests:\n\n```ts\nnode.inbox.id.on('deadbeef', (msg : SignedMessage) =\u003e {\n  console.log('Got message:', msg.data)\n})\n```\n\n#### On Message Peer\n\nFor tracking messages from a specific peer, based on their public key:\n\n```ts\nnode.inbox.peer.on('pubkey123', (msg : SignedMessage) =\u003e {\n  console.log('From peer:', msg.data)\n})\n```\n\n#### On Message Topic\n\nListens for messages with a specific topic, allowing you to handle different types of messages with dedicated handlers.\n\n```ts\nnode.inbox.tag.on('status', (msg : SignedMessage) =\u003e {\n  console.log('Status update:', msg.data)\n})\n```\n\n#### Inbox Events\n\nThe inbox emits several events that you can subscribe to for monitoring message handling:\n\n```ts\n// Listen for published messages.\nnode.inbox.event.on('published', (msg: SignedMessage) =\u003e {\n  console.log('Message published:', msg)\n})\n\n// Listen for broadcast results\nnode.inbox.event.on('broadcast', (res: BroadcastResponse \u0026 MessageIdResponse) =\u003e {\n  console.log('Broadcast complete:', response)\n})\n\n// Listen for settled messages (completed deliveries)\nnode.inbox.event.on('settled', (res: PubResponse \u0026 MessageIdResponse) =\u003e {\n  console.log('Message settled:', response)\n})\n```\n\n#### Node Events\n\nThe node itself emits several events that provide comprehensive monitoring of its operation:\n\n```ts\n// Listen for bounced events\nnode.on('bounced',    (event_id: string, error: string) =\u003e console.log('Message bounced:', event_id, error))\n// Listen for when the node is closed.\nnode.on('closed',     (node: NostrNode)    =\u003e console.log('Node closed:', node))\n// Listen for debug messages\nnode.on('debug',      (info: unknown)      =\u003e console.log('Debug:', info))\n// Listen for errors.\nnode.on('error',      (error: unknown)     =\u003e console.error('Node error:', error))\n// Listen for info messages.\nnode.on('info',       (info: Json)         =\u003e console.log('Info:', info))\n// Listen for received messages.\nnode.on('message',    (msg: SignedMessage) =\u003e console.log('Received message:', msg))\n// Listen for when the node is ready to send and receive messages.\nnode.on('ready',      (node: NostrNode)    =\u003e console.log('Node is ready:', node))\n// Listen for new subscriptions to the relays.\nnode.on('subscribed', (sub_id: string, filter: EventFilter) =\u003e console.log('New subscription:', sub_id, filter))\n```\n\n### Advanced Features\n\nHere are some advanced features that you can use:\n\n#### Event Handling Options\n```ts\n// Time-limited subscriptions\nnode.inbox.tag.within('status', (msg) =\u003e {\n  console.log('Status within 5s:', msg)\n}, 5000)\n```\nThe `within` method creates a temporary subscription that automatically unsubscribes after the specified timeout. This is useful for gathering time-sensitive responses.\n\n```ts\n// One-time handlers\nnode.inbox.id.once('deadbeef', (msg) =\u003e {\n  console.log('First response:', msg)\n})\n```\nUse `once` when you only need to handle the first occurrence of a message. The handler automatically unsubscribes after being triggered once.\n\n### Demo Example\n\nHere is a basic example of how to pass a message between two nodes:\n\n```ts\n// Create the actors.\nconst Alice = new NostrNode(relays, alice_sk)\nconst Bob   = new NostrNode(relays, bob_sk)\n\n// Configure the Alice node.\nAlice.inbox.tag.on('ping', (msg) =\u003e {\n  const res = { id: msg.id, tag: 'pong', data: 'pong!' }\n  Alice.publish(res, msg.env.pubkey)\n})\n\nAlice.on('ready', () =\u003e {\n  console.log('alice connected')\n})\n\n// Configure the Bob node.\nBob.inbox.tag.on('pong', (msg) =\u003e {\n  console.log('received pong message:', msg.data)\n  cleanup()\n})\n\nBob.on('ready', () =\u003e {\n  console.log('bob connected')\n  Bob.publish({ tag: 'ping', data: 'ping!' }, Alice.pubkey)\n})\n```\nYou can see a full example of this in the `test/demo.ts` file, and run the demo yourself with `npm run demo`.\n\n## Development\n\nThe project is built using the `rollup` bundler and `tsx` for live transpilation. The `script/build.sh` script will build the project and copy the necessary files to the `dist` directory.\n\n```bash\n# Install dependencies.\nnpm install\n\n# Run test suite\nnpm test\n\n# Build package to dist directory.\nnpm run build\n```\n\nThe test suite contains a basic implementation of a nostr relay, plus methods for generating a set of nodes. Please refer to the `test/tape.ts` file for more details.\n\n## Roadmap\n\nHere is a list of features that are planned for future releases:\n\n* **Peer Discovery**  \n  Currently, each node must be manually configured with a list of peers. We plan to implement \n  a message channel for automatic peer discovery, including join/leave notifications.\n\n* **Public Profiles**  \n  Enable nodes to leverage public profiles for advertising their presence and sharing metadata \n  with the network.\n\n* **Private Stores**  \n  Implement a secure, local storage system for each node to maintain configuration and state \n  data persistently.\n\n* **Shared Stores**  \n  Develop a distributed data sharing mechanism allowing nodes to maintain and synchronize \n  shared data repositories.\n\n## Resources\n\nThis project uses the following open source libraries:\n\n- [Buff](https://github.com/cmdcode/buff): Swiss-army knife for byte manipulation and encoding.\n- [Noble Curves](https://github.com/paulmillr/noble-curves): A fast, lightweight ECC library.\n- [Noble Ciphers](https://github.com/paulmillr/noble-ciphers): A fast, lightweight encryption library.\n- [Nostr Tools](https://github.com/nostr-tools): Tools for working with the Nostr protocol.\n- [Zod](https://github.com/colinhacks/zod): Run-time data validation library.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcmdruid%2Fnostr-p2p","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcmdruid%2Fnostr-p2p","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcmdruid%2Fnostr-p2p/lists"}