@konemono/nostr-login 1.7.36 → 1.7.38

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,4 +1,4 @@
1
- import { Info, RecentType } from 'nostr-login-components';
1
+ import { Info, RecentType } from 'nostr-login-components/dist/types/types';
2
2
  import NDK, { NDKSigner } from '@nostr-dev-kit/ndk';
3
3
  import { NostrLoginOptions } from '../types';
4
4
  export declare const localStorageSetItem: (key: string, value: string) => void;
@@ -2,7 +2,7 @@ export declare function encryptNip44(plaintext: string, conversationKey: Uint8Ar
2
2
  export declare function decryptNip44(payload: string, conversationKey: Uint8Array): string;
3
3
  export declare class Nip44 {
4
4
  private cache;
5
- createKey(privkey: string, pubkey: string): Uint8Array;
5
+ createKey(privkey: string, pubkey: string): Uint8Array<ArrayBufferLike>;
6
6
  private getKey;
7
7
  encrypt(privkey: string, pubkey: string, text: string): string;
8
8
  decrypt(privkey: string, pubkey: string, data: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konemono/nostr-login",
3
- "version": "1.7.36",
3
+ "version": "1.7.38",
4
4
  "description": "",
5
5
  "main": "./dist/index.esm.js",
6
6
  "types": "./dist/index.d.ts",
@@ -11,17 +11,17 @@
11
11
  },
12
12
  "author": "a-fralou",
13
13
  "dependencies": {
14
- "@nostr-dev-kit/ndk": "^2.3.1",
14
+ "@nostr-dev-kit/ndk": "^2.18.1",
15
15
  "nostr-tools": "^1.17.0",
16
- "tseep": "^1.2.1"
16
+ "tseep": "^1.3.1"
17
17
  },
18
18
  "devDependencies": {
19
- "@rollup/plugin-commonjs": "^25.0.7",
20
- "@rollup/plugin-node-resolve": "^15.2.3",
19
+ "@rollup/plugin-commonjs": "^25.0.8",
20
+ "@rollup/plugin-node-resolve": "^15.3.1",
21
21
  "@rollup/plugin-terser": "^0.4.4",
22
22
  "nostr-login-components": "^1.0.3",
23
- "prettier": "^3.2.2",
24
- "rollup": "^4.9.6",
23
+ "prettier": "^3.7.4",
24
+ "rollup": "^4.53.5",
25
25
  "rollup-plugin-typescript2": "^0.36.0"
26
26
  },
27
27
  "license": "MIT"
@@ -1,2 +1 @@
1
- export const CALL_TIMEOUT = 10000;
2
- export const NIP46_TIMEOUT = 30000;
1
+ export const CALL_TIMEOUT = 5000;
@@ -1,7 +1,9 @@
1
1
  import { init } from './index';
2
2
  import { NostrLoginOptions, StartScreens } from './types';
3
3
 
4
+ // wrap to hide local vars
4
5
  (() => {
6
+ // currentScript only visible in global scope code, not event handlers
5
7
  const cs = document.currentScript;
6
8
  const start = async () => {
7
9
  const options: NostrLoginOptions = {};
@@ -69,13 +71,6 @@ import { NostrLoginOptions, StartScreens } from './types';
69
71
  const custom = cs.getAttribute('data-custom-nostr-connect') === 'true';
70
72
  if (custom) options.customNostrConnect = custom;
71
73
 
72
- const connectRelays = cs.getAttribute('data-connect-relays');
73
- if (connectRelays)
74
- options.connectRelays = connectRelays
75
- .split(',')
76
- .map(r => r.trim())
77
- .filter(r => !!r);
78
-
79
74
  console.log('nostr-login options', options);
80
75
  }
81
76
 
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@ import 'nostr-login-components';
2
2
  import { AuthNostrService, NostrExtensionService, Popup, NostrParams, Nostr, ProcessManager, BannerManager, ModalManager } from './modules';
3
3
  import { NostrLoginAuthOptions, NostrLoginOptions, StartScreens } from './types';
4
4
  import { localStorageGetAccounts, localStorageGetCurrent, localStorageGetRecents, localStorageSetItem } from './utils';
5
- import { Info } from 'nostr-login-components';
5
+ import { Info } from 'nostr-login-components/dist/types/types';
6
6
  import { NostrObjectParams } from './modules/Nostr';
7
7
 
8
8
  export class NostrLoginInitializer {
@@ -93,11 +93,6 @@ export class NostrLoginInitializer {
93
93
  this.bannerManager.onUserInfo(info);
94
94
  });
95
95
 
96
- this.authNostrService.on('reconnecting', () => {
97
- // リレー再接続中はProcessManagerのタイマーをリセット
98
- this.processManager.onIframeUrl();
99
- });
100
-
101
96
  this.modalManager.on('onAuthUrlClick', url => {
102
97
  this.openPopup(url);
103
98
  });
@@ -327,7 +322,7 @@ export class NostrLoginInitializer {
327
322
  };
328
323
 
329
324
  public cancelNeedAuth = () => {
330
- console.log('cancelNeedAuth');
325
+ console.log("cancelNeedAuth");
331
326
  this.fulfillCustomLaunchPromise();
332
327
  this.authNostrService.cancelNostrConnect();
333
328
  };
@@ -2,16 +2,17 @@ import { localStorageAddAccount, bunkerUrlToInfo, isBunkerUrl, fetchProfile, get
2
2
  import { ConnectionString, Info } from 'nostr-login-components/dist/types/types';
3
3
  import { generatePrivateKey, getEventHash, getPublicKey, nip19 } from 'nostr-tools';
4
4
  import { NostrLoginAuthOptions, Response } from '../types';
5
- import NDK, { NDKEvent, NDKNip46Signer, NDKRpcResponse, NDKUser, NostrEvent } from '@nostr-dev-kit/ndk';
5
+ import NDK, { NDKEvent, NDKNip46Signer, NDKPrivateKeySigner, NDKRpcResponse, NDKUser, NostrEvent } from '@nostr-dev-kit/ndk';
6
6
  import { NostrParams } from './';
7
7
  import { EventEmitter } from 'tseep';
8
8
  import { Signer } from './Nostr';
9
9
  import { Nip44 } from '../utils/nip44';
10
10
  import { IframeNostrRpc, Nip46Signer, ReadyListener } from './Nip46';
11
- import { PrivateKeySigner } from './Signer';
11
+ //import { PrivateKeySigner } from './Signer';
12
12
 
13
13
  const OUTBOX_RELAYS = ['wss://user.kindpag.es', 'wss://purplepag.es', 'wss://relay.nos.social'];
14
14
  const DEFAULT_NOSTRCONNECT_RELAYS = ['wss://relay.nsec.app/', 'wss://ephemeral.snowflare.cc/'];
15
+ const CONNECT_TIMEOUT = 5000;
15
16
  const NOSTRCONNECT_APPS: ConnectionString[] = [
16
17
  {
17
18
  name: 'Nsec.app',
@@ -19,19 +20,19 @@ const NOSTRCONNECT_APPS: ConnectionString[] = [
19
20
  canImport: true,
20
21
  img: 'https://nsec.app/assets/favicon.ico',
21
22
  link: 'https://use.nsec.app/<nostrconnect>',
22
- relay: 'wss://relay.nsec.app/',
23
+ relays: DEFAULT_NOSTRCONNECT_RELAYS,
23
24
  },
24
25
  {
25
26
  name: 'Amber',
26
27
  img: 'https://raw.githubusercontent.com/greenart7c3/Amber/refs/heads/master/assets/android-icon.svg',
27
28
  link: '<nostrconnect>',
28
- relay: 'wss://relay.nsec.app/',
29
+ relays: DEFAULT_NOSTRCONNECT_RELAYS,
29
30
  },
30
31
  {
31
32
  name: 'Other key stores',
32
33
  img: '',
33
34
  link: '<nostrconnect>',
34
- relay: 'wss://relay.nsec.app/',
35
+ relays: DEFAULT_NOSTRCONNECT_RELAYS,
35
36
  },
36
37
  ];
37
38
 
@@ -39,7 +40,7 @@ class AuthNostrService extends EventEmitter implements Signer {
39
40
  private ndk: NDK;
40
41
  private profileNdk: NDK;
41
42
  private signer: Nip46Signer | null = null;
42
- private localSigner: PrivateKeySigner | null = null;
43
+ private localSigner: NDKPrivateKeySigner | null = null;
43
44
  private params: NostrParams;
44
45
  private signerPromise?: Promise<void>;
45
46
  private signerErrCallback?: (err: string) => void;
@@ -71,7 +72,7 @@ class AuthNostrService extends EventEmitter implements Signer {
71
72
  enableOutboxModel: true,
72
73
  explicitRelayUrls: OUTBOX_RELAYS,
73
74
  });
74
- this.profileNdk.connect();
75
+ this.profileNdk.connect(CONNECT_TIMEOUT);
75
76
 
76
77
  this.nip04 = {
77
78
  encrypt: this.encrypt04.bind(this),
@@ -106,15 +107,8 @@ class AuthNostrService extends EventEmitter implements Signer {
106
107
  this.resetAuth();
107
108
  }
108
109
 
109
- private getDefaultRelays(customRelays?: string[]): string[] {
110
- if (customRelays && customRelays.length > 0) {
111
- return customRelays;
112
- }
113
- return [...DEFAULT_NOSTRCONNECT_RELAYS];
114
- }
115
-
116
110
  public async nostrConnect(
117
- relay?: string | string[],
111
+ relays?: string[],
118
112
  {
119
113
  domain = '',
120
114
  link = '',
@@ -127,12 +121,12 @@ class AuthNostrService extends EventEmitter implements Signer {
127
121
  iframeUrl?: string;
128
122
  } = {},
129
123
  ) {
130
- const relays = Array.isArray(relay) ? relay : relay ? [relay] : this.getDefaultRelays();
124
+ relays = relays && relays.length > 0 ? relays : DEFAULT_NOSTRCONNECT_RELAYS;
131
125
 
132
126
  const info: Info = {
133
127
  authMethod: 'connect',
134
- pubkey: '',
135
- signerPubkey: '',
128
+ pubkey: '', // unknown yet!
129
+ signerPubkey: '', // unknown too!
136
130
  sk: this.nostrConnectKey,
137
131
  domain: domain,
138
132
  relays: relays,
@@ -141,20 +135,31 @@ class AuthNostrService extends EventEmitter implements Signer {
141
135
 
142
136
  console.log('nostrconnect info', info, link);
143
137
 
138
+ // non-iframe flow
144
139
  if (link && !iframeUrl) window.open(link, '_blank', 'width=400,height=700');
145
140
 
141
+ // init nip46 signer
146
142
  await this.initSigner(info, { listen: true });
147
143
 
144
+ // signer learns the remote pubkey
148
145
  if (!info.pubkey || !info.signerPubkey) throw new Error('Bad remote pubkey');
149
146
 
150
- info.bunkerUrl = `bunker://${info.signerPubkey}?${relays.map(r => `relay=${r}`).join('&')}`;
147
+ info.bunkerUrl = `bunker://${info.signerPubkey}?${relays.map((r, i) => `${i !== 0 ? '&' : ''}relay=${r}`)}`;
151
148
 
149
+ // callback
152
150
  if (!importConnect) this.onAuth('login', info);
153
151
 
154
152
  return info;
155
153
  }
156
154
 
157
- public async createNostrConnect(relay?: string | string[]) {
155
+ public async createNostrConnect(relays?: string[]) {
156
+ /*const relayList = relays
157
+ ? relays
158
+ .split(",")
159
+ .map(r => r.trim().replace(/['"]/g, ""))
160
+ .filter(r => r.length > 0)
161
+ : [];*/
162
+
158
163
  this.nostrConnectKey = generatePrivateKey();
159
164
  this.nostrConnectSecret = Math.random().toString(36).substring(7);
160
165
 
@@ -166,37 +171,52 @@ class AuthNostrService extends EventEmitter implements Signer {
166
171
  perms: encodeURIComponent(this.params.optionsModal.perms || ''),
167
172
  };
168
173
 
169
- const relays = Array.isArray(relay) ? relay : relay ? [relay] : this.getDefaultRelays();
170
-
171
- const relayParams = relays.map(r => `relay=${r}`).join('&');
172
- return `nostrconnect://${pubkey}?image=${meta.icon}&url=${meta.url}&name=${meta.name}&perms=${meta.perms}&secret=${this.nostrConnectSecret}&${relayParams}`;
174
+ return `nostrconnect://${pubkey}?image=${meta.icon}&url=${meta.url}&name=${meta.name}&perms=${meta.perms}&secret=${this.nostrConnectSecret}${(relays || []).length > 0 ? (relays || []).map((r, i) => `&relay=${r}`) : ''}`;
173
175
  }
174
176
 
175
177
  public async getNostrConnectServices(): Promise<[string, ConnectionString[]]> {
176
- const customRelays = this.params.optionsModal.connectRelays;
177
- const nostrconnect = await this.createNostrConnect(customRelays);
178
+ const nostrconnect = await this.createNostrConnect();
178
179
 
180
+ // copy defaults
179
181
  const apps = NOSTRCONNECT_APPS.map(a => ({ ...a }));
182
+ // if (this.params.optionsModal.dev) {
183
+ // apps.push({
184
+ // name: 'Dev.Nsec.app',
185
+ // domain: 'new.nsec.app',
186
+ // canImport: true,
187
+ // img: 'https://new.nsec.app/assets/favicon.ico',
188
+ // link: 'https://dev.nsec.app/<nostrconnect>',
189
+ // relay: 'wss://relay.nsec.app/',
190
+ // });
191
+ // }
180
192
 
181
193
  for (const a of apps) {
182
- let relays = customRelays && customRelays.length > 0 ? customRelays : this.getDefaultRelays();
194
+ let relays: string[] = [...DEFAULT_NOSTRCONNECT_RELAYS];
183
195
 
184
196
  if (a.link.startsWith('https://')) {
185
- let domain = a.domain || new URL(a.link).hostname;
197
+ const domain = a.domain || new URL(a.link).hostname;
186
198
  try {
187
199
  const info = await (await fetch(`https://${domain}/.well-known/nostr.json`)).json();
188
200
  const pubkey = info.names['_'];
189
- const serviceRelays = info.nip46[pubkey] as string[];
190
- if (serviceRelays && serviceRelays.length) {
191
- relays = serviceRelays;
201
+ const appRelays = info.nip46?.[pubkey] as string[] | undefined;
202
+
203
+ if (Array.isArray(appRelays) && appRelays.length > 0) {
204
+ relays = appRelays;
192
205
  }
193
- a.iframeUrl = info.nip46.iframe_url || '';
206
+
207
+ a.iframeUrl = info.nip46?.iframe_url || '';
194
208
  } catch (e) {
195
209
  console.log('Bad app info', e, a);
196
210
  }
197
211
  }
198
212
 
199
- const nc = nostrconnect + '&' + relays.map(r => `relay=${r}`).join('&');
213
+ const relayParams = relays
214
+ .map(r => r.replace(/['"]/g, ''))
215
+ .map(r => `&relay=${encodeURIComponent(r)}`)
216
+ .join('');
217
+
218
+ const nc = nostrconnect + relayParams;
219
+
200
220
  if (a.iframeUrl) {
201
221
  a.link = nc;
202
222
  } else {
@@ -223,7 +243,7 @@ class AuthNostrService extends EventEmitter implements Signer {
223
243
 
224
244
  public async setLocal(info: Info, signup?: boolean) {
225
245
  this.releaseSigner();
226
- this.localSigner = new PrivateKeySigner(info.sk!);
246
+ this.localSigner = new NDKPrivateKeySigner(info.sk!);
227
247
 
228
248
  if (signup) await createProfile(info, this.profileNdk, this.localSigner, this.params.optionsModal.signupRelays, this.params.optionsModal.outboxRelays);
229
249
 
@@ -231,23 +251,27 @@ class AuthNostrService extends EventEmitter implements Signer {
231
251
  }
232
252
 
233
253
  public prepareImportUrl(url: string) {
254
+ // for OTP we choose interactive import
234
255
  if (this.params.userInfo?.authMethod === 'otp') return url + '&import=true';
235
256
 
257
+ // for local we export our existing key
236
258
  if (!this.localSigner || this.params.userInfo?.authMethod !== 'local') throw new Error('Most be local keys');
237
259
  return url + '#import=' + nip19.nsecEncode(this.localSigner.privateKey!);
238
260
  }
239
261
 
240
262
  public async importAndConnect(cs: ConnectionString) {
241
- const { relay, domain, link, iframeUrl } = cs;
263
+ const { relays, domain, link, iframeUrl } = cs;
242
264
  if (!domain) throw new Error('Domain required');
243
265
 
244
- const relays = relay ? [relay] : this.getDefaultRelays();
245
- const info = await this.nostrConnect(relays, { domain, link, importConnect: true, iframeUrl });
266
+ const info = await this.nostrConnect(relays, {
267
+ domain,
268
+ link,
269
+ importConnect: true,
270
+ iframeUrl,
271
+ });
246
272
 
247
273
  await this.logout(true);
248
-
249
274
  this.localSigner = null;
250
-
251
275
  this.onAuth('login', info);
252
276
  }
253
277
 
@@ -277,20 +301,23 @@ class AuthNostrService extends EventEmitter implements Signer {
277
301
  public async createAccount(nip05: string) {
278
302
  const [name, domain] = nip05.split('@');
279
303
 
304
+ // bunker's own url
280
305
  const bunkerUrl = await getBunkerUrl(`_@${domain}`, this.params.optionsModal);
281
306
  console.log("create account bunker's url", bunkerUrl);
282
307
 
308
+ // parse bunker url and generate local nsec
283
309
  const info = bunkerUrlToInfo(bunkerUrl);
284
310
  if (!info.signerPubkey) throw new Error('Bad bunker url');
285
311
 
286
312
  const eventToAddAccount = Boolean(this.params.userInfo);
287
313
 
314
+ // init signer to talk to the bunker (not the user!)
288
315
  await this.initSigner(info, { eventToAddAccount });
289
316
 
290
317
  const userPubkey = await this.signer!.createAccount2({ bunkerPubkey: info.signerPubkey!, name, domain, perms: this.params.optionsModal.perms });
291
318
 
292
319
  return {
293
- bunkerUrl: `bunker://${userPubkey}?relay=${info.relays?.[0]}`,
320
+ bunkerUrl: `bunker://${userPubkey}?` + (info.relays ?? []).map((r: string) => `relay=${encodeURIComponent(r)}`).join('&'),
294
321
  sk: info.sk,
295
322
  };
296
323
  }
@@ -300,6 +327,7 @@ class AuthNostrService extends EventEmitter implements Signer {
300
327
  this.signerErrCallback?.('cancelled');
301
328
  this.localSigner = null;
302
329
 
330
+ // disconnect from signer relays
303
331
  for (const r of this.ndk.pool.relays.keys()) {
304
332
  this.ndk.pool.removeRelay(r);
305
333
  }
@@ -308,8 +336,10 @@ class AuthNostrService extends EventEmitter implements Signer {
308
336
  public async logout(keepSigner = false) {
309
337
  if (!keepSigner) this.releaseSigner();
310
338
 
339
+ // move current to recent
311
340
  localStorageRemoveCurrentAccount();
312
341
 
342
+ // notify everyone
313
343
  this.onAuth('logout');
314
344
 
315
345
  this.emit('updateAccounts');
@@ -334,6 +364,7 @@ class AuthNostrService extends EventEmitter implements Signer {
334
364
  private onAuth(type: 'login' | 'signup' | 'logout', info: Info | null = null) {
335
365
  if (type !== 'logout' && !info) throw new Error('No user info in onAuth');
336
366
 
367
+ // make sure we emulate logout first
337
368
  if (info && this.params.userInfo && (info.pubkey !== this.params.userInfo.pubkey || info.authMethod !== this.params.userInfo.authMethod)) {
338
369
  const event = new CustomEvent('nlAuth', { detail: { type: 'logout' } });
339
370
  console.log('nostr-login auth', event.detail);
@@ -343,6 +374,7 @@ class AuthNostrService extends EventEmitter implements Signer {
343
374
  this.setUserInfo(info);
344
375
 
345
376
  if (info) {
377
+ // async profile fetch
346
378
  fetchProfile(info, this.profileNdk).then(p => {
347
379
  if (this.params.userInfo !== info) return;
348
380
 
@@ -350,6 +382,10 @@ class AuthNostrService extends EventEmitter implements Signer {
350
382
  ...this.params.userInfo,
351
383
  picture: p?.image || p?.picture,
352
384
  name: p?.name || p?.displayName || p?.nip05 || nip19.npubEncode(info.pubkey),
385
+ // NOTE: do not overwrite info.nip05 with the one from profile!
386
+ // info.nip05 refers to nip46 provider,
387
+ // profile.nip05 is just a fancy name that user has chosen
388
+ // nip05: p?.nip05
353
389
  };
354
390
 
355
391
  this.setUserInfo(userInfo);
@@ -364,6 +400,7 @@ class AuthNostrService extends EventEmitter implements Signer {
364
400
  };
365
401
 
366
402
  if (type === 'logout') {
403
+ // reset
367
404
  if (this.iframe) this.iframe.remove();
368
405
  this.iframe = undefined;
369
406
  } else {
@@ -400,10 +437,12 @@ class AuthNostrService extends EventEmitter implements Signer {
400
437
  private async createIframe(iframeUrl?: string) {
401
438
  if (!iframeUrl) return undefined;
402
439
 
440
+ // ensure iframe
403
441
  const url = new URL(iframeUrl);
404
442
  const domain = url.hostname;
405
443
  let iframe: HTMLIFrameElement | undefined;
406
444
 
445
+ // one iframe per domain
407
446
  const did = domain.replaceAll('.', '-');
408
447
  const id = '__nostr-login-worker-iframe-' + did;
409
448
  iframe = document.querySelector(`#${id}`) as HTMLIFrameElement;
@@ -414,25 +453,46 @@ class AuthNostrService extends EventEmitter implements Signer {
414
453
  iframe.setAttribute('height', '0');
415
454
  iframe.setAttribute('border', '0');
416
455
  iframe.style.display = 'none';
456
+ // iframe.setAttribute('sandbox', 'allow-forms allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts');
417
457
  iframe.id = id;
418
458
  document.body.append(iframe);
419
459
  }
420
460
 
461
+ // wait until loaded
421
462
  iframe.setAttribute('src', iframeUrl);
422
463
 
464
+ // we start listening right now to avoid races
465
+ // with 'load' event below
423
466
  const ready = new ReadyListener(['workerReady', 'workerError'], url.origin);
424
467
 
425
468
  await new Promise(ok => {
426
469
  iframe!.addEventListener('load', ok);
427
470
  });
428
471
 
472
+ // now make sure the iframe is ready,
473
+ // timeout timer starts here
429
474
  const r = await ready.wait();
430
475
 
476
+ // FIXME wait until the iframe is ready to accept requests,
477
+ // maybe it should send us some message?
478
+
431
479
  console.log('nostr-login iframe ready', iframeUrl, r);
432
480
 
433
481
  return { iframe, port: r[1] as MessagePort };
434
482
  }
435
483
 
484
+ // private async getIframeUrl(domain?: string) {
485
+ // if (!domain) return '';
486
+ // try {
487
+ // const r = await fetch(`https://${domain}/.well-known/nostr.json`);
488
+ // const data = await r.json();
489
+ // return data.nip46?.iframe_url || '';
490
+ // } catch (e) {
491
+ // console.log('failed to fetch iframe url', e, domain);
492
+ // return '';
493
+ // }
494
+ // }
495
+
436
496
  public async sendNeedAuth() {
437
497
  const [nostrconnect] = await this.getNostrConnectServices();
438
498
  const event = new CustomEvent('nlNeedAuth', { detail: { nostrconnect } });
@@ -448,16 +508,19 @@ class AuthNostrService extends EventEmitter implements Signer {
448
508
  console.log('startAuth');
449
509
  if (this.readyCallback) throw new Error('Already started');
450
510
 
511
+ // start the new promise
451
512
  this.readyPromise = new Promise<void>(ok => (this.readyCallback = ok));
452
513
  }
453
514
 
454
515
  public async endAuth() {
455
516
  console.log('endAuth', this.params.userInfo);
456
517
  if (this.params.userInfo && this.params.userInfo.iframeUrl) {
518
+ // create iframe
457
519
  const { iframe, port } = (await this.createIframe(this.params.userInfo.iframeUrl)) || {};
458
520
  this.iframe = iframe;
459
521
  if (!this.iframe || !port) return;
460
522
 
523
+ // assign iframe to RPC object
461
524
  (this.signer!.rpc as IframeNostrRpc).setWorkerIframePort(port);
462
525
  }
463
526
 
@@ -482,58 +545,85 @@ class AuthNostrService extends EventEmitter implements Signer {
482
545
  }
483
546
 
484
547
  public async initSigner(info: Info, { listen = false, connect = false, eventToAddAccount = false } = {}) {
548
+ // mutex
485
549
  if (this.signerPromise) {
486
550
  try {
487
551
  await this.signerPromise;
488
552
  } catch {}
489
553
  }
490
554
 
555
+ // we remove support for iframe from nip05 and bunker-url methods,
556
+ // only nostrconnect flow will use it.
557
+ // info.iframeUrl = info.iframeUrl || (await this.getIframeUrl(info.domain));
491
558
  console.log('initSigner info', info);
492
559
 
560
+ // start listening for the ready signal
493
561
  const iframeOrigin = info.iframeUrl ? new URL(info.iframeUrl!).origin : undefined;
494
562
  if (iframeOrigin) this.starterReady = new ReadyListener(['starterDone', 'starterError'], iframeOrigin);
495
563
 
564
+ // notify modals so they could show the starter iframe,
565
+ // FIXME shouldn't this come from nostrconnect service list?
496
566
  this.emit('onIframeUrl', info.iframeUrl);
497
567
 
498
568
  this.signerPromise = new Promise<void>(async (ok, err) => {
499
569
  this.signerErrCallback = err;
500
570
  try {
571
+ // pre-connect if we're creating the connection (listen|connect) or
572
+ // not iframe mode
501
573
  if (info.relays && !info.iframeUrl) {
502
574
  for (const r of info.relays) {
503
575
  this.ndk.addExplicitRelay(r, undefined);
504
576
  }
505
577
  }
506
578
 
507
- await this.ndk.connect();
579
+ // wait until we connect, otherwise
580
+ // signer won't start properly
581
+ await this.ndk.connect(CONNECT_TIMEOUT);
508
582
 
509
- const localSigner = new PrivateKeySigner(info.sk!);
583
+ // create and prepare the signer
584
+ const localSigner = new NDKPrivateKeySigner(info.sk!);
510
585
  this.signer = new Nip46Signer(this.ndk, localSigner, info.signerPubkey!, iframeOrigin);
511
586
 
587
+ // we should notify the banner the same way as
588
+ // the onAuthUrl does
512
589
  this.signer.on(`iframeRestart`, async () => {
513
590
  const iframeUrl = info.iframeUrl + (info.iframeUrl!.includes('?') ? '&' : '?') + 'pubkey=' + info.pubkey + '&rebind=' + localSigner.pubkey;
514
591
  this.emit('iframeRestart', { pubkey: info.pubkey, iframeUrl });
515
592
  });
516
593
 
594
+ // OAuth flow
595
+ // if (!listen) {
517
596
  this.signer.on('authUrl', (url: string) => {
518
597
  console.log('nostr login auth url', url);
519
598
 
599
+ // notify our UI
520
600
  this.emit('onAuthUrl', { url, iframeUrl: info.iframeUrl, eventToAddAccount });
521
601
  });
602
+ // }
522
603
 
523
604
  if (listen) {
605
+ // nostrconnect: flow
606
+ // wait for the incoming message from signer
524
607
  await this.listen(info);
525
608
  } else if (connect) {
609
+ // bunker: flow
610
+ // send 'connect' message to signer
526
611
  await this.connect(info, this.params.optionsModal.perms);
527
612
  } else {
613
+ // provide saved pubkey as a hint
528
614
  await this.signer!.initUserPubkey(info.pubkey);
529
615
  }
530
616
 
531
- info.pubkey = this.signer!.userPubkey;
617
+ // ensure, we're using it in callbacks above
618
+ // and expect info to be valid after this call
619
+ info.pubkey = this.signer!.userPubkey!;
620
+ // learned after nostrconnect flow
532
621
  info.signerPubkey = this.signer!.remotePubkey;
533
622
 
534
623
  ok();
535
624
  } catch (e) {
536
625
  console.log('initSigner failure', e);
626
+ // make sure signer isn't set
537
627
  this.signer = null;
538
628
  err(e);
539
629
  }
@@ -556,35 +646,50 @@ class AuthNostrService extends EventEmitter implements Signer {
556
646
  if (domain) info.domain = domain;
557
647
  if (iframeUrl) info.iframeUrl = iframeUrl;
558
648
 
559
- if (!info.signerPubkey || !info.sk || !info.relays?.[0]) {
649
+ // console.log('nostr login auth info', info);
650
+ if (!info.signerPubkey || !info.sk || !info.relays || info.relays.length === 0) {
560
651
  throw new Error(`Bad bunker url ${bunkerUrl}`);
561
652
  }
562
653
 
563
654
  const eventToAddAccount = Boolean(this.params.userInfo);
564
655
  console.log('authNip46', type, info);
565
656
 
657
+ // updates the info
566
658
  await this.initSigner(info, { connect: true, eventToAddAccount });
567
659
 
660
+ // callback
568
661
  this.onAuth(type, info);
569
662
  } catch (e) {
570
663
  console.log('nostr login auth failed', e);
664
+ // make ure it's closed
665
+ // this.popupManager.closePopup();
571
666
  throw e;
572
667
  }
573
668
  }
574
669
 
575
- public async signEvent(ev: any) {
576
- const event = { ...ev };
577
- if (this.localSigner) {
578
- event.pubkey = getPublicKey(this.localSigner.privateKey!);
579
- event.id = getEventHash(event);
580
- event.sig = await this.localSigner.sign(event);
581
- } else {
582
- event.pubkey = this.signer?.remotePubkey;
583
- event.id = getEventHash(event);
584
- event.sig = await this.signer?.sign(event);
585
- }
586
- console.log('signed', { event });
587
- return event;
670
+ public async signEvent(event: any) {
671
+ const timeoutMs = 20000;
672
+
673
+ const signPromise = (async () => {
674
+ if (this.localSigner) {
675
+ event.pubkey = getPublicKey(this.localSigner.privateKey!);
676
+ event.id = getEventHash(event);
677
+ event.sig = await this.localSigner.sign(event);
678
+ } else {
679
+ event.pubkey = this.signer?.remotePubkey;
680
+ event.id = getEventHash(event);
681
+ event.sig = await this.signer?.sign(event);
682
+ }
683
+ return event;
684
+ })();
685
+
686
+ const timeoutPromise = new Promise((_, reject) => {
687
+ setTimeout(() => reject(new Error('Sign timeout')), timeoutMs);
688
+ });
689
+
690
+ const result = await Promise.race([signPromise, timeoutPromise]);
691
+ console.log('signed', { event: result });
692
+ return result;
588
693
  }
589
694
 
590
695
  private async codec_call(method: string, pubkey: string, param: string) {
@@ -611,6 +716,10 @@ class AuthNostrService extends EventEmitter implements Signer {
611
716
  if (this.localSigner) {
612
717
  return this.localSigner.decrypt(new NDKUser({ pubkey }), ciphertext);
613
718
  } else {
719
+ // decrypt is broken in ndk v2.3.1, and latest
720
+ // ndk v2.8.1 doesn't allow to override connect easily,
721
+ // so we reimplement and fix decrypt here as a temporary fix
722
+
614
723
  return this.codec_call('nip04_decrypt', pubkey, ciphertext);
615
724
  }
616
725
  }
@@ -619,6 +728,7 @@ class AuthNostrService extends EventEmitter implements Signer {
619
728
  if (this.localSigner) {
620
729
  return this.nip44Codec.encrypt(this.localSigner.privateKey!, pubkey, plaintext);
621
730
  } else {
731
+ // no support of nip44 in ndk yet
622
732
  return this.codec_call('nip44_encrypt', pubkey, plaintext);
623
733
  }
624
734
  }
@@ -627,6 +737,7 @@ class AuthNostrService extends EventEmitter implements Signer {
627
737
  if (this.localSigner) {
628
738
  return this.nip44Codec.decrypt(this.localSigner.privateKey!, pubkey, ciphertext);
629
739
  } else {
740
+ // no support of nip44 in ndk yet
630
741
  return this.codec_call('nip44_decrypt', pubkey, ciphertext);
631
742
  }
632
743
  }