TS-Roots-WS - Nostr WebSocket Transport for TypeScript

Source: https://git.wisehodl.dev/jay/ts-roots-ws

Mirror: https://github.com/wisehodl/ts-roots-ws

What this library does

ts-roots-ws is a consensus-layer Nostr protocol websocket transport library for TypeScript. It only provides primitives for working with Nostr protocol websocket connection states and messages:

  • WebSocket Connection States
  • Envelope Structure
  • Message Validation
  • Protocol Message Creation
  • Protocol Message Parsing
  • Standard Label Handling

What this library does not do

ts-roots-ws serves as a foundation for other libraries and applications to implement higher level transport abstractions on top of it, including:

  • Connection Management
  • Event Loops
  • Subscription Handling
  • State Management
  • Reconnection Logic

Installation

  1. Add ts-roots-ws to your project:
npm install @wisehodl/roots-ws
  1. Import the packages:
import {
  encloseEvent,
  encloseSubscriptionEvent,
  findEvent,
  findSubscriptionEvent,
  getLabel,
  isStandardLabel
} from "@wisehodl/roots-ws/envelope";

import { ConnectionStatus } from "@wisehodl/roots-ws";
import { InvalidJSONError, WrongEnvelopeLabelError } from "@wisehodl/roots-ws/errors";
  1. Access functions with appropriate namespaces.

Usage Examples

Envelope Creation

Create EVENT envelope

// Create an event using ts-roots
import { Event } from "@wisehodl/roots/events";

const event: Event = {
  id: "abc123",
  pubkey: "def456",
  kind: 1,
  content: "Hello Nostr!",
  created_at: Math.floor(Date.now() / 1000),
  tags: [],
  sig: ""
};

// Convert to JSON
const eventJSON = JSON.stringify(event);

// Create envelope
const env = encloseEvent(eventJSON);
// Result: ["EVENT",{"id":"abc123","pubkey":"def456","kind":1,"content":"Hello Nostr!","created_at":1636394097}]

Create subscription EVENT envelope

// Create an event using ts-roots
import { Event } from "@wisehodl/roots/events";

const event: Event = {
  id: "abc123",
  pubkey: "def456",
  kind: 1,
  content: "Hello Nostr!",
  created_at: Math.floor(Date.now() / 1000),
  tags: [],
  sig: ""
};

// Convert to JSON
const eventJSON = JSON.stringify(event);

// Create envelope with subscription ID
const subID = "sub1";
const env = encloseSubscriptionEvent(subID, eventJSON);
// Result: ["EVENT","sub1",{"id":"abc123","pubkey":"def456","kind":1,"content":"Hello Nostr!","created_at":1636394097}]

Create REQ envelope

// Create filters using ts-roots
import { Filter } from "@wisehodl/roots/filters";

const since = Math.floor(Date.now() / 1000) - (24 * 60 * 60);
const limit = 50;

const filter1: Filter = {
  kinds: [1],
  limit: limit,
  since: since
};

const filter2: Filter = {
  authors: ["def456"]
};

// Convert to JSON
const filter1JSON = JSON.stringify(filter1);
const filter2JSON = JSON.stringify(filter2);

// Create envelope
const subID = "sub1";
const filtersJSON = [filter1JSON, filter2JSON];
const env = encloseReq(subID, filtersJSON);
// Result: ["REQ","sub1",{"kinds":[1],"limit":50,"since":1636307697},{"authors":["def456"]}]

Create other envelope types

// Create CLOSE envelope
const env1 = encloseClose("sub1");
// Result: ["CLOSE","sub1"]

// Create EOSE envelope
const env2 = encloseEOSE("sub1");
// Result: ["EOSE","sub1"]

// Create NOTICE envelope
const env3 = encloseNotice("This is a notice");
// Result: ["NOTICE","This is a notice"]

// Create OK envelope
const env4 = encloseOK("abc123", true, "Event accepted");
// Result: ["OK","abc123",true,"Event accepted"]

// Create AUTH challenge
const env5 = encloseAuthChallenge("random-challenge-string");
// Result: ["AUTH","random-challenge-string"]

// Create AUTH response
import { Event } from "@wisehodl/roots/events";

