@konemono/nostr-login 1.7.14 → 1.7.16

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konemono/nostr-login",
3
- "version": "1.7.14",
3
+ "version": "1.7.16",
4
4
  "description": "",
5
5
  "main": "./dist/index.esm.js",
6
6
  "types": "./dist/index.d.ts",
package/src/index.ts CHANGED
@@ -94,6 +94,7 @@ export class NostrLoginInitializer {
94
94
  });
95
95
 
96
96
  this.authNostrService.on('timeout', () => {
97
+ console.log('nostr-login: timeout event received, calling cancelNostrConnect');
97
98
  this.bannerManager.onCallTimeout();
98
99
  this.authNostrService.cancelNostrConnect();
99
100
  });
@@ -323,6 +323,14 @@ class AuthNostrService extends EventEmitter implements Signer {
323
323
  }
324
324
 
325
325
  private releaseSigner() {
326
+ // cleanup iframe ping interval if using IframeNostrRpc
327
+ if (this.signer && '_rpc' in this.signer) {
328
+ const rpc = (this.signer as any)._rpc;
329
+ if (rpc && typeof rpc.cleanup === 'function') {
330
+ rpc.cleanup();
331
+ }
332
+ }
333
+
326
334
  this.signer = null;
327
335
  this.signerErrCallback?.('cancelled');
328
336
  this.localSigner = null;
@@ -465,8 +473,12 @@ class AuthNostrService extends EventEmitter implements Signer {
465
473
  // with 'load' event below
466
474
  const ready = new ReadyListener(['workerReady', 'workerError'], url.origin);
467
475
 
468
- await new Promise(ok => {
469
- iframe!.addEventListener('load', ok);
476
+ await new Promise((ok, err) => {
477
+ const timeoutId = setTimeout(() => err('Iframe load timeout'), 30000);
478
+ iframe!.addEventListener('load', () => {
479
+ clearTimeout(timeoutId);
480
+ ok(undefined);
481
+ });
470
482
  });
471
483
 
472
484
  // now make sure the iframe is ready,
@@ -584,6 +596,7 @@ class AuthNostrService extends EventEmitter implements Signer {
584
596
  setTimeout(() => {
585
597
  if (!finished) {
586
598
  finished = true
599
+ console.log('nostr-login: relay connection timeout, emitting timeout event');
587
600
  this.emit('timeout');
588
601
  reject('Connection timed out')
589
602
  }
@@ -48,6 +48,7 @@ class BannerManager extends EventEmitter {
48
48
 
49
49
  public onCallTimeout() {
50
50
  if (this.banner) {
51
+ this.banner.isLoading = false;
51
52
  this.banner.notify = {
52
53
  mode: 'timeout',
53
54
  };
@@ -78,8 +78,24 @@ class NostrRpc extends NDKNostrRpc {
78
78
  'kinds': [24133],
79
79
  '#p': [pubkey],
80
80
  });
81
+
81
82
  return new Promise<string>((ok, err) => {
83
+ let finished = false;
84
+
85
+ // 30 sec timeout
86
+ const timeoutId = setTimeout(() => {
87
+ if (!finished) {
88
+ finished = true;
89
+ this.stop();
90
+ console.log('nostr-login: listen timeout, emitting timeout event');
91
+ this.emit('timeout');
92
+ err('Listen timed out');
93
+ }
94
+ }, 30000);
95
+
82
96
  sub.on('event', async (event: NDKEvent) => {
97
+ if (finished) return;
98
+
83
99
  try {
84
100
  const parsedEvent = await this.parseEvent(event);
85
101
  // console.log('ack parsedEvent', parsedEvent);
@@ -92,16 +108,20 @@ class NostrRpc extends NDKNostrRpc {
92
108
  // FIXME for now accept 'ack' replies, later on only
93
109
  // accept secrets
94
110
  if (response.result === 'ack' || response.result === nostrConnectSecret) {
111
+ finished = true;
112
+ clearTimeout(timeoutId);
113
+ this.stop();
95
114
  ok(event.pubkey);
96
115
  } else {
116
+ finished = true;
117
+ clearTimeout(timeoutId);
118
+ this.stop();
97
119
  err(response.error);
98
120
  }
99
121
  }
100
122
  } catch (e) {
101
123
  console.log('error parsing event', e, event.rawEvent());
102
124
  }
103
- // done
104
- this.stop();
105
125
  });
106
126
  });
107
127
  }
@@ -177,6 +197,7 @@ class NostrRpc extends NDKNostrRpc {
177
197
  if (this.requests.has(id)) {
178
198
  this.requests.delete(id);
179
199
  this.removeListener(`response-${id}`, responseHandler);
200
+ console.log('nostr-login: NIP-46 request timeout, emitting timeout event');
180
201
  this.emit('timeout');
181
202
  reject('Request timed out');
182
203
  }
@@ -211,6 +232,7 @@ export class IframeNostrRpc extends NostrRpc {
211
232
  private peerOrigin?: string;
212
233
  private iframePort?: MessagePort;
213
234
  private iframeRequests = new Map<string, { id: string; pubkey: string }>();
235
+ private iframePingInterval?: NodeJS.Timeout;
214
236
 
215
237
  public constructor(ndk: NDK, localSigner: PrivateKeySigner, iframePeerOrigin?: string) {
216
238
  super(ndk, localSigner);
@@ -237,7 +259,7 @@ export class IframeNostrRpc extends NostrRpc {
237
259
  this.iframePort = port;
238
260
 
239
261
  // to make sure Chrome doesn't terminate the channel
240
- setInterval(() => {
262
+ this.iframePingInterval = setInterval(() => {
241
263
  console.log('iframe-nip46 ping');
242
264
  this.iframePort!.postMessage('ping');
243
265
  }, 5000);
@@ -270,6 +292,13 @@ export class IframeNostrRpc extends NostrRpc {
270
292
  };
271
293
  }
272
294
 
295
+ public cleanup() {
296
+ if (this.iframePingInterval) {
297
+ clearInterval(this.iframePingInterval);
298
+ this.iframePingInterval = undefined;
299
+ }
300
+ }
301
+
273
302
  public async sendRequest(remotePubkey: string, method: string, params: string[] = [], kind = 24133, cb?: (res: NDKRpcResponse) => void): Promise<NDKRpcResponse> {
274
303
  const id = this.getId();
275
304
 
@@ -333,18 +362,14 @@ export class ReadyListener {
333
362
 
334
363
  async wait(): Promise<any> {
335
364
  console.log(new Date(), 'waiting for', this.messages);
336
- const r = await this.promise;
337
- // NOTE: timer here doesn't help bcs it must be activated when
338
- // user "confirms", but that's happening on a different
339
- // origin and we can't really know.
340
- // await new Promise<any>((ok, err) => {
341
- // // 10 sec should be more than enough
342
- // setTimeout(() => err(new Date() + ' timeout for ' + this.message), 10000);
343
-
344
- // // if promise already resolved or will resolve in the future
345
- // this.promise.then(ok);
346
- // });
347
-
365
+
366
+ const r = await new Promise<any>((ok, err) => {
367
+ // 30 sec timeout for iframe ready
368
+ setTimeout(() => err(new Date() + ' timeout for ' + this.messages), 30000);
369
+
370
+ this.promise.then(ok).catch(err);
371
+ });
372
+
348
373
  console.log(new Date(), 'finished waiting for', this.messages, r);
349
374
  return r;
350
375
  }
@@ -24,8 +24,18 @@ class ProcessManager extends EventEmitter {
24
24
  public async wait<T>(cb: () => Promise<T>): Promise<T> {
25
25
  // FIXME only allow 1 parallel req
26
26
 
27
+ let timeoutReject: ((reason?: any) => void) | undefined;
28
+ const timeoutPromise = new Promise<T>((_, reject) => {
29
+ timeoutReject = reject;
30
+ });
31
+
27
32
  if (!this.callTimer) {
28
- this.callTimer = setTimeout(() => this.emit('onCallTimeout'), CALL_TIMEOUT);
33
+ this.callTimer = setTimeout(() => {
34
+ this.emit('onCallTimeout');
35
+ if (timeoutReject) {
36
+ timeoutReject(new Error('Request timed out'));
37
+ }
38
+ }, CALL_TIMEOUT);
29
39
  }
30
40
 
31
41
  if (!this.callCount) {
@@ -38,7 +48,7 @@ class ProcessManager extends EventEmitter {
38
48
  let result;
39
49
 
40
50
  try {
41
- result = await cb();
51
+ result = await Promise.race([cb(), timeoutPromise]);
42
52
  } catch (e) {
43
53
  error = e;
44
54
  }
@@ -115,22 +115,36 @@ export const getBunkerUrl = async (value: string, optionsModal: NostrLoginOption
115
115
  const origin = optionsModal.devOverrideBunkerOrigin || `https://${domain}`;
116
116
  const bunkerUrl = `${origin}/.well-known/nostr.json?name=_`;
117
117
  const userUrl = `${origin}/.well-known/nostr.json?name=${name}`;
118
- const bunker = await fetch(bunkerUrl);
119
- const bunkerData = await bunker.json();
120
- const bunkerPubkey = bunkerData.names['_'];
121
- const bunkerRelays = bunkerData.nip46[bunkerPubkey];
122
- const user = await fetch(userUrl);
123
- const userData = await user.json();
124
- const userPubkey = userData.names[name];
125
- // console.log({
126
- // bunkerData, userData, bunkerPubkey, bunkerRelays, userPubkey,
127
- // name, domain, origin
128
- // })
129
- if (!bunkerRelays.length) {
130
- throw new Error('Bunker relay not provided');
131
- }
118
+
119
+ // 10 sec timeout for fetch
120
+ const controller = new AbortController();
121
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
122
+
123
+ try {
124
+ const bunker = await fetch(bunkerUrl, { signal: controller.signal });
125
+ const bunkerData = await bunker.json();
126
+ const bunkerPubkey = bunkerData.names['_'];
127
+ const bunkerRelays = bunkerData.nip46[bunkerPubkey];
128
+ const user = await fetch(userUrl, { signal: controller.signal });
129
+ const userData = await user.json();
130
+ const userPubkey = userData.names[name];
131
+ // console.log({
132
+ // bunkerData, userData, bunkerPubkey, bunkerRelays, userPubkey,
133
+ // name, domain, origin
134
+ // })
135
+ if (!bunkerRelays.length) {
136
+ throw new Error('Bunker relay not provided');
137
+ }
132
138
 
133
- return `bunker://${userPubkey}?relay=${bunkerRelays[0]}`;
139
+ return `bunker://${userPubkey}?relay=${bunkerRelays[0]}`;
140
+ } catch (e: any) {
141
+ if (e.name === 'AbortError') {
142
+ throw new Error('Request timed out');
143
+ }
144
+ throw e;
145
+ } finally {
146
+ clearTimeout(timeoutId);
147
+ }
134
148
  }
135
149
 
136
150
  throw new Error('Invalid user name or bunker url');