import { bech32ToHex } from "@snort/shared" import debug from "debug" import { Nip55SignerTimeout } from "../const" import type { NostrEvent, NotSignedNostrEvent } from "../nostr" import type { EventSigner } from "../signer" export class Nip55Signer implements EventSigner { #log = debug("NIP-55") #queue: Array<{ id: string; resolve: (o: any) => void; reject: () => void }> = [] init(): Promise { // nothing here return Promise.resolve() } async getPubKey() { let pk = await this.#call("get_public_key", "signature") if (pk.startsWith("npub")) { pk = bech32ToHex(pk) } return pk } async nip4Encrypt(content: string, key: string) { return await this.#call("nip04_encrypt", "signature", content, new Map([["pubkey", key]])) } async nip4Decrypt(content: string, otherKey: string) { return await this.#call("nip04_decrypt", "signature", content, new Map([["pubkey", otherKey]])) } async nip44Encrypt(content: string, key: string) { return await this.#call("nip44_encrypt", "signature", content, new Map([["pubkey", key]])) } async nip44Decrypt(content: string, otherKey: string) { return await this.#call("nip44_decrypt", "signature", content, new Map([["pubkey", otherKey]])) } async sign(ev: NostrEvent | NotSignedNostrEvent) { const evRet = await this.#call("sign_event", "event", ev) return JSON.parse(evRet) } get supports(): string[] { return ["nip04", "nip44"] } #call( method: string, returnType: string, obj?: NostrEvent | NotSignedNostrEvent | string, otherParams?: Map, ) { const id = crypto.randomUUID() const objString = typeof obj === "string" ? obj : obj != undefined ? JSON.stringify(obj) : undefined const params = new URLSearchParams() params.append("compressionType", "none") params.append("returnType", returnType) params.append("type", method) if (otherParams) { for (const [k, v] of otherParams) { params.append(k, v) } } return new Promise((resolve, reject) => { const t = setInterval(async () => { if (document.hasFocus()) { const text = await navigator.clipboard.readText() if (text) { this.#log("Response: %s", text) await navigator.clipboard.writeText("") clearInterval(t) clearTimeout(timeout) resolve(text) } } }, 500) const timeout = setTimeout(() => { clearInterval(t) reject(new Error(`NIP-55 signer timed out after ${Nip55SignerTimeout}ms — no clipboard response received`)) }, Nip55SignerTimeout) const dst = `nostrsigner:${objString ?? ""}?${params.toString()}` this.#log("Sending command %s, %s", id, dst) globalThis.location.href = dst }) } }