diff --git a/packages/app/src/session.ts b/packages/app/src/session.ts index 991d31d..a7ce034 100644 --- a/packages/app/src/session.ts +++ b/packages/app/src/session.ts @@ -1,13 +1,15 @@ import {derived, writable} from "svelte/store" -import {cached, omit, equals, assoc} from "@welshman/lib" +import {cached, randomId, append, omit, equals, assoc} from "@welshman/lib" import {withGetter} from "@welshman/store" import { + WrappedSigner, Nip46Broker, Nip46Signer, Nip07Signer, Nip01Signer, Nip55Signer, getPubkey, + ISigner, } from "@welshman/signer" export enum SessionMethod { @@ -175,13 +177,62 @@ export const loginWithPubkey = (pubkey: string) => addSession(makePubkeySession( export const nip46Perms = "sign_event:22242,nip04_encrypt,nip04_decrypt,nip44_encrypt,nip44_decrypt" +export enum SignerLogEntryStatus { + Pending = "pending", + Success = "success", + Failure = "failure", +} + +export type SignerLogEntry = { + id: string + method: string + status: SignerLogEntryStatus + duration: number +} + +export const signerLog = withGetter(writable([])) + +export const wrapSigner = (signer: ISigner) => + new WrappedSigner(signer, async (method: string, thunk: () => Promise) => { + const id = randomId() + const now = Date.now() + + signerLog.update(log => + append({id, method, status: SignerLogEntryStatus.Pending, duration: 0}, log), + ) + + try { + const result = await thunk() + + signerLog.update(log => + log.map(x => + x.id === id + ? {...x, status: SignerLogEntryStatus.Success, duration: Date.now() - now} + : x, + ), + ) + + return result + } catch (error: any) { + signerLog.update(log => + log.map(x => + x.id === id + ? {...x, status: SignerLogEntryStatus.Failure, duration: Date.now() - now} + : x, + ), + ) + + throw error + } + }) + export const getSigner = cached({ maxSize: 100, getKey: ([session]: [Session | undefined]) => `${session?.method}:${session?.pubkey}`, getValue: ([session]: [Session | undefined]) => { - if (isNip07Session(session)) return new Nip07Signer() - if (isNip01Session(session)) return new Nip01Signer(session.secret) - if (isNip55Session(session)) return new Nip55Signer(session.signer) + if (isNip07Session(session)) return wrapSigner(new Nip07Signer()) + if (isNip01Session(session)) return wrapSigner(new Nip01Signer(session.secret)) + if (isNip55Session(session)) return wrapSigner(new Nip55Signer(session.signer)) if (isNip46Session(session)) { const { secret: clientSecret, @@ -190,7 +241,7 @@ export const getSigner = cached({ const broker = new Nip46Broker({clientSecret, signerPubkey, relays}) const signer = new Nip46Signer(broker) - return signer + return wrapSigner(signer) } }, }) diff --git a/packages/app/src/storage.ts b/packages/app/src/storage.ts index ba21060..ba272de 100644 --- a/packages/app/src/storage.ts +++ b/packages/app/src/storage.ts @@ -22,7 +22,7 @@ export const ready = defer() export const dead = withGetter(writable(false)) -export const subs: Unsubscriber[] = [] +export const unsubscribers: Unsubscriber[] = [] export const getAll = async (name: string) => { await ready @@ -100,16 +100,18 @@ export const initStorage = async ( ready.resolve() - await Promise.all(Object.values(adapters).map(adapter => adapter.init())) + await Promise.all( + Object.values(adapters).map(async adapter => { + await adapter.init() - const unsubscribers = Object.values(adapters).map(adapter => adapter.sync()) - - return () => unsubscribers.forEach(call) + unsubscribers.push(adapter.sync()) + }), + ) } export const closeStorage = async () => { dead.set(true) - subs.forEach(unsub => unsub()) + unsubscribers.forEach(call) await db?.close() } diff --git a/packages/app/src/thunk.ts b/packages/app/src/thunk.ts index d2f0130..df49fed 100644 --- a/packages/app/src/thunk.ts +++ b/packages/app/src/thunk.ts @@ -108,7 +108,7 @@ export class Thunk { try { event = await signer.sign(event) } catch (e: any) { - return this._fail(String(e.error || e)) + return this._fail(`Failed to sign event: ${String(e.error || e)}`) } } diff --git a/packages/net/src/auth.ts b/packages/net/src/auth.ts index 7133f3f..31fada5 100644 --- a/packages/net/src/auth.ts +++ b/packages/net/src/auth.ts @@ -1,5 +1,5 @@ import EventEmitter from "events" -import {on, poll, call} from "@welshman/lib" +import {on, poll, call, tryCatch} from "@welshman/lib" import {SignedEvent, StampedEvent} from "@welshman/util" import {makeRelayAuth} from "@welshman/util" import {isRelayAuth, isClientAuth, isRelayOk, RelayMessage} from "./message.js" @@ -101,7 +101,7 @@ export class AuthState extends EventEmitter { this.setStatus(AuthStatus.PendingSignature) const template = makeRelayAuth(this.socket.url, this.challenge) - const event = await sign(template) + const event = await tryCatch(() => sign(template)) if (event) { this.request = event.id diff --git a/packages/signer/src/util.ts b/packages/signer/src/util.ts index 7dd9fb5..9e62ec9 100644 --- a/packages/signer/src/util.ts +++ b/packages/signer/src/util.ts @@ -3,7 +3,7 @@ import {bytesToHex, hexToBytes} from "@noble/hashes/utils" import * as nt04 from "nostr-tools/nip04" import * as nt44 from "nostr-tools/nip44" import {generateSecretKey, getPublicKey, getEventHash} from "nostr-tools/pure" -import {cached, now} from "@welshman/lib" +import {Emitter, cached, now} from "@welshman/lib" import {SignedEvent, HashedEvent, EventTemplate, StampedEvent, OwnedEvent} from "@welshman/util" export const makeSecret = () => bytesToHex(generateSecretKey()) @@ -64,3 +64,36 @@ export const decrypt = async (signer: ISigner, pubkey: string, message: string) nip04.detect(message) ? signer.nip04.decrypt(pubkey, message) : signer.nip44.decrypt(pubkey, message) + +export type SignerMethodWrapper = (method: string, thunk: () => Promise) => Promise + +export class WrappedSigner extends Emitter implements ISigner { + constructor( + private signer: ISigner, + private wrapMethod: SignerMethodWrapper, + ) { + super() + } + + sign(event: StampedEvent) { + return this.wrapMethod("sign", () => this.signer.sign(event)) + } + + getPubkey() { + return this.wrapMethod("getPubkey", () => this.signer.getPubkey()) + } + + nip04 = { + encrypt: async (pubkey: string, message: string) => + this.wrapMethod("nip04.encrypt", () => this.signer.nip04.encrypt(pubkey, message)), + decrypt: async (pubkey: string, message: string) => + this.wrapMethod("nip04.decrypt", () => this.signer.nip04.decrypt(pubkey, message)), + } + + nip44 = { + encrypt: async (pubkey: string, message: string) => + this.wrapMethod("nip44.encrypt", () => this.signer.nip44.encrypt(pubkey, message)), + decrypt: async (pubkey: string, message: string) => + this.wrapMethod("nip44.decrypt", () => this.signer.nip44.decrypt(pubkey, message)), + } +}