Add sign timeout to thunk

This commit is contained in:
Jon Staab
2025-09-16 11:56:00 -07:00
parent 80944e64a7
commit a3295dc2fe
10 changed files with 94 additions and 35 deletions
+4 -1
View File
@@ -20,8 +20,11 @@ import { makeEvent } from '@welshman/util'
import { ISigner, Nip01Signer, makeSecret } from '@welshman/signer' import { ISigner, Nip01Signer, makeSecret } from '@welshman/signer'
const signer: ISigner = new Nip01Signer(makeSecret()) const signer: ISigner = new Nip01Signer(makeSecret())
const options = {
signal: AbortSignal.timeout(10_000),
}
signer.sign(makeEvent(1)).then(signedEvent => console.log(signedEvent)) signer.sign(makeEvent(1), options).then(signedEvent => console.log(signedEvent))
``` ```
## Installation ## Installation
+7 -1
View File
@@ -5,9 +5,15 @@ It includes methods for signing messages, verifying signatures, and encrypting/d
```typescript ```typescript
export type SignOptions = {
signal?: AbortSignal
}
export type SignWithOptions = (event: StampedEvent, options?: SignOptions) => Promise<SignedEvent>
interface ISigner { interface ISigner {
// Core signing functionality // Core signing functionality
sign: (event: StampedEvent) => Promise<SignedEvent> sign: SignWithOptions
getPubkey: () => Promise<string> getPubkey: () => Promise<string>
// Encryption capabilities // Encryption capabilities
+1 -1
View File
@@ -8,7 +8,7 @@ class Nip01Signer implements ISigner {
constructor(private secret: string) constructor(private secret: string)
// ISigner implementation // ISigner implementation
sign: (event: StampedEvent) => Promise<SignedEvent> sign: SignWithOptions
getPubkey: () => Promise<string> getPubkey: () => Promise<string>
nip04: { encrypt, decrypt } nip04: { encrypt, decrypt }
nip44: { encrypt, decrypt } nip44: { encrypt, decrypt }
+1 -1
View File
@@ -55,7 +55,7 @@ class Nip55Signer implements ISigner {
constructor(private secret: string) constructor(private secret: string)
// ISigner implementation // ISigner implementation
sign: (event: StampedEvent) => Promise<SignedEvent> sign: SignWithOptions
getPubkey: () => Promise<string> getPubkey: () => Promise<string>
nip04: { encrypt, decrypt } nip04: { encrypt, decrypt }
nip44: { encrypt, decrypt } nip44: { encrypt, decrypt }
+4 -2
View File
@@ -132,9 +132,11 @@ export class Thunk {
} }
try { try {
event = await signer.sign(event) event = await signer.sign(event, {
signal: AbortSignal.timeout(15_000),
})
} catch (e: any) { } catch (e: any) {
return this._fail(`Failed to sign event: ${String(e.error || e)}`) return this._fail(String(e || "Failed to sign event"))
} }
} }
+14 -2
View File
@@ -1,5 +1,16 @@
import {StampedEvent} from "@welshman/util" import {StampedEvent} from "@welshman/util"
import {nip04, nip44, own, hash, sign, getPubkey, ISigner, makeSecret} from "../util.js" import {
nip04,
nip44,
own,
hash,
sign,
getPubkey,
ISigner,
SignOptions,
signWithOptions,
makeSecret,
} from "../util.js"
export class Nip01Signer implements ISigner { export class Nip01Signer implements ISigner {
#pubkey: string #pubkey: string
@@ -14,7 +25,8 @@ export class Nip01Signer implements ISigner {
getPubkey = async () => this.#pubkey getPubkey = async () => this.#pubkey
sign = async (event: StampedEvent) => sign(hash(own(event, this.#pubkey)), this.secret) sign = (event: StampedEvent, options: SignOptions = {}) =>
signWithOptions(sign(hash(own(event, this.#pubkey)), this.secret), options)
nip04 = { nip04 = {
encrypt: async (pubkey: string, message: string) => nip04.encrypt(pubkey, this.secret, message), encrypt: async (pubkey: string, message: string) => nip04.encrypt(pubkey, this.secret, message),
+14 -6
View File
@@ -1,5 +1,13 @@
import {StampedEvent} from "@welshman/util" import {StampedEvent} from "@welshman/util"
import {hash, own, Sign, ISigner, EncryptionImplementation} from "../util.js" import {
hash,
own,
signWithOptions,
SignOptions,
Sign,
ISigner,
EncryptionImplementation,
} from "../util.js"
export type Nip07 = { export type Nip07 = {
signEvent: Sign signEvent: Sign
@@ -33,11 +41,11 @@ export class Nip07Signer implements ISigner {
getPubkey = async () => this.#then<string>(ext => ext.getPublicKey() as string) getPubkey = async () => this.#then<string>(ext => ext.getPublicKey() as string)
sign = async (template: StampedEvent) => { sign = (template: StampedEvent, options: SignOptions = {}) =>
const event = hash(own(template, await this.getPubkey())) signWithOptions(
this.#then(async ext => ext.signEvent(hash(own(template, await ext.getPublicKey()!)))),
return this.#then(ext => ext.signEvent(event)) options,
} )
nip04 = { nip04 = {
encrypt: (pubkey: string, message: string) => encrypt: (pubkey: string, message: string) =>
+14 -3
View File
@@ -7,7 +7,15 @@ import {
NOSTR_CONNECT, NOSTR_CONNECT,
} from "@welshman/util" } from "@welshman/util"
import {publish, request, AdapterContext} from "@welshman/net" import {publish, request, AdapterContext} from "@welshman/net"
import {ISigner, EncryptionImplementation, decrypt, hash, own} from "../util.js" import {
ISigner,
EncryptionImplementation,
signWithOptions,
SignOptions,
decrypt,
hash,
own,
} from "../util.js"
import {Nip01Signer} from "./nip01.js" import {Nip01Signer} from "./nip01.js"
export type Nip46Context = { export type Nip46Context = {
@@ -463,6 +471,9 @@ export class Nip46Signer implements ISigner {
return this.pubkey return this.pubkey
} }
sign = async (template: StampedEvent) => sign = (template: StampedEvent, options: SignOptions = {}) =>
this.broker.signEvent(hash(own(template, await this.getPubkey()))) signWithOptions(
this.getPubkey().then(pubkey => this.broker.signEvent(hash(own(template, pubkey)))),
options,
)
} }
+17 -15
View File
@@ -1,7 +1,7 @@
import {NostrSignerPlugin, AppInfo} from "nostr-signer-capacitor-plugin" import {NostrSignerPlugin, AppInfo} from "nostr-signer-capacitor-plugin"
import {decode} from "nostr-tools/nip19" import {decode} from "nostr-tools/nip19"
import {SignedEvent, StampedEvent} from "@welshman/util" import {SignedEvent, StampedEvent} from "@welshman/util"
import {hash, own, ISigner} from "../util.js" import {hash, own, signWithOptions, SignOptions, ISigner} from "../util.js"
export const getNip55 = async (): Promise<AppInfo[]> => { export const getNip55 = async (): Promise<AppInfo[]> => {
const {apps} = await NostrSignerPlugin.getInstalledSignerApps() const {apps} = await NostrSignerPlugin.getInstalledSignerApps()
@@ -64,21 +64,23 @@ export class Nip55Signer implements ISigner {
}) })
} }
sign = async (template: StampedEvent): Promise<SignedEvent> => { sign = (template: StampedEvent, options: SignOptions = {}): Promise<SignedEvent> =>
const pubkey = await this.getPubkey() // hex-encoded public key signWithOptions(
const npub = this.#npub! // Bech32-encoded public key this.getPubkey().then(pubkey => {
const event = {sig: "", ...hash(own(template, pubkey))} const hashedEvent = hash(own(template, pubkey))
return this.#then(async signer => { return this.#then(async signer => {
const {event: signedEventJson} = await signer.signEvent({ const {event: json} = await signer.signEvent({
eventJson: JSON.stringify(event), eventJson: JSON.stringify({sig: "", ...hashedEvent}),
eventId: event.id, eventId: hashedEvent.id,
npub: npub, npub: this.#npub!,
}) })
const signedEvent = JSON.parse(signedEventJson) as SignedEvent
return signedEvent return JSON.parse(json) as SignedEvent
}) })
} }),
options,
)
nip04 = { nip04 = {
encrypt: async (recipientPubKey: string, message: string): Promise<string> => { encrypt: async (recipientPubKey: string, message: string): Promise<string> => {
+18 -3
View File
@@ -44,6 +44,12 @@ export const nip44 = {
export type Sign = (event: StampedEvent) => Promise<SignedEvent> export type Sign = (event: StampedEvent) => Promise<SignedEvent>
export type SignOptions = {
signal?: AbortSignal
}
export type SignWithOptions = (event: StampedEvent, options?: SignOptions) => Promise<SignedEvent>
export type Encrypt = (pubkey: string, message: string) => Promise<string> export type Encrypt = (pubkey: string, message: string) => Promise<string>
export type Decrypt = (pubkey: string, message: string) => Promise<string> export type Decrypt = (pubkey: string, message: string) => Promise<string>
@@ -54,7 +60,7 @@ export type EncryptionImplementation = {
} }
export interface ISigner { export interface ISigner {
sign: Sign sign: SignWithOptions
nip04: EncryptionImplementation nip04: EncryptionImplementation
nip44: EncryptionImplementation nip44: EncryptionImplementation
getPubkey: () => Promise<string> getPubkey: () => Promise<string>
@@ -75,8 +81,8 @@ export class WrappedSigner extends Emitter implements ISigner {
super() super()
} }
sign(event: StampedEvent) { sign(event: StampedEvent, options: SignOptions = {}) {
return this.wrapMethod("sign", () => this.signer.sign(event)) return this.wrapMethod("sign", () => this.signer.sign(event, options))
} }
getPubkey() { getPubkey() {
@@ -97,3 +103,12 @@ export class WrappedSigner extends Emitter implements ISigner {
this.wrapMethod("nip44.decrypt", () => this.signer.nip44.decrypt(pubkey, message)), this.wrapMethod("nip44.decrypt", () => this.signer.nip44.decrypt(pubkey, message)),
} }
} }
export const signWithOptions = (
promise: Promise<SignedEvent> | SignedEvent,
options: SignOptions,
) =>
new Promise<SignedEvent>((resolve, reject) => {
Promise.resolve(promise).then(resolve)
options.signal?.addEventListener("abort", () => reject("Signing was aborted"))
})