Converted go-roots-ws to typescript.
This commit is contained in:
344
README.md
Normal file
344
README.md
Normal file
@@ -0,0 +1,344 @@
|
||||
# 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:
|
||||
|
||||
```bash
|
||||
npm install @wisehodl/roots-ws
|
||||
```
|
||||
|
||||
2. Import the packages:
|
||||
|
||||
```typescript
|
||||
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";
|
||||
```
|
||||
|
||||
3. Access functions with appropriate namespaces.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Envelope Creation
|
||||
|
||||
#### Create EVENT envelope
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
```typescript
|
||||
// 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:
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
Or for a single run:
|
||||
|
||||
```bash
|
||||
npm run test:run
|
||||
```
|
||||
Reference in New Issue
Block a user