4.2 KiB
title, description
| title | description |
|---|---|
| Custom Relay Handler Development | Learn how to create a custom relay handler for the @contextvm/sdk. |
Custom Relay Handler Development
The @contextvm/sdk's-pluggable architecture, centered around the RelayHandler interface, allows developers to implement custom logic for managing Nostr-relay connections. This is particularly useful for advanced use cases that require more sophisticated behavior than what the default SimpleRelayPool provides.
Why Create a Custom Relay Handler?
You might want to create a custom RelayHandler for several reasons:
- Intelligent Relay Selection: To dynamically select relays based on performance, reliability, or the specific type of data being requested. For example, you might use a different set of relays for fetching user metadata versus broadcasting messages.
- Auth Relays: To integrate with auth relays that require authentication or specific connection logic.
- Dynamic Relay Discovery: To discover and connect to new relays at runtime, rather than using a static list.
- Custom Caching: To implement a custom caching layer to reduce redundant requests to relays.
- Resiliency and-failover: To build more robust-failover logic, such as automatically retrying failed connections or switching to backup relays.
Implementing the RelayHandler Interface
To create a custom relay handler, you need to create a class that implements the RelayHandler interface. This involves implementing five methods: connect, disconnect, publish, subscribe, and unsubscribe.
Example: A Handler with logging
Here is a simple example of a custom RelayHandler that wraps the default SimpleRelayPool and adds logging to each operation. This illustrates how you can extend or compose existing handlers.
import { RelayHandler } from '@ctxvm/sdk/core';
import { SimpleRelayPool } from '@ctxvm/sdk/relay';
import { Filter, NostrEvent } from 'nostr-tools';
class LoggingRelayHandler implements RelayHandler {
private readonly innerHandler: RelayHandler;
constructor(relayUrls: string[]) {
this.innerHandler = new SimpleRelayPool(relayUrls);
console.log(`[LoggingRelayHandler] Initialized with relays: ${relayUrls.join(', ')}`);
}
async connect(): Promise<void> {
console.log('[LoggingRelayHandler] Attempting to connect...');
await this.innerHandler.connect();
console.log('[LoggingRelayHandler] Connected successfully.');
}
async disconnect(): Promise<void> {
console.log('[LoggingRelayHandler] Disconnecting...');
await this.innerHandler.disconnect();
console.log('[LoggingRelayHandler] Disconnected.');
}
publish(event: NostrEvent): void {
console.log(`[LoggingRelayHandler] Publishing event kind ${event.kind}...`);
this.innerHandler.publish(event);
}
subscribe(filters: Filter[], onEvent: (event: NostrEvent) => void): void {
console.log(`[LoggingRelayHandler] Subscribing with filters:`, filters);
this.innerHandler.subscribe(filters, (event) => {
console.log(`[LoggingRelayHandler] Received event kind ${event.kind}`);
onEvent(event);
});
}
unsubscribe(): void {
console.log('[LoggingRelayHandler] Unsubscribing from all.');
this.innerHandler.unsubscribe();
}
}
// Usage
const loggingHandler = new LoggingRelayHandler(['wss://relay.damus.io']);
const transport = new NostrClientTransport({
relayHandler: loggingHandler,
// ... other options
});
This example demonstrates the composition pattern. For a more advanced handler, you might use a different underlying relay management library or implement the connection logic from scratch using WebSockets.
Using Your Custom Relay Handler
Once your custom handler class is created, you can instantiate it and pass it to any component that requires a RelayHandler, such as the NostrClientTransport or NostrServerTransport. The SDK will then use your custom logic for all relay interactions.
Next Steps
With the Relay component covered, we will now look at the high-level bridging components provided by the SDK.