Browse Source

Use functional programming for self-cleaning WebSocket callbacks

master
buttercat1791 8 months ago
parent
commit
ff4162739a
  1. 63
      src/lib/utils/websocket_utils.ts

63
src/lib/utils/websocket_utils.ts

@ -22,13 +22,18 @@ export interface NostrFilter { @@ -22,13 +22,18 @@ export interface NostrFilter {
limit?: number;
}
export async function fetchNostrEvent(filter: NostrFilter): Promise<NostrEvent> {
// TODO: Improve relay selection when relay management is implemented.
const ws = await WebSocketPool.instance.acquire("wss://thecitadel.nostr1.com");
const subId = crypto.randomUUID();
type ResolveCallback<T> = (value: T | PromiseLike<T>) => void;
type RejectCallback = (reason?: any) => void;
type EventHandler = (ev: Event) => void;
type EventHandlerReject = (reject: RejectCallback) => EventHandler;
type EventHandlerResolve<T> = (resolve: ResolveCallback<T>) => EventHandlerReject;
const res = new Promise<NostrEvent>((resolve, reject) => {
ws.addEventListener("message", (ev) => {
function handleMessage(
ev: MessageEvent,
subId: string,
resolve: (event: NostrEvent) => void,
reject: (reason: any) => void
) {
const data = JSON.parse(ev.data);
if (data[1] !== subId) {
@ -52,12 +57,52 @@ export async function fetchNostrEvent(filter: NostrFilter): Promise<NostrEvent> @@ -52,12 +57,52 @@ export async function fetchNostrEvent(filter: NostrFilter): Promise<NostrEvent>
}
resolve(event);
});
}
ws.addEventListener("error", (ev) => {
function handleError(
ev: Event,
reject: (reason: any) => void
) {
reject(ev);
}
export async function fetchNostrEvent(filter: NostrFilter): Promise<NostrEvent> {
// TODO: Improve relay selection when relay management is implemented.
const ws = await WebSocketPool.instance.acquire("wss://thecitadel.nostr1.com");
const subId = crypto.randomUUID();
// AI-NOTE: Currying is used here to abstract the internal handler logic away from the WebSocket
// handling logic. The message and error handlers themselves can be refactored without affecting
// the WebSocket handling logic.
const curriedMessageHandler: (subId: string) => EventHandlerResolve<NostrEvent> =
(subId) =>
(resolve) =>
(reject) =>
(ev: MessageEvent) =>
handleMessage(ev, subId, resolve, reject);
const curriedErrorHandler: EventHandlerReject =
(reject) =>
(ev: Event) =>
handleError(ev, reject);
// AI-NOTE: These variables store references to partially-applied handlers so that the `finally`
// block receives the correct references to clean up the listeners.
let messageHandler: EventHandler;
let errorHandler: EventHandler;
const res = new Promise<NostrEvent>((resolve, reject) => {
messageHandler = curriedMessageHandler(subId)(resolve)(reject);
errorHandler = curriedErrorHandler(reject);
ws.addEventListener("message", messageHandler);
ws.addEventListener("error", errorHandler);
})
.withTimeout(2000)
.finally(() => {
ws.removeEventListener("message", messageHandler);
ws.removeEventListener("error", errorHandler);
WebSocketPool.instance.release(ws);
});
}).withTimeout(2000);
ws.send(JSON.stringify(["REQ", subId, filter]));
return res;

Loading…
Cancel
Save