@nostr-dev-kit/ndk
Version:
NDK - Nostr Development Kit
133 lines (113 loc) • 4.47 kB
text/typescript
import type { NDKEvent, NostrEvent } from "../events";
import { NDKKind } from "../events/kinds";
import { NDKDVMJobFeedback } from "../events/kinds/dvm";
import { NDKDVMRequest } from "../events/kinds/dvm/request";
import type { NDKSubscription } from "../subscription";
import type { NDKUser } from "../user";
function addRelays(event: NDKEvent, relays?: string[]) {
const tags = [];
if (!relays || relays.length === 0) {
const poolRelays = event.ndk?.pool.relays;
relays = poolRelays ? Object.keys(poolRelays) : undefined;
}
if (relays && relays.length > 0) tags.push(["relays", ...relays]);
return tags;
}
/**
* Schedule a post for publishing at a later time using * a NIP-90 DVM.
*
* @param dvm {NDKUser} The DVM to use for scheduling.
* @param relays {string[]} The relays the schedule event should be published to by the DVM. Defaults to all relays in the pool.
* @param encrypted {boolean} Whether to encrypt the event. Defaults to true.
* @param waitForConfirmationForMs {number} How long to wait for the DVM to confirm the schedule event. If none is provided, the event will be scheduled but not confirmed.
*
* @example
* const event = new NDKEvent(ndk, { kind: 1, content: "hello world" });
* event.created_at = Date.now()/1000 + 60 // schedule for 60 seconds from now
* await event.sign();
*
* const dvm = ndk.getUser({ pubkey: "<a-kind-5905-dvm-pubkey>" });
*
* const result = await dvmSchedule(event, dvm);
* console.log(result.status); // "success"
*/
export async function dvmSchedule(
events: NDKEvent | NDKEvent[],
dvm: NDKUser,
relays?: string[],
encrypted = true,
waitForConfirmationForMs?: number,
) {
if (!Array.isArray(events)) {
events = [events];
}
const ndk = events[0].ndk;
if (!ndk) throw new Error("NDK not set");
for (const event of events) {
// check the event has a future date and that it's signed
if (!event.sig) throw new Error("Event not signed");
if (!event.created_at) throw new Error("Event has no date");
if (!dvm) throw new Error("No DVM specified");
if (event.created_at <= Date.now() / 1000) throw new Error("Event needs to be in the future");
}
const scheduleEvent = new NDKDVMRequest(ndk, {
kind: NDKKind.DVMEventSchedule,
} as NostrEvent);
for (const event of events) {
scheduleEvent.addInput(JSON.stringify(event.rawEvent()), "text");
}
scheduleEvent.tags.push(...addRelays(events[0], relays));
if (encrypted) {
await scheduleEvent.encryption(dvm);
} else {
scheduleEvent.dvm = dvm;
}
await scheduleEvent.sign();
let res: NDKSubscription | undefined;
if (waitForConfirmationForMs) {
res = ndk.subscribe(
{
kinds: [NDKKind.DVMEventSchedule + 1000, NDKKind.DVMJobFeedback],
...scheduleEvent.filter(),
},
{ groupable: false, closeOnEose: false },
);
}
const timeoutPromise = new Promise<string>((reject) => {
setTimeout(() => {
res?.stop();
reject("Timeout waiting for an answer from the DVM");
}, waitForConfirmationForMs);
});
const schedulePromise = new Promise<NDKDVMJobFeedback | NDKEvent | string | undefined>((resolve, reject) => {
if (waitForConfirmationForMs) {
res?.on("event", async (e: NDKEvent) => {
res?.stop();
if (e.kind === NDKKind.DVMJobFeedback) {
const feedback = await NDKDVMJobFeedback.from(e);
if (feedback.status === "error") {
const statusTag = feedback.getMatchingTags("status");
reject(statusTag?.[2] ?? feedback);
} else {
resolve(feedback);
}
}
resolve(e);
});
}
scheduleEvent.publish().then(() => {
if (!waitForConfirmationForMs) resolve(undefined);
});
});
return new Promise<NDKEvent | string | undefined>((resolve, reject) => {
if (waitForConfirmationForMs) {
Promise.race([timeoutPromise, schedulePromise])
.then((e) => {
resolve(e);
})
.catch(reject);
} else {
schedulePromise.then(resolve);
}
});
}