Home

Awesome

nostr-relaypool-ts

A Nostr RelayPool implementation in TypeScript using https://github.com/nbd-wtf/nostr-tools library as a dependency.

Its main goal is to make it simpler to build a client on top of it than just a dumb RelayPool implementation.

It's used by:

(Add yourself here :) )

Features:

Installation:

npm i nostr-relaypool

Installation for use in NodeJS

npm i nostr-relaypool ws

Usage:

import {RelayPool} from "nostr-relaypool";

let relays = [
  "wss://relay.damus.io",
  "wss://nostr.fmt.wiz.biz",
  "wss://nostr.bongbong.com",
];

let relayPool = new RelayPool(relays);

let unsub = relayPool.subscribe(
  [
    {
      authors: [
        "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245",
      ],
    },
    {
      kinds: [0],
      authors: [
        "0000000035450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245",
      ],
      relay: "wss://nostr.sandwich.farm",
    },
  ],
  relays,
  (event, isAfterEose, relayURL) => {
    console.log(event, isAfterEose, relayURL);
  },
  undefined,
  (events, relayURL) => {
    console.log(events, relayURL);
  }
);

relayPool.onerror((err, relayUrl) => {
  console.log("RelayPool error", err, " from relay ", relayUrl);
});
relayPool.onnotice((relayUrl, notice) => {
  console.log("RelayPool notice", notice, " from relay ", relayUrl);
});

Web worker support (not all functionality is supported yet, but feel free to add, it's easy):

import { RelayPoolWorker } from 'nostr-relaypool';

const worker = new Worker(
  new URL('./node_modules/nostr-relaypool/lib/nostr-relaypool.worker.js', document.location.href)
);
const relayPool = new RelayPoolWorker(worker);
<br/>

API documentation:

RelayPool(relays:string[] = [], options:{useEventCache?: boolean, logSubscriptions?: boolean,
          deleteSignatures?: boolean, skipVerification?: boolean,
          autoReconnect?: boolean} = {})

RelayPool constructor connects to the given relays, but it doesn't determine which relays are used for specific subscriptions.

It caches all events and returns filtering id and 0 / 3 kinds with requested pubkeys from cache.

options:

<br/>
 RelayPool::subscribe(filters: Filter & {relay?: string, noCache?: boolean},
                      relays: string[] | undefined,
                      onEvent: (event: Event, isAfterEose: boolean,
                          relayURL: string | undefined) => void,
                      maxDelayms?: number,
                      onEose?: (relayURL, minCreatedAt) => void,
                      options: {
                        allowDuplicateEvents?: boolean,
                        allowOlderEvents?: boolean,
                        logAllEvents?: boolean,
                        unsubscribeOnEose: boolean,
                        defaultRelays?: string[]} = {}
              ) : () => void

Creates a subscription to a list of filters and sends them to a pool of relays if new data is required from those relays.

It's called maxDelay instead of delay, as subscribing with a maxDelay of 100ms and later subscribing with infinity time will reset the timer to 100ms delay.

The first implementation uses matchFilter (O(n^2)) for redistributing events that can be easily optimized if the abstraction is successful.

If it's used, the returned function doesn't do anything. It can't be used together with onEose.

Return value:

Returns a function that stops sending more data with the onEvent callback. When all virtual subscriptions are unsubscribed, an unsubscribe request is sent to all relays.

 RelayPool::sendSubscriptions(onEose?: (events, relayURL) => void) : () => void

Sends subscriptions queued up with delayed subscriptions. It can be used after all subscriptions are requested (with some delay or Infinite delay).

  async getEventById(id: string, relays: string[], maxDelayms: number) : Promise<Event>

Gets one event by event id. Many similar subscriptions should be batched together. It is useful inside a component when many components are rendered.

Other API functions:

RelayPool::publish(event: Event, relays: string[])

RelayPool::onnotice(cb: (url: string, msg: string) => void)

RelayPool::onerror(cb: (url: string, msg: string) => void)

RelayPool::setWriteRelaysForPubKey(pubkey: string, writeRelays: string[])

RelayPool::subscribeReferencedEvents(
    event: Event,
    onEvent: OnEvent,
    maxDelayms?: number,
    onEose?: OnEose,
    options: SubscriptionOptions = {}
  ): () => void

RelayPool::fetchAndCacheMetadata(pubkey: string): Promise<Event>

RelayPool::subscribeReferencedEventsAndPrefetchMetadata(
    event: Event,
    onEvent: OnEvent,
    maxDelayms?: number,
    onEose?: OnEose,
    options: SubscriptionOptions = {}
  ): () => void

RelayPool::setCachedMetadata(pubkey: string, metadata: Event)

Support:

Telegram: @AdamRitter

npub1dcl4zejwr8sg9h6jzl75fy4mj6g8gpdqkfczseca6lef0d5gvzxqvux5ey

DEPRECIATED: Experimental API wrapper for RelayPool

All these are depreciated as they are not too useful abstractions in practice.

This is the first taste of an API wrapper that makes RelayPool easier to use. It's experimental (many methods haven't been tested at all) and subject to change significantly.

The first parameter is OnEvent, last parameter is always maxDelayms, and the middle parameter is limit if it's needed.

An unsubscribe function is returned, although it's not implemented yet.

 RelayPool::subscribeEventObject(filters: Filter & {relay?: string, noCache?: boolean},
                      relays: string[] | undefined,
                      onEventObject: (eventObject: EventObject, isAfterEose: boolean,
                          relayURL: string | undefined) => void,
                      maxDelayms?: number,
                      onEose?: (relayURL, minCreatedAt) => void,
                      options: {allowDuplicateEvents?: boolean, allowOlderEvents?: boolean,
                          logAllEvents?: boolean, unsubscribeOnEose: boolean} = {}
              ) : () => void

const pubkey =
  "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245";
const relays = [
  "wss://relay.damus.io",
  "wss://nostr.fmt.wiz.biz",
  "wss://relay.snort.social",
];
const relayPool = new RelayPool();
const author = new Author(relayPool, relays, pubkey);
author.metaData(console.log, 0);
author.follows(console.log, 0);
author.followers(console.log, 0);
author.subscribe([{kinds: [Kind.Contacts]}], console.log, 0);
author.allEvents(console.log, 5, 0);
author.referenced(console.log, 5, 0);
author.followers(console.log, 50, 0);
author.sentAndRecievedDMs(console.log, 50, 0);
author.text(console.log, 10, 0);

new Author(relayPool: RelayPool, relays: string[], pubkey: string)

Author::metaData(cb: (event: EventObject) => void, maxDelayms: number): () => void

Author::subscribe(filters: Filter[], cb: OnEvent, maxDelayms: number): () => void

Author::followsPubkeys(cb: (pubkeys: string[]) => void, maxDelayms: number): () => void

Author::follows(cb: (authors: Author[]) => void, maxDelayms: number): () => void

Author::allEvents(cb: OnEventObject, limit = 100, maxDelayms: number): () => void

Author::referenced(cb: OnEventObject, limit = 100, maxDelayms: number): () => void

Author::followers(cb: OnEventObject, limit = 100, maxDelayms: number): () => void

Author::sentAndRecievedDMs(cb: OnEventObject, limit = 100, maxDelayms: number): () => void

Author::text(cb: OnEventObject, limit = 100, maxDelayms: number): () => void

collect(onEvents: (events: Event[]) => void): OnEvent  // Keeps events array sorted by created_at