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'
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
+7 -1
View File
@@ -5,9 +5,15 @@ It includes methods for signing messages, verifying signatures, and encrypting/d
```typescript
export type SignOptions = {
signal?: AbortSignal
}
export type SignWithOptions = (event: StampedEvent, options?: SignOptions) => Promise<SignedEvent>
interface ISigner {
// Core signing functionality
sign: (event: StampedEvent) => Promise<SignedEvent>
sign: SignWithOptions
getPubkey: () => Promise<string>
// Encryption capabilities
+1 -1
View File
@@ -8,7 +8,7 @@ class Nip01Signer implements ISigner {
constructor(private secret: string)
// ISigner implementation
sign: (event: StampedEvent) => Promise<SignedEvent>
sign: SignWithOptions
getPubkey: () => Promise<string>
nip04: { encrypt, decrypt }
nip44: { encrypt, decrypt }
+1 -1
View File
@@ -55,7 +55,7 @@ class Nip55Signer implements ISigner {
constructor(private secret: string)
// ISigner implementation
sign: (event: StampedEvent) => Promise<SignedEvent>
sign: SignWithOptions
getPubkey: () => Promise<string>
nip04: { encrypt, decrypt }
nip44: { encrypt, decrypt }
+4 -2
View File
@@ -132,9 +132,11 @@ export class Thunk {
}
try {
event = await signer.sign(event)
event = await signer.sign(event, {
signal: AbortSignal.timeout(15_000),
})
} 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 {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 {
#pubkey: string
@@ -14,7 +25,8 @@ export class Nip01Signer implements ISigner {
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 = {
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 {hash, own, Sign, ISigner, EncryptionImplementation} from "../util.js"
import {
hash,
own,
signWithOptions,
SignOptions,
Sign,
ISigner,
EncryptionImplementation,
} from "../util.js"
export type Nip07 = {
signEvent: Sign
@@ -33,11 +41,11 @@ export class Nip07Signer implements ISigner {
getPubkey = async () => this.#then<string>(ext => ext.getPublicKey() as string)
sign = async (template: StampedEvent) => {
const event = hash(own(template, await this.getPubkey()))
return this.#then(ext => ext.signEvent(event))
}
sign = (template: StampedEvent, options: SignOptions = {}) =>
signWithOptions(
this.#then(async ext => ext.signEvent(hash(own(template, await ext.getPublicKey()!)))),
options,
)
nip04 = {
encrypt: (pubkey: string, message: string) =>
+14 -3
View File
@@ -7,7 +7,15 @@ import {
NOSTR_CONNECT,
} from "@welshman/util"
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"
export type Nip46Context = {
@@ -463,6 +471,9 @@ export class Nip46Signer implements ISigner {
return this.pubkey
}
sign = async (template: StampedEvent) =>
this.broker.signEvent(hash(own(template, await this.getPubkey())))
sign = (template: StampedEvent, options: SignOptions = {}) =>
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 {decode} from "nostr-tools/nip19"
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[]> => {
const {apps} = await NostrSignerPlugin.getInstalledSignerApps()
@@ -64,21 +64,23 @@ export class Nip55Signer implements ISigner {
})
}
sign = async (template: StampedEvent): Promise<SignedEvent> => {
const pubkey = await this.getPubkey() // hex-encoded public key
const npub = this.#npub! // Bech32-encoded public key
const event = {sig: "", ...hash(own(template, pubkey))}
sign = (template: StampedEvent, options: SignOptions = {}): Promise<SignedEvent> =>
signWithOptions(
this.getPubkey().then(pubkey => {
const hashedEvent = hash(own(template, pubkey))
return this.#then(async signer => {
const {event: signedEventJson} = await signer.signEvent({
eventJson: JSON.stringify(event),
eventId: event.id,
npub: npub,
})
const signedEvent = JSON.parse(signedEventJson) as SignedEvent
return signedEvent
})
}
return this.#then(async signer => {
const {event: json} = await signer.signEvent({
eventJson: JSON.stringify({sig: "", ...hashedEvent}),
eventId: hashedEvent.id,
npub: this.#npub!,
})
return JSON.parse(json) as SignedEvent
})
}),
options,
)
nip04 = {
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 SignOptions = {
signal?: AbortSignal
}
export type SignWithOptions = (event: StampedEvent, options?: SignOptions) => Promise<SignedEvent>
export type Encrypt = (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 {
sign: Sign
sign: SignWithOptions
nip04: EncryptionImplementation
nip44: EncryptionImplementation
getPubkey: () => Promise<string>
@@ -75,8 +81,8 @@ export class WrappedSigner extends Emitter implements ISigner {
super()
}
sign(event: StampedEvent) {
return this.wrapMethod("sign", () => this.signer.sign(event))
sign(event: StampedEvent, options: SignOptions = {}) {
return this.wrapMethod("sign", () => this.signer.sign(event, options))
}
getPubkey() {
@@ -97,3 +103,12 @@ export class WrappedSigner extends Emitter implements ISigner {
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"))
})