Re-write net docs
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# Adapter
|
||||
|
||||
Adapters provide a unified interface for communicating with relays. Adapters aren't meant to be used directly, but as an injection point for custom logic.
|
||||
|
||||
## Core Classes
|
||||
|
||||
### `AbstractAdapter`
|
||||
|
||||
Base class for all adapters. Handles events and cleanup.
|
||||
|
||||
- `send(message)` - Send message to relay
|
||||
- `cleanup()` - Clean up resources
|
||||
- Emits `AdapterEvent.Receive` when messages arrive
|
||||
|
||||
### Built-in Adapters
|
||||
|
||||
- `SocketAdapter(socket)` - WebSocket relay connections
|
||||
- `LocalAdapter(relay)` - Local in-memory relays
|
||||
- `MockAdapter(url, sendHandler)` - Testing with manual control
|
||||
|
||||
### Factory
|
||||
|
||||
`getAdapter(url, context?)` creates the appropriate adapter:
|
||||
|
||||
```typescript
|
||||
const adapter = getAdapter('wss://relay.example.com')
|
||||
adapter.on(AdapterEvent.Receive, (message, url) => {
|
||||
console.log('Received:', message)
|
||||
})
|
||||
adapter.send(['REQ', 'sub1', {}])
|
||||
adapter.cleanup()
|
||||
```
|
||||
|
||||
## Custom Adapter Example
|
||||
|
||||
Custom adapters can be created against any target:
|
||||
|
||||
```typescript
|
||||
class IPFSAdapter extends AbstractAdapter {
|
||||
constructor(private url string) {
|
||||
super()
|
||||
|
||||
// Set up an IPFS connection here
|
||||
}
|
||||
|
||||
get urls() { return [this.url] }
|
||||
get sockets() { return [] }
|
||||
|
||||
send(message: ClientMessage) {
|
||||
// Handle messages as if the ipfs backend was a relay
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Custom adapters can also be provided to several net utilities, including `publish` and `request`:
|
||||
|
||||
```typescript
|
||||
request({
|
||||
relays: ['ipfs://QmTy8w65yBXgyfG2ZBg5TrfB2hPjrDQH3RCQFJGkARStJb'],
|
||||
filters: [{kinds: [1]}],
|
||||
context: {
|
||||
getAdapter: (url: string) => {
|
||||
// getAdapter optionally returns an adapter. If none is returned, the stock adapters will be used.
|
||||
if (url.startsWith('ipfs://')) {
|
||||
return new IPFSAdapter(url)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,42 @@
|
||||
# Auth
|
||||
|
||||
Handles NIP-42 relay authentication flow.
|
||||
|
||||
## Core Classes
|
||||
|
||||
### `AuthState`
|
||||
|
||||
Manages authentication state for a socket connection.
|
||||
|
||||
**Status Values:**
|
||||
- `AuthStatus.None` - No authentication required/attempted
|
||||
- `AuthStatus.Requested` - Relay requested authentication
|
||||
- `AuthStatus.PendingSignature` - Waiting for user to sign auth event
|
||||
- `AuthStatus.DeniedSignature` - User denied signing
|
||||
- `AuthStatus.PendingResponse` - Waiting for relay response
|
||||
- `AuthStatus.Forbidden` - Authentication failed
|
||||
- `AuthStatus.Ok` - Authentication successful
|
||||
|
||||
**Methods:**
|
||||
- `doAuth(sign)` - Authenticate with the relay using provided signing function
|
||||
- `attemptAuth(sign)` - Attempt authentication with timeout handling
|
||||
- `cleanup()` - Clean up event listeners
|
||||
|
||||
**Events:**
|
||||
- `AuthStateEvent.Status` - Emitted when authentication status changes
|
||||
|
||||
## Example
|
||||
|
||||
```typescript
|
||||
const authState = new AuthState(socket)
|
||||
|
||||
// Listen for auth status changes
|
||||
authState.on(AuthStateEvent.Status, (status) => {
|
||||
console.log('Auth status:', status)
|
||||
})
|
||||
|
||||
// Attempt authentication when relay requests it
|
||||
await authState.attemptAuth(async (template) => {
|
||||
return await signer.signEvent(template)
|
||||
})
|
||||
```
|
||||
@@ -1,78 +0,0 @@
|
||||
# Connection
|
||||
|
||||
The `Connection` class is the core building block for relay communication in `@welshman/net`. It manages the complete lifecycle of a relay connection, including socket handling, message queuing, authentication, and statistics tracking.
|
||||
|
||||
## Overview
|
||||
|
||||
A Connection handles:
|
||||
- WebSocket lifecycle
|
||||
- Message queuing and throttling
|
||||
- Connection state tracking
|
||||
- Relay authentication
|
||||
- Connection statistics
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```typescript
|
||||
import {Connection} from '@welshman/net'
|
||||
|
||||
// Create connection
|
||||
const connection = new Connection("wss://relay.example.com")
|
||||
|
||||
// Listen for events
|
||||
connection.on('event', (conn, subId, event) => {
|
||||
console.log(`Got event from ${conn.url}`)
|
||||
})
|
||||
|
||||
// Send a subscription
|
||||
connection.send(["REQ", "my-sub", {kinds: [1], limit: 10}])
|
||||
|
||||
// Clean up when done
|
||||
connection.cleanup()
|
||||
```
|
||||
|
||||
## Handling Authentication
|
||||
|
||||
The `connection.open()` promise resolves when the WebSocket connection is fully established and ready for communication.
|
||||
However, it's important to understand the authentication flow:
|
||||
|
||||
```typescript
|
||||
import {Connection} from '@welshman/net'
|
||||
|
||||
const connection = new Connection("wss://relay.example.com")
|
||||
|
||||
// Basic open
|
||||
await connection.open()
|
||||
// Promise resolves when WebSocket is connected
|
||||
// BUT might not be auth-ready yet!
|
||||
|
||||
// Complete open with auth handling
|
||||
const openRelay = async (url: string) => {
|
||||
const connection = new Connection(url)
|
||||
|
||||
// Open socket
|
||||
await connection.open()
|
||||
|
||||
// Check if relay requires auth
|
||||
if (connection.auth.status === 'requested') {
|
||||
try {
|
||||
// Handle auth challenge
|
||||
await connection.auth.attempt(3000) // 3s timeout
|
||||
} catch (e) {
|
||||
console.error('Auth failed:', e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// NOW connection is fully ready
|
||||
return connection
|
||||
}
|
||||
```
|
||||
|
||||
The key states after `open()` resolves:
|
||||
- Socket is connected
|
||||
- Messages can be queued
|
||||
- BUT relay might request authentication
|
||||
- AND authentication might fail
|
||||
|
||||
Always check `connection.auth.status` if you need to ensure the connection is fully authenticated before use.
|
||||
+32
-52
@@ -1,63 +1,43 @@
|
||||
# Context
|
||||
|
||||
The Context system is the backbone of `@welshman/net`, providing global configuration and shared services that are essential for the package's operation. It defines how events are handled, validated, and routed throughout the application.
|
||||
Provides global configuration and dependencies for the net package.
|
||||
|
||||
## Overview
|
||||
## NetContext
|
||||
|
||||
- Global connection pool (`ctx.net.pool`)
|
||||
- Event validation (`ctx.net.isValid`)
|
||||
- Event handling (`ctx.net.onEvent`)
|
||||
- Deletion tracking (`ctx.net.isDeleted`)
|
||||
- Event signing (`ctx.net.signEvent`)
|
||||
- Subscription optimization (`ctx.net.optimizeSubscriptions`)
|
||||
Configuration object that defines how the net package operates:
|
||||
|
||||
## Basic Usage
|
||||
- `pool: Pool` - Socket connection pool
|
||||
- `repository: Repository` - Event storage and retrieval
|
||||
- `isEventValid: (event, url) => boolean` - Event validation function
|
||||
- `isEventDeleted: (event, url) => boolean` - Event deletion check
|
||||
- `getAdapter?: (url, context) => AbstractAdapter` - Custom adapter factory
|
||||
|
||||
## Default Context
|
||||
|
||||
The `netContext` global provides sensible defaults:
|
||||
|
||||
- Uses singleton pool and repository instances
|
||||
- Validates events using `verifyEvent`
|
||||
- Checks deletions via repository
|
||||
- No custom adapter factory
|
||||
|
||||
## Example
|
||||
|
||||
```typescript
|
||||
import {ctx, setContext} from '@welshman/lib'
|
||||
import {
|
||||
getDefaultNetContext,
|
||||
Pool,
|
||||
hasValidSignature
|
||||
} from '@welshman/net'
|
||||
import {netContext} from '@welshman/net'
|
||||
|
||||
// Setup networking context
|
||||
setContext({
|
||||
net: getDefaultNetContext({
|
||||
// Use shared pool
|
||||
pool: new Pool(),
|
||||
// Override event validation
|
||||
netContext.isEventValid: (event, url) => {
|
||||
return event.kind < 30000 && verifyEvent(event)
|
||||
}
|
||||
|
||||
// Track events
|
||||
onEvent: (url, event) => {
|
||||
tracker.track(event.id, url)
|
||||
repository.publish(event)
|
||||
},
|
||||
|
||||
// Validate based on source
|
||||
isValid: (url, event) => {
|
||||
// Trust local relay
|
||||
if (url === LOCAL_RELAY_URL) return true
|
||||
|
||||
// Validate signature for remote events
|
||||
return hasValidSignature(event)
|
||||
},
|
||||
|
||||
// Check deletion status
|
||||
isDeleted: (url, event) =>
|
||||
repository.isDeleted(event),
|
||||
|
||||
// Sign with current user
|
||||
signEvent: async (event) =>
|
||||
signer.get().sign(event)
|
||||
})
|
||||
// Use in requests
|
||||
request({
|
||||
filters: [{kinds: [1]}],
|
||||
relays: ['wss://relay.example.com'],
|
||||
context: {
|
||||
...netContext,
|
||||
// Request-specific overrides
|
||||
}
|
||||
})
|
||||
|
||||
// Now all package features will use these settings
|
||||
subscribe(/*...*/) // Uses pool, validates events
|
||||
publish(/*...*/) // Uses signEvent
|
||||
```
|
||||
|
||||
The Context is used internally by most features in the package.
|
||||
Without proper context configuration, core features like subscription, publishing, and event validation won't work correctly.
|
||||
|
||||
Think of it as the central configuration that defines how your nostr networking behaves.
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# Diff
|
||||
|
||||
Efficient event synchronization using NIP-77 Negentropy protocol.
|
||||
|
||||
## Core Classes
|
||||
|
||||
### `Difference`
|
||||
|
||||
Handles negentropy synchronization with a single relay for a single filter.
|
||||
|
||||
**Events:**
|
||||
- `DifferenceEvent.Message` - Emitted with `{have, need}` arrays and relay URL
|
||||
- `DifferenceEvent.Error` - Emitted when sync fails
|
||||
- `DifferenceEvent.Close` - Emitted when sync completes
|
||||
|
||||
**Methods:**
|
||||
- `close()` - Stop synchronization and cleanup
|
||||
|
||||
## Functions
|
||||
|
||||
### `diff(options)`
|
||||
|
||||
Check what events each relay has or needs compared to local events.
|
||||
|
||||
**Returns:** Array of `{relay, have, need}` objects - `have` are events the relay has that you don't, `need` are events you have that the relay doesn't.
|
||||
|
||||
### `pull(options)`
|
||||
|
||||
Fetch missing events after comparing with relays.
|
||||
|
||||
**Returns:** Array of retrieved events.
|
||||
|
||||
### `push(options)`
|
||||
|
||||
Publish local events that relays are missing.
|
||||
|
||||
## Example
|
||||
|
||||
```typescript
|
||||
// Check what events each relay has/needs
|
||||
const results = await diff({
|
||||
relays: ['wss://relay1.com', 'wss://relay2.com'],
|
||||
filters: [{kinds: [1], authors: ['pubkey...']}],
|
||||
events: localEvents
|
||||
})
|
||||
|
||||
// Pull events that relays have but we don't
|
||||
const newEvents = await pull({
|
||||
relays: ['wss://relay1.com'],
|
||||
filters: [{kinds: [1]}],
|
||||
events: localEvents
|
||||
})
|
||||
```
|
||||
@@ -1,56 +0,0 @@
|
||||
# Executor
|
||||
|
||||
The Executor class orchestrates event delivery and subscription management across one or more [targets](/net/targets.md). It abstracts the complexity of handling multiple connections into a single interface.
|
||||
|
||||
## Overview
|
||||
|
||||
The Executor:
|
||||
- Manages subscriptions
|
||||
- Handles event publishing
|
||||
- Supports NIP-77 (negentropy)
|
||||
- Routes messages to appropriate targets
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```typescript
|
||||
import {Executor, Relays} from '@welshman/net'
|
||||
|
||||
// Create executor with relay target
|
||||
const executor = new Executor(
|
||||
new Relays([
|
||||
connection1,
|
||||
connection2
|
||||
])
|
||||
)
|
||||
|
||||
// Subscribe to events
|
||||
const sub = executor.subscribe(
|
||||
[{kinds: [1], limit: 10}],
|
||||
{
|
||||
onEvent: (url, event) => {
|
||||
console.log(`Got event from ${url}`, event)
|
||||
},
|
||||
onEose: (url) => {
|
||||
console.log(`EOSE from ${url}`)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Publish event
|
||||
const pub = executor.publish(
|
||||
signedEvent,
|
||||
{
|
||||
onOk: (url, id, success, message) => {
|
||||
console.log(`Published to ${url}: ${success ? 'OK' : message}`)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Clean up
|
||||
sub.unsubscribe()
|
||||
executor.target.cleanup()
|
||||
```
|
||||
|
||||
The Executor is used internally by higher-level APIs but can be used directly when you need fine-grained control over event routing and subscription management.
|
||||
|
||||
It's particularly useful when implementing custom targets or handling special relay configurations (like local relays or relay groups).
|
||||
+2
-67
@@ -11,71 +11,6 @@ Core networking layer for nostr applications, handling relay connections, messag
|
||||
- **Publishing Tools** - Event broadcasting with status tracking
|
||||
- **Sync Utilities** - NIP-77 (negentropy) event synchronization
|
||||
- **Connection Pool** - Shared relay connection management
|
||||
- **Targets** - Flexible message routing strategies
|
||||
- **Custom Adapters** - Flexible adapter layer to support queries against any target
|
||||
- **Event Tracking** - Monitor which relays have seen events
|
||||
|
||||
## Quick Example
|
||||
|
||||
```typescript
|
||||
import {ctx, setContext} from '@welshman/lib'
|
||||
import {type TrustedEvent, createEvent, NOTE} from '@welshman/util'
|
||||
import {subscribe, publish, getDefaultNetContext} from '@welshman/net'
|
||||
|
||||
// Sets up customizable event valdation, handlers, etc
|
||||
setContext(getDefaultNetContext())
|
||||
|
||||
// Send a subscription
|
||||
const sub = subscribe({
|
||||
relays: ['wss://relay.example.com/'],
|
||||
filters: [{kinds: [1], limit: 1}],
|
||||
closeOnEose: true,
|
||||
timeout: 10000,
|
||||
})
|
||||
|
||||
sub.on(SubscriptionEvent.Event, (url: string, event: TrustedEvent) => {
|
||||
console.log(url, event)
|
||||
sub.close()
|
||||
})
|
||||
|
||||
// Publish an event
|
||||
const pub = publish({
|
||||
relays: ['wss://relay.example.com/'],
|
||||
event: createEvent(NOTE, {content: 'hi'}),
|
||||
})
|
||||
|
||||
pub.emitter.on('*', (status: PublishStatus, url: string) => {
|
||||
console.log(status, url)
|
||||
})
|
||||
|
||||
// The Tracker class can tell you which relays an event was read from or published to
|
||||
console.log(ctx.net.tracker.getRelays(event.id))
|
||||
```
|
||||
|
||||
The main reason this module exists is to support different backends via Executor and different `target` classes. For example, to add a local relay that automatically gets used:
|
||||
|
||||
```typescript
|
||||
import {setContext} from '@welshman/lib'
|
||||
import {LOCAL_RELAY_URL, Relay, Repository} from '@welshman/util'
|
||||
import {getDefaultNetContext, Multi, Local, Relays, Executor} from '@welshman/net'
|
||||
|
||||
const repository = new Repository()
|
||||
|
||||
const relay = new Relay(repository)
|
||||
|
||||
setContext(getDefaultNetContext({
|
||||
getExecutor: (relays: string[]) => {
|
||||
return new Executor(
|
||||
new Multi([
|
||||
new Local(relay),
|
||||
new Relays(remoteUrls.map(url => ctx.net.pool.get(url))),
|
||||
])
|
||||
)
|
||||
},
|
||||
}))
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @welshman/net
|
||||
```
|
||||
- **Socket policies** - Default and customizable policies for NIP 42 AUTH, reconnects, etc
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# Message
|
||||
|
||||
Type definitions and utilities for Nostr protocol messages.
|
||||
|
||||
## Relay Message Types
|
||||
|
||||
**Enums:**
|
||||
- `RelayMessageType.Auth` - Authentication challenge
|
||||
- `RelayMessageType.Closed` - Subscription closed
|
||||
- `RelayMessageType.Eose` - End of stored events
|
||||
- `RelayMessageType.Event` - Event data
|
||||
- `RelayMessageType.NegErr` - Negentropy error
|
||||
- `RelayMessageType.NegMsg` - Negentropy message
|
||||
- `RelayMessageType.Ok` - Command result
|
||||
|
||||
**Type Definitions:**
|
||||
- `RelayMessage` - Base relay message type
|
||||
- `RelayAuth`, `RelayClosed`, `RelayEose`, `RelayEvent`, `RelayNegErr`, `RelayNegMsg`, `RelayOk` - Specific message types
|
||||
- `RelayAuthPayload`, `RelayClosedPayload`, etc. - Payload types for each message
|
||||
|
||||
**Type Guards:**
|
||||
- `isRelayAuth()`, `isRelayClosed()`, `isRelayEose()`, `isRelayEvent()`, `isRelayNegErr()`, `isRelayNegMsg()`, `isRelayOk()`
|
||||
|
||||
## Client Message Types
|
||||
|
||||
**Enums:**
|
||||
- `ClientMessageType.Auth` - Authentication response
|
||||
- `ClientMessageType.Close` - Close subscription
|
||||
- `ClientMessageType.Event` - Publish event
|
||||
- `ClientMessageType.NegClose` - Close negentropy
|
||||
- `ClientMessageType.NegOpen` - Open negentropy
|
||||
- `ClientMessageType.Req` - Request subscription
|
||||
|
||||
**Type Definitions:**
|
||||
- `ClientMessage` - Base client message type
|
||||
- `ClientAuth`, `ClientClose`, `ClientEvent`, `ClientNegClose`, `ClientNegOpen`, `ClientReq` - Specific message types
|
||||
- `ClientAuthPayload`, `ClientClosePayload`, etc. - Payload types for each message
|
||||
|
||||
**Type Guards:**
|
||||
- `isClientAuth()`, `isClientClose()`, `isClientEvent()`, `isClientNegClose()`, `isClientNegOpen()`, `isClientReq()`
|
||||
|
||||
## Example
|
||||
|
||||
```typescript
|
||||
// Handle incoming relay message
|
||||
const handleMessage = (message: RelayMessage) => {
|
||||
if (isRelayEvent(message)) {
|
||||
const [type, subId, event] = message
|
||||
console.log('Event:', event.id)
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,63 @@
|
||||
# Policy
|
||||
|
||||
Socket policies provide automated behavior for socket connections. They are intended to be applied on socket creation via `makeSocket` or `PoolOptions.makeSocket`.
|
||||
|
||||
## Built-in Policies
|
||||
|
||||
### `socketPolicyAuthBuffer`
|
||||
|
||||
Buffers messages during authentication flow and replays them after successful auth.
|
||||
|
||||
### `socketPolicyConnectOnSend`
|
||||
|
||||
Auto-connects closed sockets when messages are sent (with error cooldown).
|
||||
|
||||
### `socketPolicyCloseOnTimeout`
|
||||
|
||||
Closes sockets after 30 seconds of inactivity.
|
||||
|
||||
### `socketPolicyReopenActive`
|
||||
|
||||
Reopens sockets that have pending requests or publishes.
|
||||
|
||||
## Custom Auth Policy
|
||||
|
||||
### `makeSocketPolicyAuth(options)`
|
||||
|
||||
Creates a policy that handles authentication challenges.
|
||||
|
||||
**Options:**
|
||||
- `sign: (event) => Promise<SignedEvent>` - Signing function
|
||||
- `shouldAuth?: (socket) => boolean` - Optional auth condition
|
||||
|
||||
## Default Policies
|
||||
|
||||
`defaultSocketPolicies` includes all built-in policies except auth (which requires configuration).
|
||||
|
||||
It's common to include the following code in order to enable automatic authentication on all connections:
|
||||
|
||||
```typescript
|
||||
defaultSocketPolicies.push(
|
||||
makeSocketPolicyAuth({
|
||||
sign: (event: StampedEvent) => signer.sign(event),
|
||||
shouldAuth: (socket: Socket) => true,
|
||||
}),
|
||||
)
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
It's possible to create custom policies simply by defining a function which returns a cleanup function:
|
||||
|
||||
```typescript
|
||||
import {on} from "@welshman/lib"
|
||||
import {SocketEvent, Socket, SocketStatus} from "@welshman/net"
|
||||
|
||||
const logStatusChangePolicy = (socket: Socket) => {
|
||||
const unsubscribe = on(socket, SocketEvent.Status, (newStatus: SocketStatus) => {
|
||||
console.log(socket.url, newStatus)
|
||||
})
|
||||
|
||||
return unsubscribe
|
||||
}
|
||||
```
|
||||
+30
-24
@@ -1,37 +1,43 @@
|
||||
# Pool
|
||||
|
||||
The Pool class manages a collection of relay connections, providing a centralized way to track and reuse connections across your application.
|
||||
The Pool class manages a collection of websocket connections to relay servers, providing connection pooling and lifecycle management.
|
||||
|
||||
## Overview
|
||||
## Classes
|
||||
|
||||
- Creates and caches connections
|
||||
- Ensures single connection per relay
|
||||
- Handles cleanup of unused connections
|
||||
- Provides connection lookup
|
||||
### Pool
|
||||
|
||||
## Usage
|
||||
A connection pool that creates and manages Socket instances for different relay URLs.
|
||||
|
||||
**Methods:**
|
||||
- `static get()` - Returns the singleton pool instance
|
||||
- `has(url)` - Checks if a socket exists for the given URL
|
||||
- `get(url)` - Gets or creates a socket for the given URL
|
||||
- `subscribe(callback)` - Subscribes to new socket creation events
|
||||
- `remove(url)` - Removes and cleans up a socket
|
||||
- `clear()` - Removes all sockets from the pool
|
||||
|
||||
## Functions
|
||||
|
||||
### makeSocket(url, policies)
|
||||
|
||||
Creates a new Socket instance with the given URL and applies default policies.
|
||||
|
||||
## Example
|
||||
|
||||
```typescript
|
||||
import {Pool} from '@welshman/net'
|
||||
import {Pool} from "@welshman/net"
|
||||
|
||||
// Create pool
|
||||
const pool = new Pool()
|
||||
// Get the singleton - Pool can also be instantiated directly if you want multiple pools
|
||||
const pool = Pool.get()
|
||||
|
||||
// Get or create connection
|
||||
const connection = pool.get("wss://relay.example.com")
|
||||
// Get a socket for a relay
|
||||
const socket = pool.get("wss://relay.example.com")
|
||||
|
||||
// Check if relay is in pool
|
||||
if (pool.has("wss://relay.example.com")) {
|
||||
// Use existing connection
|
||||
}
|
||||
// Subscribe to new socket creation
|
||||
const unsubscribe = pool.subscribe((socket) => {
|
||||
console.log("New socket created:", socket.url)
|
||||
})
|
||||
|
||||
// Remove connection
|
||||
// Clean up
|
||||
pool.remove("wss://relay.example.com")
|
||||
|
||||
// Clear all connections
|
||||
pool.clear()
|
||||
```
|
||||
|
||||
|
||||
The Pool is typically used internally by the router and executor, but can be used directly for custom connection management.
|
||||
It ensures efficient connection reuse across your application.
|
||||
|
||||
+42
-74
@@ -1,85 +1,53 @@
|
||||
# Publish
|
||||
|
||||
The `Publish` class handles event publishing to relays, managing publish status, relay responses, and error handling.
|
||||
Utilities for publishing events to Nostr relays with status tracking and callback handling.
|
||||
|
||||
## Overview
|
||||
## Enums
|
||||
|
||||
- Sends events to relays
|
||||
- Tracks publish status per relay
|
||||
- Handles OK/Error responses
|
||||
- Manages timeouts
|
||||
### PublishStatus
|
||||
|
||||
## Basic Usage
|
||||
Status values for publish operations:
|
||||
- `Sending` - Event is being sent
|
||||
- `Pending` - Waiting for relay response
|
||||
- `Success` - Event published successfully
|
||||
- `Failure` - Event rejected by relay
|
||||
- `Timeout` - Request timed out
|
||||
- `Aborted` - Request was aborted
|
||||
|
||||
## Functions
|
||||
|
||||
### publishOne(options)
|
||||
|
||||
Publishes an event to a single relay and returns a promise that resolves with the publish status.
|
||||
|
||||
**Options:**
|
||||
- `event` - The signed event to publish
|
||||
- `relay` - Relay URL
|
||||
- `signal?` - AbortSignal for cancellation
|
||||
- `timeout?` - Timeout in milliseconds (default: 10000)
|
||||
- `context?` - Adapter context
|
||||
- Callback functions: `onSuccess`, `onFailure`, `onPending`, `onTimeout`, `onAborted`, `onComplete`
|
||||
|
||||
### publish(options)
|
||||
|
||||
Publishes an event to multiple relays in parallel and returns a status object mapping relay URLs to their publish status.
|
||||
|
||||
## Example
|
||||
|
||||
```typescript
|
||||
import {`Publish`, `Publish`Status} from '@welshman/net'
|
||||
import {publish, PublishStatus} from "@welshman/net"
|
||||
|
||||
const {Pending, Success, Failure, Timeout, Aborted} = `Publish`Status
|
||||
|
||||
// Basic `Publish`
|
||||
const pub = `Publish`({
|
||||
event: signedEvent,
|
||||
relays: ["wss://relay.example.com"],
|
||||
timeout: 3000 // 3s timeout
|
||||
})
|
||||
|
||||
// Track status
|
||||
pub.emitter.on('*', (status: `Publish`Status, url: string, message?: string) => {
|
||||
switch (status) {
|
||||
case Success:
|
||||
console.log(``Publish`ed to ${url}`)
|
||||
break
|
||||
case Failure:
|
||||
console.log(`Failed on ${url}: ${message}`)
|
||||
break
|
||||
case Timeout:
|
||||
console.log(`Timeout on ${url}`)
|
||||
break
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Real World Example
|
||||
|
||||
```typescript
|
||||
const publishWithStatus = async (event: SignedEvent) => {
|
||||
const pub = `Publish`({
|
||||
event,
|
||||
relays: ctx.app.router
|
||||
.FromUser()
|
||||
.getUrls(),
|
||||
timeout: 5000
|
||||
})
|
||||
|
||||
// Track per-relay status
|
||||
const status = new Map<string, string>()
|
||||
|
||||
pub.emitter.on('*', (state: `Publish`Status, url: string) => {
|
||||
status.set(url, state)
|
||||
|
||||
// Log progress
|
||||
const counts = {
|
||||
pending: 0,
|
||||
success: 0,
|
||||
failed: 0
|
||||
}
|
||||
|
||||
for (const s of status.values()) {
|
||||
counts[s] = (counts[s] || 0) + 1
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Progress: ${counts.success}/${status.size}`,
|
||||
`(${counts.failed} failed)`
|
||||
)
|
||||
})
|
||||
|
||||
// Wait for completion
|
||||
return pub.result
|
||||
const event = {
|
||||
// ... signed event
|
||||
}
|
||||
```
|
||||
|
||||
Like [Subscribe](/net/subscribe.md), `Publish` uses [Pool](/net/pool.md) for connections and creates appropriate [Targets](/net/targets.md) via an [Executor](/net/executor.md), but focuses on event publishing rather than subscription management.
|
||||
const statusByRelay = await publish({
|
||||
event,
|
||||
relays: ["wss://relay1.com", "wss://relay2.com"],
|
||||
timeout: 5000,
|
||||
onSuccess: (detail, relay) => console.log(`Published to ${relay}`),
|
||||
onFailure: (detail, relay) => console.log(`Failed on ${relay}: ${detail}`)
|
||||
})
|
||||
|
||||
Note: The base `@welshman/net` Publish class just handles network publishing.
|
||||
For optimistic updates and repository integration, use Publish from `@welshman/app`.
|
||||
console.log(statusByRelay) // { "wss://relay1.com": "success", "wss://relay2.com": "failure" }
|
||||
```
|
||||
@@ -0,0 +1,63 @@
|
||||
# Request
|
||||
|
||||
Utilities for requesting events from Nostr relays with filtering, deduplication, and batching capabilities.
|
||||
|
||||
## Functions
|
||||
|
||||
### requestOne(options)
|
||||
|
||||
Requests events from a single relay using the given filters. Returns a promise that resolves with deduplicated events.
|
||||
|
||||
**Options:**
|
||||
- `relay` - Relay URL
|
||||
- `filters` - Array of Nostr filters
|
||||
- `signal?` - AbortSignal for cancellation
|
||||
- `tracker?` - Event tracker for deduplication
|
||||
- `context?` - Adapter context
|
||||
- `autoClose?` - Auto-close subscription after EOSE
|
||||
- Validation functions: `isEventValid`, `isEventDeleted`
|
||||
- Callback functions: `onEvent`, `onDeleted`, `onInvalid`, `onFiltered`, `onDuplicate`, `onDisconnect`, `onEose`, `onClose`
|
||||
|
||||
### request(options)
|
||||
|
||||
Requests events from multiple relays in parallel. Returns a promise that resolves with all events from all relays.
|
||||
|
||||
**Options:**
|
||||
- `relays` - Array of relay URLs
|
||||
- `filters` - Array of Nostr filters
|
||||
- `threshold?` - Fraction of relays that must close before completing (default: 1)
|
||||
- All other options from `requestOne`
|
||||
|
||||
### makeLoader(options)
|
||||
|
||||
Creates a batched loader function that delays and combines requests for efficiency.
|
||||
|
||||
**Options:**
|
||||
- `delay` - Batch delay in milliseconds
|
||||
- `timeout?` - Request timeout
|
||||
- `threshold?` - Relay completion threshold
|
||||
- Validation functions and context options
|
||||
|
||||
### load(filters, relays, options)
|
||||
|
||||
Pre-configured loader with 200ms delay, 3s timeout, and 0.5 threshold.
|
||||
|
||||
## Example
|
||||
|
||||
```typescript
|
||||
import {request, load} from "@welshman/net"
|
||||
|
||||
// Simple request - without autoClose or a signal this will continue to stream indefinitely.
|
||||
// Default policies (see policy.ts) will also re-send the subscription when sockets reconnect
|
||||
const events = await request({
|
||||
relays: ["wss://relay1.com", "wss://relay2.com"],
|
||||
filters: [{kinds: [1], limit: 10}],
|
||||
onEvent: (event, url) => console.log(`Event from ${url}:`, event.id)
|
||||
})
|
||||
|
||||
// Batched loading (more efficient for multiple requests)
|
||||
const profileEvents = await load({
|
||||
relays: ["wss://relay1.com"],
|
||||
filters: [{kinds: [0], authors: ["pubkey1", "pubkey2"]}]
|
||||
})
|
||||
```
|
||||
+55
-62
@@ -1,69 +1,62 @@
|
||||
# Socket
|
||||
The Socket class is exclusively used by the `Connection` class as its low-level WebSocket manager. It's not meant to be used directly by other classes.
|
||||
Its sole purpose is to provide a reliable, manageable WebSocket connection with nostr-specific handling.
|
||||
|
||||
## Core Responsibilities
|
||||
WebSocket wrapper for Nostr relay connections with status tracking, queuing, and authentication support. Not intended to be used directly, instead access sockets through the `Pool` interface.
|
||||
|
||||
## Enums
|
||||
|
||||
### SocketStatus
|
||||
|
||||
Connection status values:
|
||||
- `Open` - Socket is connected and ready
|
||||
- `Opening` - Socket is connecting
|
||||
- `Closing` - Socket is closing
|
||||
- `Closed` - Socket is closed
|
||||
- `Error` - Socket encountered an error
|
||||
|
||||
### SocketEvent
|
||||
|
||||
Event types emitted by the socket:
|
||||
- `Error` - Socket error occurred
|
||||
- `Status` - Status changed
|
||||
- `Send` - Message sent to relay
|
||||
- `Sending` - Message queued for sending
|
||||
- `Receive` - Message received from relay
|
||||
- `Receiving` - Message queued for processing
|
||||
|
||||
## Classes
|
||||
|
||||
### Socket
|
||||
|
||||
WebSocket connection to a Nostr relay with queuing and authentication.
|
||||
|
||||
**Properties:**
|
||||
- `url` - Relay URL
|
||||
- `status` - Current socket status
|
||||
- `auth` - Authentication state
|
||||
|
||||
**Methods:**
|
||||
- `open()` - Opens the WebSocket connection
|
||||
- `attemptToOpen()` - Opens connection if not already open
|
||||
- `close()` - Closes the connection
|
||||
- `cleanup()` - Closes connection and removes all listeners
|
||||
- `send(message)` - Queues a message to send
|
||||
|
||||
## Example
|
||||
|
||||
```typescript
|
||||
export class Socket {
|
||||
// Track connection state
|
||||
status: SocketStatus = "new" | "open" | "opening" | "closing" | "closed" | "error"
|
||||
import {Socket, SocketEvent, SocketStatus} from "@welshman/net"
|
||||
|
||||
// Handle nostr message queue
|
||||
worker: Worker<Message>
|
||||
const socket = new Socket("wss://relay.example.com")
|
||||
|
||||
// Core operations
|
||||
open = async () => {/* Initialize WebSocket */}
|
||||
close = async () => {/* Clean shutdown */}
|
||||
send = async (message: Message) => {/* Send with JSON serialization */}
|
||||
}
|
||||
socket.on(SocketEvent.Status, (status, url) => {
|
||||
console.log(`Socket ${url} status: ${status}`)
|
||||
})
|
||||
|
||||
socket.on(SocketEvent.Receive, (message, url) => {
|
||||
console.log("Received:", message)
|
||||
})
|
||||
|
||||
socket.open()
|
||||
socket.send(["REQ", "sub-id", {kinds: [1], limit: 10}])
|
||||
socket.cleanup()
|
||||
```
|
||||
|
||||
Key features:
|
||||
- State tracking
|
||||
- Message queuing
|
||||
- JSON serialization
|
||||
- Error recovery
|
||||
- Connection lifecycle
|
||||
|
||||
Think of it as a thin wrapper that turns raw WebSocket connections into something more suitable for nostr:
|
||||
```typescript
|
||||
// Raw WebSocket
|
||||
ws.send(JSON.stringify(["REQ", "sub1", {kinds: [1]}]))
|
||||
|
||||
// With Socket
|
||||
socket.send(["REQ", "sub1", {kinds: [1]}]) // Handles serialization
|
||||
```
|
||||
|
||||
## Usage Chain
|
||||
|
||||
```typescript
|
||||
// Hierarchy
|
||||
Socket // WebSocket management
|
||||
↳ Connection // Uses Socket
|
||||
↳ Relay Target // Uses Connection
|
||||
↳ Executor // Uses Target
|
||||
↳ Subscribe // Uses Executor
|
||||
↳ Publish // Uses Executor
|
||||
|
||||
// In Connection.ts
|
||||
export class Connection extends Emitter {
|
||||
socket: Socket
|
||||
|
||||
constructor(url: string) {
|
||||
this.socket = new Socket(this)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It's an internal implementation detail that you shouldn't need to use directly - always interact with the `Connection` class instead, which provides a higher-level interface.
|
||||
|
||||
```typescript
|
||||
// DON'T use Socket directly
|
||||
const socket = new Socket(/*...*/) // ❌
|
||||
|
||||
// DO use Connection
|
||||
const connection = new Connection(url) // ✅
|
||||
```
|
||||
|
||||
This encapsulation ensures consistent connection management across the library.
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
# Subscribe
|
||||
|
||||
The Subscribe class manages nostr subscriptions, handling subscription lifecycle, event filtering, and relay responses. It provides a unified interface for subscribing to events across multiple relays.
|
||||
|
||||
## Overview
|
||||
|
||||
The Subscription:
|
||||
- Manages REQ/CLOSE lifecycle
|
||||
- Handles EOSE responses
|
||||
- Emits filtered events
|
||||
- Tracks completion state
|
||||
|
||||
```typescript
|
||||
import {subscribe, SubscriptionEvent} from '@welshman/net'
|
||||
|
||||
// Create subscription
|
||||
const sub = subscribe({
|
||||
filters: [{kinds: [1], limit: 10}],
|
||||
relays: ["wss://relay.example.com"],
|
||||
|
||||
// Optional configurations
|
||||
closeOnEose: true, // Close after all relays send EOSE
|
||||
timeout: 3000, // Max time to wait
|
||||
authTimeout: 300, // Time for auth negotiation
|
||||
delay: 50 // Delay between batched requests
|
||||
})
|
||||
|
||||
// Handle events
|
||||
sub.on(SubscriptionEvent.Event, (url, event) => {
|
||||
console.log(`Got event from ${url}:`, event)
|
||||
})
|
||||
|
||||
sub.on(SubscriptionEvent.Eose, (url) => {
|
||||
console.log(`Got EOSE from ${url}`)
|
||||
})
|
||||
|
||||
sub.on(SubscriptionEvent.Complete, () => {
|
||||
console.log('Subscription complete')
|
||||
})
|
||||
|
||||
// Close when done
|
||||
sub.close()
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```typescript
|
||||
import {subscribe, Pool, Executor, Relays} from '@welshman/net'
|
||||
|
||||
// Under the hood, subscribe:
|
||||
// 1. Gets connections from global pool
|
||||
// 2. Creates a target (usually Relays)
|
||||
// 3. Uses Executor to manage subscription
|
||||
|
||||
// This is roughly equivalent to:
|
||||
const manualSubscribe = (urls: string[]) => {
|
||||
// Get connections from pool
|
||||
const connections = urls.map(url =>
|
||||
ctx.net.pool.get(url)
|
||||
)
|
||||
|
||||
// Create target
|
||||
const target = new Relays(connections)
|
||||
|
||||
// Create executor
|
||||
const executor = new Executor(target)
|
||||
|
||||
// Subscribe via executor
|
||||
return executor.subscribe(
|
||||
[{kinds: [1], limit: 10}],
|
||||
{
|
||||
onEvent: (url, event) => {
|
||||
console.log(`Got event from ${url}`)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Real World Example
|
||||
|
||||
```typescript
|
||||
// Combine local and remote relays
|
||||
const loadProfile = async (pubkey: string) => {
|
||||
// Get optimal relays
|
||||
const relays = ctx.app.router
|
||||
.ForPubkey(pubkey)
|
||||
.getUrls()
|
||||
|
||||
const sub = subscribe({
|
||||
filters: [{
|
||||
kinds: [0],
|
||||
authors: [pubkey],
|
||||
limit: 1
|
||||
}],
|
||||
relays,
|
||||
// This creates internally:
|
||||
// 1. Connections via Pool
|
||||
// 2. Multi target with Local + Relays
|
||||
// 3. Executor to manage subscription
|
||||
})
|
||||
|
||||
return new Promise(resolve => {
|
||||
sub.on('event', (url, event) => {
|
||||
resolve(event)
|
||||
sub.close()
|
||||
})
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The Subscribe class abstracts away:
|
||||
- Connection management (via Pool)
|
||||
- Target creation and setup
|
||||
- Executor orchestration
|
||||
@@ -1,70 +0,0 @@
|
||||
# Sync
|
||||
|
||||
The Sync utilities in `@welshman/net` provide methods for synchronizing events between relays and repositories, primarily using NIP-77 (Negentropy) when available, with fallback to traditional sync methods.
|
||||
|
||||
## Overview
|
||||
|
||||
```typescript
|
||||
import {sync, pull, push} from '@welshman/net'
|
||||
|
||||
// Three main operations:
|
||||
// 1. pull: Get events from relays
|
||||
// 2. push: Send events to relays
|
||||
// 3. sync: Bidirectional sync
|
||||
```
|
||||
|
||||
These utilities are primarily used by:
|
||||
- `Repository` for syncing with relays
|
||||
- `FeedController` for initial feed loading
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```typescript
|
||||
import {sync, pull, getFilterSelections} from '@welshman/net'
|
||||
|
||||
// Sync user profile data
|
||||
const syncProfiles = async (pubkeys: string[]) => {
|
||||
await sync({
|
||||
// What to sync
|
||||
filters: [{
|
||||
kinds: [0],
|
||||
authors: pubkeys
|
||||
}],
|
||||
|
||||
// Which relays
|
||||
relays: ctx.app.router
|
||||
.ForPubkeys(pubkeys)
|
||||
.getUrls(),
|
||||
|
||||
// Local events to consider
|
||||
events: repository.query([{
|
||||
kinds: [0],
|
||||
authors: pubkeys
|
||||
}])
|
||||
})
|
||||
}
|
||||
|
||||
// Initial feed load with negentropy
|
||||
const loadFeed = async () => {
|
||||
await pull({
|
||||
filters: [{
|
||||
kinds: [1],
|
||||
limit: 100
|
||||
}],
|
||||
relays: ctx.app.router
|
||||
.ForUser()
|
||||
.getUrls(),
|
||||
events: [], // No local events yet
|
||||
onEvent: (event) => {
|
||||
// Handle new events
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Sync operations:
|
||||
- Use NIP-77 when supported by relay
|
||||
- Fall back to traditional sync
|
||||
- Handle bidirectional sync
|
||||
- Support filtered sync
|
||||
- Track sync progress
|
||||
@@ -1,137 +0,0 @@
|
||||
# Targets
|
||||
|
||||
The targets system provides different strategies for message routing.
|
||||
Each target type implements a common interface for handling nostr messages but with different routing behaviors.
|
||||
|
||||
## Overview
|
||||
|
||||
Targets are used by the [Executor](/net/executor.md) class to:
|
||||
- Route messages to connections
|
||||
- Handle responses
|
||||
- Manage connection lifecycles
|
||||
- Combine multiple routing strategies
|
||||
|
||||
## Available Targets
|
||||
|
||||
### Echo Target
|
||||
Simple target that echoes messages back. Useful for testing.
|
||||
```typescript
|
||||
import {Echo} from '@welshman/net'
|
||||
|
||||
const echo = new Echo()
|
||||
echo.on('EVENT', (url, event) => {
|
||||
console.log('Echo received:', event)
|
||||
})
|
||||
```
|
||||
|
||||
### Local Target
|
||||
Connects to an in-memory relay implementation.
|
||||
```typescript
|
||||
import {Local} from '@welshman/net'
|
||||
import {Repository, Relay} from '@welshman/util'
|
||||
|
||||
// Create local relay
|
||||
const repository = new Repository()
|
||||
const relay = new Relay(repository)
|
||||
const local = new Local(relay)
|
||||
|
||||
// Use like any other target
|
||||
local.send(['REQ', 'sub1', {kinds: [1]}])
|
||||
```
|
||||
|
||||
### Relay Target
|
||||
Single relay connection target.
|
||||
```typescript
|
||||
import {Relay} from '@welshman/net'
|
||||
|
||||
const target = new Relay(connection)
|
||||
target.on('EVENT', (url, event) => {
|
||||
console.log(`Event from ${url}:`, event)
|
||||
})
|
||||
```
|
||||
|
||||
### Relays Target
|
||||
Manages multiple relay connections.
|
||||
```typescript
|
||||
import {Relays} from '@welshman/net'
|
||||
|
||||
const target = new Relays([
|
||||
connection1,
|
||||
connection2,
|
||||
connection3
|
||||
])
|
||||
```
|
||||
|
||||
### Multi Target
|
||||
Combines multiple targets into one.
|
||||
```typescript
|
||||
import {Multi, Local, Relays} from '@welshman/net'
|
||||
|
||||
// Create multi-target with local and remote relays
|
||||
const target = new Multi([
|
||||
new Local(localRelay),
|
||||
new Relays(remoteConnections)
|
||||
])
|
||||
```
|
||||
|
||||
## Real World Example
|
||||
|
||||
Here's how Coracle might set up its relay infrastructure:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
Executor,
|
||||
Multi,
|
||||
Local,
|
||||
Relays
|
||||
} from '@welshman/net'
|
||||
import {Repository, Relay} from '@welshman/util'
|
||||
|
||||
// Setup
|
||||
const setupRelayInfrastructure = () => {
|
||||
// Create local repository & relay
|
||||
const repository = new Repository()
|
||||
const localRelay = new Relay(repository)
|
||||
|
||||
// Get remote connections from pool
|
||||
const remoteConnections = [
|
||||
pool.get("wss://relay1.example.com"),
|
||||
pool.get("wss://relay2.example.com")
|
||||
]
|
||||
|
||||
// Create multi-target executor
|
||||
const executor = new Executor(
|
||||
new Multi([
|
||||
// Local relay for immediate responses
|
||||
new Local(localRelay),
|
||||
|
||||
// Remote relays for network queries
|
||||
new Relays(remoteConnections)
|
||||
])
|
||||
)
|
||||
|
||||
// Subscribe using combined target
|
||||
const sub = executor.subscribe(
|
||||
[{kinds: [1], limit: 10}],
|
||||
{
|
||||
onEvent: (url, event) => {
|
||||
if (url === LOCAL_RELAY_URL) {
|
||||
console.log('Got from cache:', event)
|
||||
} else {
|
||||
console.log('Got from network:', url, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return {executor, sub}
|
||||
}
|
||||
```
|
||||
|
||||
The target system allows for flexible relay configurations while maintaining a consistent interface for the rest of the application. This is particularly useful for:
|
||||
- Caching with local relays
|
||||
- Load balancing across relays
|
||||
- Fallback strategies
|
||||
- Testing and simulation
|
||||
|
||||
Each target type serves a specific purpose but can be combined using `Multi` for complex routing scenarios.
|
||||
+55
-57
@@ -1,72 +1,70 @@
|
||||
# Tracker
|
||||
|
||||
The Tracker is a simple but crucial class that keeps track of which relays an event was seen on or published to. It's essential for relay selection and event source tracking.
|
||||
Event tracker for managing which events have been seen from which relays, used for deduplication across multiple relay connections.
|
||||
|
||||
## Overview
|
||||
## Classes
|
||||
|
||||
### Tracker
|
||||
|
||||
Tracks the relationship between event IDs and relay URLs to prevent duplicate processing.
|
||||
|
||||
**Properties:**
|
||||
- `relaysById` - Map of event IDs to sets of relay URLs
|
||||
- `idsByRelay` - Map of relay URLs to sets of event IDs
|
||||
|
||||
**Methods:**
|
||||
- `getIds(relay)` - Gets all event IDs seen from a relay
|
||||
- `getRelays(eventId)` - Gets all relays that have sent an event
|
||||
- `hasRelay(eventId, relay)` - Checks if an event was seen from a relay
|
||||
- `addRelay(eventId, relay)` - Records that an event was seen from a relay
|
||||
- `removeRelay(eventId, relay)` - Removes the event-relay association
|
||||
- `track(eventId, relay)` - Tracks an event and returns true if already seen
|
||||
- `copy(eventId1, eventId2)` - Copies relay associations from one event to another
|
||||
- `load(relaysById)` - Loads tracker state from a map
|
||||
- `clear()` - Clears all tracked data
|
||||
|
||||
**Events:**
|
||||
- `add` - Emitted when event-relay association is added
|
||||
- `remove` - Emitted when event-relay association is removed
|
||||
- `load` - Emitted when tracker state is loaded
|
||||
- `clear` - Emitted when tracker is cleared
|
||||
|
||||
## Example
|
||||
|
||||
```typescript
|
||||
import {Tracker} from '@welshman/net'
|
||||
import {Tracker} from "@welshman/net"
|
||||
|
||||
const tracker = new Tracker()
|
||||
|
||||
// Track event source
|
||||
tracker.track(eventId, relayUrl)
|
||||
// Track events from different relays
|
||||
const isDuplicate1 = tracker.track("event123", "wss://relay1.com") // false
|
||||
const isDuplicate2 = tracker.track("event123", "wss://relay2.com") // false
|
||||
const isDuplicate3 = tracker.track("event123", "wss://relay1.com") // true (duplicate)
|
||||
|
||||
// Get relays for event
|
||||
const relays = tracker.getRelays(eventId) // Set<string>
|
||||
|
||||
// Get events from relay
|
||||
const events = tracker.getIds(relayUrl) // Set<string>
|
||||
|
||||
// Check specific relay
|
||||
const seen = tracker.hasRelay(eventId, relayUrl)
|
||||
// Check which relays have sent an event
|
||||
const relays = tracker.getRelays("event123") // Set(["wss://relay1.com", "wss://relay2.com"])
|
||||
```
|
||||
|
||||
## Used By
|
||||
If you're not using `@welshman/app`, you might want to track relays for all events that come through:
|
||||
|
||||
1. **Repository & Sync**
|
||||
```typescript
|
||||
// In sync operations
|
||||
pull({
|
||||
events,
|
||||
relays,
|
||||
onEvent: (event) => {
|
||||
tracker.track(event.id, relay)
|
||||
}
|
||||
import {Pool, Tracker, SocketEvent, isRelayEvent} from "@welshman/net"
|
||||
import {isEphemeralKind, isDVMKind, verifyEvent} from "@welshman/util"
|
||||
import {Repository} from "@welshman/relay"
|
||||
|
||||
const tracker = new Tracker()
|
||||
const repository = new Repository()
|
||||
|
||||
Pool.get().subscribe(socket => {
|
||||
socket.on(SocketEvent.Receive, message => {
|
||||
if (isRelayEvent(message)) {
|
||||
const event = message[2]
|
||||
|
||||
if (!isEphemeralKind(event.kind) && !isDVMKind(event.kind) && verifyEvent(event)) {
|
||||
tracker.track(event.id, socket.url)
|
||||
repository.publish(event)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
2. **Subscribe**
|
||||
```typescript
|
||||
// In @welshman/app subscribe
|
||||
sub.on('event', (url, event) => {
|
||||
// Track where we got the event
|
||||
tracker.track(event.id, url)
|
||||
})
|
||||
```
|
||||
|
||||
3. **Publish**
|
||||
```typescript
|
||||
// In publish operations
|
||||
pub.emitter.on('success', (url) => {
|
||||
// Track where we published
|
||||
tracker.track(event.id, url)
|
||||
})
|
||||
```
|
||||
|
||||
4. **Router**
|
||||
```typescript
|
||||
// Used for relay selection
|
||||
const relays = tracker
|
||||
.getRelays(event.id)
|
||||
.filter(url =>
|
||||
isHealthyRelay(url)
|
||||
)
|
||||
```
|
||||
|
||||
The Tracker:
|
||||
- Maps events to their source relays
|
||||
- Maps relays to their known events
|
||||
- Helps optimize relay selection
|
||||
|
||||
Think of it as a memory of where events came from, helping make better decisions about where to find or publish events.
|
||||
|
||||
Reference in New Issue
Block a user