const authEvent: Event = {
  id: "abc123",
  pubkey: "def456",
  kind: 22242,
  content: "",
  created_at: Math.floor(Date.now() / 1000),
  tags: [],
  sig: ""
};

// Convert to JSON
const authEventJSON = JSON.stringify(authEvent);

// Create envelope
const env6 = encloseAuthResponse(authEventJSON);
// Result: ["AUTH",{"id":"abc123","pubkey":"def456","kind":22242,"content":"","created_at":1636394097}]

Envelope Parsing

Extract label from envelope

const env = `["EVENT",{"id":"abc123","pubkey":"def456","kind":1,"content":"Hello Nostr!"}]`;
try {
  const label = getLabel(env);
  // label: "EVENT"

  // Check if label is standard
  const isStandard = isStandardLabel(label);
  // isStandard: true
} catch (err) {
  console.error(err);
}

Extract event from EVENT envelope

const env = `["EVENT",{"id":"abc123","pubkey":"def456","kind":1,"content":"Hello Nostr!"}]`;
try {
  const eventObj = findEvent(env);

  // Parse into ts-roots Event if needed
  import { Event, validate } from "@wisehodl/roots/events";

  // Validate the event
  try {
    validate(eventObj as Event);
  } catch (err) {
    console.error(`Invalid event: ${err.message}`);
  }

  // Now you can access event properties
  console.log(eventObj.id, eventObj.kind, eventObj.content);
} catch (err) {
  console.error(err);
}

Extract subscription event

const env = `["EVENT","sub1",{"id":"abc123","pubkey":"def456","kind":1,"content":"Hello Nostr!"}]`;
try {
  const [subID, eventObj] = findSubscriptionEvent(env);

  // Parse into ts-roots Event if needed
  import { Event } from "@wisehodl/roots/events";

  console.log(`Subscription: ${subID}, Event ID: ${eventObj.id}`);
} catch (err) {
  console.error(err);
}

Extract subscription request

const env = `["REQ","sub1",{"kinds":[1],"limit":50},{"authors":["def456"]}]`;
try {
  const [subID, filtersObj] = findReq(env);

  // Parse each filter
  import { Filter } from "@wisehodl/roots/filters";

  // Now you can use the filter objects
  filtersObj.forEach((filter, i) => {
    console.log(`Filter ${i}: `, filter);
  });
} catch (err) {
  console.error(err);
}

Extract other envelope types

// Extract OK response
const env1 = `["OK","abc123",true,"Event accepted"]`;
try {
  const [eventID, status, message] = findOK(env1);
  // eventID: "abc123"
  // status: true
  // message: "Event accepted"
} catch (err) {
  console.error(err);
}

// Extract EOSE message
const env2 = `["EOSE","sub1"]`;
try {
  const subID = findEOSE(env2);
  // subID: "sub1"
} catch (err) {
  console.error(err);
}

// Extract CLOSE message
const env3 = `["CLOSE","sub1"]`;
try {
  const subID = findClose(env3);
  // subID: "sub1"
} catch (err) {
  console.error(err);
}

// Extract CLOSED message
const env4 = `["CLOSED","sub1","Subscription complete"]`;
try {
  const [subID, message] = findClosed(env4);
  // subID: "sub1"
  // message: "Subscription complete"
} catch (err) {
  console.error(err);
}

// Extract NOTICE message
const env5 = `["NOTICE","This is a notice"]`;
try {
  const message = findNotice(env5);
  // message: "This is a notice"
} catch (err) {
  console.error(err);
}

// Extract AUTH challenge
const env6 = `["AUTH","random-challenge-string"]`;
try {
  const challenge = findAuthChallenge(env6);
  // challenge: "random-challenge-string"
} catch (err) {
  console.error(err);
}

// Extract AUTH response
const env7 = `["AUTH",{"id":"abc123","pubkey":"def456","kind":22242,"content":""}]`;
try {
  const authEvent = findAuthResponse(env7);

  // Parse into ts-roots Event if needed
  import { Event } from "@wisehodl/roots/events";
} catch (err) {
  console.error(err);
}

Testing

This library contains a comprehensive suite of unit tests. Run them with:

npm test

Or for a single run:

npm run test:run
Description
Nostr Websockets Data-Transport Core Library written in Typescript
Readme 49 KiB
Languages
TypeScript 100%