2023-08-24 20:28:13 +00:00
|
|
|
import { type Event } from '@/deps.ts';
|
|
|
|
import { matchDittoFilters } from './filter.ts';
|
|
|
|
import { isEventLocal } from '@/utils.ts';
|
2023-08-24 04:25:38 +00:00
|
|
|
|
|
|
|
import type { DittoFilter } from '@/types.ts';
|
|
|
|
|
|
|
|
/** Nostr subscription to receive realtime events. */
|
|
|
|
interface Subscription {
|
|
|
|
/** User-defined NIP-01 subscription ID. */
|
|
|
|
id: string;
|
|
|
|
/** Event filters for the subscription. */
|
|
|
|
filters: DittoFilter[];
|
|
|
|
/** WebSocket to deliver results to. */
|
|
|
|
socket: WebSocket;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Manages Ditto event subscriptions.
|
|
|
|
*
|
|
|
|
* Subscriptions can be added, removed, and matched against events.
|
|
|
|
*
|
|
|
|
* ```ts
|
2023-08-24 20:28:13 +00:00
|
|
|
* for await (const sub of Sub.matches(event)) {
|
2023-08-24 04:25:38 +00:00
|
|
|
* // Send event to sub.socket
|
|
|
|
* sub.socket.send(JSON.stringify(event));
|
|
|
|
* }
|
|
|
|
* ```
|
|
|
|
*/
|
|
|
|
class SubscriptionStore {
|
|
|
|
#store = new Map<WebSocket, Map<string, Subscription>>();
|
|
|
|
|
|
|
|
/** Add a subscription to the store. */
|
|
|
|
sub(data: Subscription): void {
|
|
|
|
let subs = this.#store.get(data.socket);
|
|
|
|
|
|
|
|
if (!subs) {
|
|
|
|
subs = new Map();
|
|
|
|
this.#store.set(data.socket, subs);
|
|
|
|
}
|
|
|
|
|
|
|
|
subs.set(data.id, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Remove a subscription from the store. */
|
|
|
|
unsub(sub: Pick<Subscription, 'socket' | 'id'>): void {
|
|
|
|
this.#store.get(sub.socket)?.delete(sub.id);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Remove an entire socket. */
|
|
|
|
close(socket: WebSocket): void {
|
|
|
|
this.#store.delete(socket);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loop through matching subscriptions to stream out.
|
|
|
|
*
|
|
|
|
* ```ts
|
2023-08-24 20:28:13 +00:00
|
|
|
* for await (const sub of Sub.matches(event)) {
|
2023-08-24 04:25:38 +00:00
|
|
|
* // Send event to sub.socket
|
|
|
|
* sub.socket.send(JSON.stringify(event));
|
|
|
|
* }
|
|
|
|
* ```
|
|
|
|
*/
|
2023-08-24 20:28:13 +00:00
|
|
|
async *matches(event: Event) {
|
|
|
|
const isLocal = await isEventLocal(event);
|
|
|
|
|
2023-08-24 04:25:38 +00:00
|
|
|
for (const subs of this.#store.values()) {
|
|
|
|
for (const sub of subs.values()) {
|
2023-08-24 20:28:13 +00:00
|
|
|
if (matchDittoFilters(sub.filters, event, { isLocal })) {
|
2023-08-24 04:25:38 +00:00
|
|
|
yield sub;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const Sub = new SubscriptionStore();
|
|
|
|
|
|
|
|
export { Sub };
|