nostr-relaypool
Version:
A Nostr RelayPool implementation in TypeScript using only nostr-tools library as a dependency.
186 lines (160 loc) • 5.25 kB
text/typescript
import type {Event} from "nostr-tools";
import type {OnEose, OnEvent, SubscriptionOptions} from "./relay-pool";
export class RelayPoolWorker {
// eslint-disable-next-line no-undef
private worker: Worker;
private subscriptionCallbacks = new Map<
number | string,
{onEvent: OnEvent; onEose?: OnEose}
>();
constructor(
// eslint-disable-next-line no-undef
worker: Worker,
relays: string[] = [],
options: {
useEventCache?: boolean;
logSubscriptions?: boolean;
deleteSignatures?: boolean;
skipVerification?: boolean;
autoReconnect?: boolean;
} = {}
) {
this.worker = worker;
this.worker.onmessage = this.handleWorkerMessage.bind(this);
this.worker.postMessage({
action: "create_relay_pool",
data: {relays, options},
});
}
private handleWorkerMessage(event: MessageEvent) {
const {type, subscriptionId, ...rest} = event.data;
if (type === "event" || type === "eose") {
const callbacks = this.subscriptionCallbacks.get(subscriptionId);
if (callbacks) {
if (type === "event") {
callbacks.onEvent(rest.event, rest.isAfterEose, rest.relayURL);
} else if (type === "eose" && callbacks.onEose) {
callbacks.onEose(rest.relayURL, rest.minCreatedAt);
}
}
} else if (type === "subscribed") {
// Do nothing
} else if (type === "metadata") {
// Need better handling
} else if (type === "contactList") {
// Need better handling
} else {
console.warn("Unhandled message from worker:", event.data);
}
}
subscribe(
filters: any,
relays: string[] | undefined,
onEvent: OnEvent,
maxDelayms?: number,
onEose?: OnEose,
options: SubscriptionOptions = {}
): () => void {
const subscriptionId = Math.random().toString(36).slice(2, 9);
this.subscriptionCallbacks.set(subscriptionId, {onEvent, onEose});
this.worker.postMessage({
action: "subscribe",
data: {
filters,
relays,
maxDelayms,
onEose: !!onEose,
options,
subscriptionId,
},
});
return () => {
this.subscriptionCallbacks.delete(subscriptionId);
this.worker.postMessage({action: "unsubscribe", data: {subscriptionId}});
};
}
publish(event: Event, relays: string[]) {
this.worker.postMessage({action: "publish", data: {event, relays}});
}
setWriteRelaysForPubKey(pubkey: string, writeRelays: string[]) {
this.worker.postMessage({
action: "set_write_relays_for_pub_key",
data: {pubkey, writeRelays},
});
}
subscribeReferencedEvents(
event: Event,
onEvent: OnEvent,
maxDelayms?: number,
onEose?: OnEose,
options: SubscriptionOptions = {}
): () => void {
const subscriptionId = Math.random().toString(36).slice(2, 9);
this.subscriptionCallbacks.set(subscriptionId, {onEvent, onEose});
this.worker.postMessage({
action: "subscribe_referenced_events",
data: {event, maxDelayms, onEose: !!onEose, options, subscriptionId},
});
return () => {
this.subscriptionCallbacks.delete(subscriptionId);
this.worker.postMessage({action: "unsubscribe", data: {subscriptionId}});
};
}
fetchAndCacheMetadata(pubkey: string): Promise<Event> {
return new Promise((resolve) => {
const listener = (event: MessageEvent) => {
if (event.data.type === "metadata" && event.data.pubkey === pubkey) {
this.worker.removeEventListener("message", listener);
resolve(event.data.metadata);
}
};
this.worker.addEventListener("message", listener);
this.worker.postMessage({
action: "fetch_and_cache_metadata",
data: {pubkey},
});
});
}
fetchAndCacheContactList(pubkey: string): Promise<Event> {
return new Promise((resolve) => {
const listener = (event: MessageEvent) => {
if (event.data.type === "contactList" && event.data.pubkey === pubkey) {
this.worker.removeEventListener("message", listener);
resolve(event.data.contactList);
}
};
this.worker.addEventListener("message", listener);
this.worker.postMessage({
action: "fetch_and_cache_contact_list",
data: {pubkey},
});
});
}
subscribeReferencedEventsAndPrefetchMetadata(
event: Event,
onEvent: OnEvent,
maxDelayms?: number,
onEose?: OnEose,
options: SubscriptionOptions = {}
): () => void {
const subscriptionId = Math.random().toString(36).slice(2, 9);
this.subscriptionCallbacks.set(subscriptionId, {onEvent, onEose});
this.worker.postMessage({
action: "subscribe_referenced_events_and_prefetch_metadata",
data: {event, maxDelayms, onEose: !!onEose, options, subscriptionId},
});
return () => {
this.subscriptionCallbacks.delete(subscriptionId);
this.worker.postMessage({action: "unsubscribe", data: {subscriptionId}});
};
}
setCachedMetadata(pubkey: string, metadata: Event) {
this.worker.postMessage({
action: "set_cached_metadata",
data: {pubkey, metadata},
});
}
close() {
this.worker.postMessage({action: "close"});
}
}