divine-signer 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,12 @@
1
1
  import { type BunkerSignerParams } from 'nostr-tools/nip46';
2
2
  import type { EventTemplate, VerifiedEvent } from 'nostr-tools/pure';
3
3
  import type { NostrSigner, SignerType } from './types';
4
+ export interface NostrConnectHandle {
5
+ /** Call after showing the QR code. Resolves when the remote signer acks. */
6
+ waitForSigner(): Promise<BunkerNIP44Signer>;
7
+ /** Abort the connection attempt. */
8
+ abort(): void;
9
+ }
4
10
  export declare class BunkerNIP44Signer implements NostrSigner {
5
11
  readonly type: SignerType;
6
12
  private readonly inner;
@@ -11,17 +17,23 @@ export declare class BunkerNIP44Signer implements NostrSigner {
11
17
  *
12
18
  * Unlike fromBunkerUrl(), this does NOT send a `connect` RPC — the session
13
19
  * already exists on the remote signer. It sets up the relay subscription
14
- * and sends a `ping` to verify the channel is alive.
20
+ * and calls getPublicKey() to verify the channel is alive (and prime the cache).
15
21
  */
16
22
  static reconnect(clientSecretKey: Uint8Array, bunkerUrl: string, params?: BunkerSignerParams, connectTimeout?: number): Promise<BunkerNIP44Signer>;
23
+ /**
24
+ * Two-phase nostrconnect: sets up relay connections and subscription,
25
+ * then returns a handle. Show the QR code, then call handle.waitForSigner().
26
+ *
27
+ * This avoids a race where the user scans before the subscription is live.
28
+ */
29
+ static prepareNostrConnect(connectionURI: string, clientSecretKey: Uint8Array, params?: BunkerSignerParams, timeoutOrAbort?: number | AbortSignal): Promise<NostrConnectHandle>;
17
30
  /**
18
31
  * Connect via a nostrconnect:// URI (QR code flow).
19
32
  *
20
- * Implements the connect handshake manually instead of using
21
- * BunkerSigner.fromURI, which sends a `switch_relays` RPC that
22
- * signers like Primal don't understand (causing a parse error and
23
- * potentially disrupting the session). After the handshake we create
24
- * the signer via BunkerSigner.fromBunker which skips switch_relays.
33
+ * Convenience wrapper around prepareNostrConnect() connects to relays,
34
+ * subscribes, and waits for the ack in one call. If you need to show a
35
+ * QR code only after the subscription is live, use prepareNostrConnect()
36
+ * instead.
25
37
  */
26
38
  static fromNostrConnect(connectionURI: string, clientSecretKey: Uint8Array, params?: BunkerSignerParams, timeoutOrAbort?: number | AbortSignal): Promise<BunkerNIP44Signer>;
27
39
  getPublicKey(): Promise<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"bunker-signer.d.ts","sourceRoot":"","sources":["../src/bunker-signer.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAI3B,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEvD,qBAAa,iBAAkB,YAAW,WAAW;IACnD,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IAErC,OAAO;IAKP,wDAAwD;WAC3C,aAAa,CACxB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,kBAAkB,EAC3B,YAAY,CAAC,EAAE,UAAU,EACzB,cAAc,SAAS,GACtB,OAAO,CAAC,iBAAiB,CAAC;IAgB7B;;;;;OAKG;WACU,SAAS,CACpB,eAAe,EAAE,UAAU,EAC3B,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,kBAAkB,EAC3B,cAAc,SAAS,GACtB,OAAO,CAAC,iBAAiB,CAAC;IAkB7B;;;;;;;;OAQG;WACU,gBAAgB,CAC3B,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,UAAU,EAC3B,MAAM,CAAC,EAAE,kBAAkB,EAC3B,cAAc,CAAC,EAAE,MAAM,GAAG,WAAW,GACpC,OAAO,CAAC,iBAAiB,CAAC;IAgFvB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAW/B,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;IAIvD,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIhE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIjE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIhE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIvE,YAAY,IAAI,MAAM;IAIhB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
1
+ {"version":3,"file":"bunker-signer.d.ts","sourceRoot":"","sources":["../src/bunker-signer.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAI3B,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEvD,MAAM,WAAW,kBAAkB;IACjC,4EAA4E;IAC5E,aAAa,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC5C,oCAAoC;IACpC,KAAK,IAAI,IAAI,CAAC;CACf;AAED,qBAAa,iBAAkB,YAAW,WAAW;IACnD,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IAErC,OAAO;IAKP,wDAAwD;WAC3C,aAAa,CACxB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,kBAAkB,EAC3B,YAAY,CAAC,EAAE,UAAU,EACzB,cAAc,SAAS,GACtB,OAAO,CAAC,iBAAiB,CAAC;IAgB7B;;;;;OAKG;WACU,SAAS,CACpB,eAAe,EAAE,UAAU,EAC3B,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,kBAAkB,EAC3B,cAAc,SAAS,GACtB,OAAO,CAAC,iBAAiB,CAAC;IAoB7B;;;;;OAKG;WACU,mBAAmB,CAC9B,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,UAAU,EAC3B,MAAM,CAAC,EAAE,kBAAkB,EAC3B,cAAc,CAAC,EAAE,MAAM,GAAG,WAAW,GACpC,OAAO,CAAC,kBAAkB,CAAC;IA2F9B;;;;;;;OAOG;WACU,gBAAgB,CAC3B,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,UAAU,EAC3B,MAAM,CAAC,EAAE,kBAAkB,EAC3B,cAAc,CAAC,EAAE,MAAM,GAAG,WAAW,GACpC,OAAO,CAAC,iBAAiB,CAAC;IAUvB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAW/B,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;IAIvD,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIhE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIjE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIhE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIvE,YAAY,IAAI,MAAM;IAIhB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export type { TokenRefreshResult } from './oauth-signer';
5
5
  export { NsecSigner } from './nsec-signer';
6
6
  export { ExtensionSigner } from './extension-signer';
7
7
  export { BunkerNIP44Signer } from './bunker-signer';
8
+ export type { NostrConnectHandle } from './bunker-signer';
8
9
  export { OAuthSigner, OAuthError } from './oauth-signer';
9
10
  export { createSessionStore, restoreSession } from './session';
10
11
  export { buildOAuthUrl, exchangeCode } from './oauth';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACvD,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC7E,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtE,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGzD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAG/D,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACvD,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC7E,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtE,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGzD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAG/D,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -112,7 +112,7 @@ var BunkerNIP44Signer = class _BunkerNIP44Signer {
112
112
  *
113
113
  * Unlike fromBunkerUrl(), this does NOT send a `connect` RPC — the session
114
114
  * already exists on the remote signer. It sets up the relay subscription
115
- * and sends a `ping` to verify the channel is alive.
115
+ * and calls getPublicKey() to verify the channel is alive (and prime the cache).
116
116
  */
117
117
  static async reconnect(clientSecretKey, bunkerUrl, params, connectTimeout = 3e4) {
118
118
  const bp = await parseBunkerInput(bunkerUrl);
@@ -121,7 +121,7 @@ var BunkerNIP44Signer = class _BunkerNIP44Signer {
121
121
  }
122
122
  const inner = BunkerSigner.fromBunker(clientSecretKey, bp, params);
123
123
  await Promise.race([
124
- inner.ping(),
124
+ inner.getPublicKey(),
125
125
  new Promise(
126
126
  (_, reject) => setTimeout(() => reject(new Error("Bunker reconnection timed out")), connectTimeout)
127
127
  )
@@ -129,15 +129,12 @@ var BunkerNIP44Signer = class _BunkerNIP44Signer {
129
129
  return new _BunkerNIP44Signer(inner, "nostrconnect");
130
130
  }
131
131
  /**
132
- * Connect via a nostrconnect:// URI (QR code flow).
132
+ * Two-phase nostrconnect: sets up relay connections and subscription,
133
+ * then returns a handle. Show the QR code, then call handle.waitForSigner().
133
134
  *
134
- * Implements the connect handshake manually instead of using
135
- * BunkerSigner.fromURI, which sends a `switch_relays` RPC that
136
- * signers like Primal don't understand (causing a parse error and
137
- * potentially disrupting the session). After the handshake we create
138
- * the signer via BunkerSigner.fromBunker which skips switch_relays.
135
+ * This avoids a race where the user scans before the subscription is live.
139
136
  */
140
- static async fromNostrConnect(connectionURI, clientSecretKey, params, timeoutOrAbort) {
137
+ static async prepareNostrConnect(connectionURI, clientSecretKey, params, timeoutOrAbort) {
141
138
  const uri = new URL(connectionURI);
142
139
  const relays = uri.searchParams.getAll("relay");
143
140
  const secret = uri.searchParams.get("secret");
@@ -146,13 +143,16 @@ var BunkerNIP44Signer = class _BunkerNIP44Signer {
146
143
  throw new Error("No relays specified in nostrconnect URI");
147
144
  }
148
145
  const abort = typeof timeoutOrAbort === "number" ? AbortSignal.timeout(timeoutOrAbort) : timeoutOrAbort;
146
+ const ac = new AbortController();
147
+ const effectiveAbort = abort ?? ac.signal;
149
148
  const pool = new SimplePool();
150
- const signerPubkey = await new Promise((resolve, reject) => {
151
- if (abort?.aborted) {
149
+ await Promise.all(relays.map((r) => pool.ensureRelay(r)));
150
+ let settled = false;
151
+ const signerPromise = new Promise((resolve, reject) => {
152
+ if (effectiveAbort.aborted) {
152
153
  reject(new Error("Aborted"));
153
154
  return;
154
155
  }
155
- let settled = false;
156
156
  const sub = pool.subscribe(
157
157
  relays,
158
158
  { kinds: [24133], "#p": [clientPubkey], limit: 0 },
@@ -179,10 +179,10 @@ var BunkerNIP44Signer = class _BunkerNIP44Signer {
179
179
  );
180
180
  }
181
181
  },
182
- abort
182
+ abort: effectiveAbort
183
183
  }
184
184
  );
185
- abort?.addEventListener(
185
+ effectiveAbort.addEventListener(
186
186
  "abort",
187
187
  () => {
188
188
  if (!settled) {
@@ -194,12 +194,37 @@ var BunkerNIP44Signer = class _BunkerNIP44Signer {
194
194
  { once: true }
195
195
  );
196
196
  });
197
- const bp = { pubkey: signerPubkey, relays, secret: secret || "" };
198
- const inner = BunkerSigner.fromBunker(clientSecretKey, bp, {
199
- ...params,
200
- pool
201
- });
202
- return new _BunkerNIP44Signer(inner, "nostrconnect");
197
+ return {
198
+ async waitForSigner() {
199
+ const signerPubkey = await signerPromise;
200
+ const bp = { pubkey: signerPubkey, relays, secret: secret || "" };
201
+ const inner = BunkerSigner.fromBunker(clientSecretKey, bp, {
202
+ ...params,
203
+ pool
204
+ });
205
+ return new _BunkerNIP44Signer(inner, "nostrconnect");
206
+ },
207
+ abort() {
208
+ ac.abort();
209
+ }
210
+ };
211
+ }
212
+ /**
213
+ * Connect via a nostrconnect:// URI (QR code flow).
214
+ *
215
+ * Convenience wrapper around prepareNostrConnect() — connects to relays,
216
+ * subscribes, and waits for the ack in one call. If you need to show a
217
+ * QR code only after the subscription is live, use prepareNostrConnect()
218
+ * instead.
219
+ */
220
+ static async fromNostrConnect(connectionURI, clientSecretKey, params, timeoutOrAbort) {
221
+ const handle = await _BunkerNIP44Signer.prepareNostrConnect(
222
+ connectionURI,
223
+ clientSecretKey,
224
+ params,
225
+ timeoutOrAbort
226
+ );
227
+ return handle.waitForSigner();
203
228
  }
204
229
  async getPublicKey() {
205
230
  return Promise.race([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "divine-signer",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",