@konemono/nostr-login 1.9.14 → 1.10.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.
@@ -2,20 +2,16 @@ 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
-
5
+ import NDK, { NDKEvent, NDKNip46Signer, 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
- import { IframeNostrRpc, Nip46Signer, ReadyListener, RpcResponse } from './Nip46';
11
- import { Nip46Client } from './nip46/Nip46Client';
12
- import { Nip46Adapter } from './nip46/Nip46Adapter';
10
+ import { IframeNostrRpc, Nip46Signer, ReadyListener } from './Nip46';
13
11
  import { PrivateKeySigner } from './Signer';
14
- import { AmberDirectSigner } from './AmberDirectSigner';
15
12
 
16
13
  const OUTBOX_RELAYS = ['wss://user.kindpag.es', 'wss://purplepag.es', 'wss://relay.nos.social'];
17
- const DEFAULT_NOSTRCONNECT_RELAYS = ['wss://relay.nsec.app/', 'wss://ephemeral.snowflare.cc/'];
18
- const CONNECT_TIMEOUT = 5000;
14
+ const DEFAULT_NOSTRCONNECT_RELAY = 'wss://relay.nsec.app/';
19
15
  const NOSTRCONNECT_APPS: ConnectionString[] = [
20
16
  {
21
17
  name: 'Nsec.app',
@@ -23,29 +19,31 @@ const NOSTRCONNECT_APPS: ConnectionString[] = [
23
19
  canImport: true,
24
20
  img: 'https://nsec.app/assets/favicon.ico',
25
21
  link: 'https://use.nsec.app/<nostrconnect>',
26
- relays: DEFAULT_NOSTRCONNECT_RELAYS,
22
+ relay: 'wss://relay.nsec.app/',
27
23
  },
28
24
  {
29
25
  name: 'Amber',
30
26
  img: 'https://raw.githubusercontent.com/greenart7c3/Amber/refs/heads/master/assets/android-icon.svg',
31
- link: 'amber',
32
- relays: DEFAULT_NOSTRCONNECT_RELAYS,
27
+ link: '<nostrconnect>',
28
+ relay: 'wss://relay.nsec.app/',
33
29
  },
34
30
  {
35
31
  name: 'Other key stores',
36
32
  img: '',
37
33
  link: '<nostrconnect>',
38
- relays: DEFAULT_NOSTRCONNECT_RELAYS,
34
+ relay: 'wss://relay.nsec.app/',
39
35
  },
40
36
  ];
41
37
 
42
38
  class AuthNostrService extends EventEmitter implements Signer {
43
- private signer: any = null;
44
- private amberSigner: AmberDirectSigner | null = null;
39
+ private ndk: NDK;
40
+ private profileNdk: NDK;
41
+ private signer: Nip46Signer | null = null;
45
42
  private localSigner: PrivateKeySigner | null = null;
46
43
  private params: NostrParams;
47
44
  private signerPromise?: Promise<void>;
48
45
  private signerErrCallback?: (err: string) => void;
46
+ private signerAbortController?: AbortController;
49
47
  private readyPromise?: Promise<void>;
50
48
  private readyCallback?: () => void;
51
49
  private nip44Codec = new Nip44();
@@ -66,6 +64,15 @@ class AuthNostrService extends EventEmitter implements Signer {
66
64
  constructor(params: NostrParams) {
67
65
  super();
68
66
  this.params = params;
67
+ this.ndk = new NDK({
68
+ enableOutboxModel: false,
69
+ });
70
+
71
+ this.profileNdk = new NDK({
72
+ enableOutboxModel: true,
73
+ explicitRelayUrls: OUTBOX_RELAYS,
74
+ });
75
+ this.profileNdk.connect();
69
76
 
70
77
  this.nip04 = {
71
78
  encrypt: this.encrypt04.bind(this),
@@ -75,96 +82,6 @@ class AuthNostrService extends EventEmitter implements Signer {
75
82
  encrypt: this.encrypt44.bind(this),
76
83
  decrypt: this.decrypt44.bind(this),
77
84
  };
78
-
79
- setTimeout(() => this.checkAmberResponse(), 100);
80
-
81
- const check = () => {
82
- this.checkAmberResponse();
83
- };
84
-
85
- window.addEventListener('focus', check);
86
- window.addEventListener('visibilitychange', () => {
87
- if (document.visibilityState === 'visible') check();
88
- });
89
- window.addEventListener('popstate', check);
90
- window.addEventListener('hashchange', check);
91
-
92
- // Periodic check as a safety net
93
- setInterval(check, 1000);
94
-
95
- window.addEventListener('message', event => {
96
- if (event.data && event.data.method === 'amberResponse') {
97
- const { id, type, result } = event.data;
98
- console.log('Amber response received via message', { id, type, result });
99
- this.handleAmberResponse({ id, type, result });
100
- }
101
- });
102
- }
103
-
104
- private checkAmberResponse() {
105
- const response = AmberDirectSigner.parseResponse();
106
- if (response) {
107
- // If we have an opener and it's not the same window, we are in a popup
108
- if (window.opener && window.opener !== window) {
109
- console.log('Amber response in popup, sending back to opener');
110
- window.opener.postMessage({ method: 'amberResponse', ...response }, window.location.origin);
111
- window.close();
112
- return;
113
- }
114
-
115
- this.handleAmberResponse(response);
116
- }
117
- }
118
-
119
- private handledAmberIds: Set<string> = new Set();
120
-
121
- private handleAmberResponse(response: { id: string; type: string; result: string }) {
122
- if (this.handledAmberIds.has(response.id)) return;
123
- this.handledAmberIds.add(response.id);
124
-
125
- console.log('Handling Amber response', response);
126
-
127
- // Stop the "Connecting..." spinner
128
- this.emit('onAuthUrl', { url: '' });
129
-
130
- // Resolve pending promises if any (for non-reload cases)
131
- const resolved = AmberDirectSigner.resolvePending(response.id, response.type, response.result);
132
- if (resolved) {
133
- console.log('Resolved pending Amber promise via resolvePending');
134
- }
135
-
136
- if (response.type === 'get_public_key' || response.type.includes('pub')) {
137
- const info: Info = {
138
- pubkey: response.result,
139
- name: nip19.npubEncode(response.result),
140
- authMethod: 'amber' as any,
141
- relays: [],
142
- signerPubkey: '',
143
- };
144
- console.log('Amber login success', info);
145
- this.onAuth('login', info);
146
- }
147
-
148
- // URLクリーンアップをより徹底的に
149
- const url = new URL(window.location.href);
150
- let changed = false;
151
- if (url.searchParams.has('event')) {
152
- url.searchParams.delete('event');
153
- changed = true;
154
- }
155
-
156
- // パス末尾が結果と一致する場合はパスもクリア
157
- const pathParts = url.pathname.split('/');
158
- if (pathParts.length > 0 && pathParts[pathParts.length - 1] === response.result) {
159
- pathParts.pop();
160
- url.pathname = pathParts.join('/') || '/';
161
- changed = true;
162
- }
163
-
164
- if (changed) {
165
- console.log('Cleaning up Amber response URL', url.toString());
166
- window.history.replaceState({}, '', url.toString());
167
- }
168
85
  }
169
86
 
170
87
  public isIframe() {
@@ -190,8 +107,20 @@ class AuthNostrService extends EventEmitter implements Signer {
190
107
  this.resetAuth();
191
108
  }
192
109
 
110
+ public cancelSignerInit() {
111
+ if (this.signerAbortController) {
112
+ this.signerAbortController.abort();
113
+ this.signerAbortController = undefined;
114
+ }
115
+ if (this.signerErrCallback) {
116
+ this.signerErrCallback('Cancelled by user');
117
+ this.signerErrCallback = undefined;
118
+ }
119
+ this.emit('signerCancelled');
120
+ }
121
+
193
122
  public async nostrConnect(
194
- relays?: string[],
123
+ relay?: string,
195
124
  {
196
125
  domain = '',
197
126
  link = '',
@@ -203,8 +132,8 @@ class AuthNostrService extends EventEmitter implements Signer {
203
132
  importConnect?: boolean;
204
133
  iframeUrl?: string;
205
134
  } = {},
206
- ): Promise<Info> {
207
- relays = relays && relays.length > 0 ? relays : DEFAULT_NOSTRCONNECT_RELAYS;
135
+ ) {
136
+ relay = relay || DEFAULT_NOSTRCONNECT_RELAY;
208
137
 
209
138
  const info: Info = {
210
139
  authMethod: 'connect',
@@ -212,51 +141,14 @@ class AuthNostrService extends EventEmitter implements Signer {
212
141
  signerPubkey: '', // unknown too!
213
142
  sk: this.nostrConnectKey,
214
143
  domain: domain,
215
- relays: relays,
144
+ relays: [relay],
216
145
  iframeUrl,
217
146
  };
218
147
 
219
148
  console.log('nostrconnect info', info, link);
220
149
 
221
150
  // non-iframe flow
222
- if (link && !iframeUrl) {
223
- if (link === 'amber') {
224
- const signer = new AmberDirectSigner();
225
- this.amberSigner = signer;
226
-
227
- const id = Math.random().toString(36).substring(7);
228
- const url = signer.generateUrl('', 'get_public_key', id);
229
-
230
- // Emit for the "Connecting..." spinner
231
- this.emit('onAuthUrl', { url });
232
-
233
- try {
234
- const pubkey = await signer.getPublicKey(id);
235
- const info: Info = {
236
- pubkey,
237
- name: nip19.npubEncode(pubkey),
238
- authMethod: 'amber' as any,
239
- relays: [],
240
- signerPubkey: '',
241
- };
242
- this.onAuth('login', info);
243
- return info;
244
- } catch (e) {
245
- console.log('Amber getPublicKey failed or was blocked', e);
246
- // Fallback: wait for onAuth to be called (e.g. via user clicking Continue)
247
- return new Promise(resolve => {
248
- const handler = (info: Info | null) => {
249
- if (info && info.authMethod === ('amber' as any)) {
250
- this.off('onUserInfo', handler); // Use onUserInfo as a proxy for onAuth
251
- resolve(info);
252
- }
253
- };
254
- this.on('onUserInfo', handler);
255
- });
256
- }
257
- }
258
- window.open(link, '_blank', 'width=400,height=700');
259
- }
151
+ if (link && !iframeUrl) window.open(link, '_blank', 'width=400,height=700');
260
152
 
261
153
  // init nip46 signer
262
154
  await this.initSigner(info, { listen: true });
@@ -264,7 +156,7 @@ class AuthNostrService extends EventEmitter implements Signer {
264
156
  // signer learns the remote pubkey
265
157
  if (!info.pubkey || !info.signerPubkey) throw new Error('Bad remote pubkey');
266
158
 
267
- info.bunkerUrl = `bunker://${info.signerPubkey}?${relays.map((r, i) => `${i !== 0 ? '&' : ''}relay=${r}`)}`;
159
+ info.bunkerUrl = `bunker://${info.signerPubkey}?relay=${relay}`;
268
160
 
269
161
  // callback
270
162
  if (!importConnect) this.onAuth('login', info);
@@ -272,14 +164,7 @@ class AuthNostrService extends EventEmitter implements Signer {
272
164
  return info;
273
165
  }
274
166
 
275
- public async createNostrConnect(relays?: string[]) {
276
- /*const relayList = relays
277
- ? relays
278
- .split(",")
279
- .map(r => r.trim().replace(/['"]/g, ""))
280
- .filter(r => r.length > 0)
281
- : [];*/
282
-
167
+ public async createNostrConnect(relay?: string) {
283
168
  this.nostrConnectKey = generatePrivateKey();
284
169
  this.nostrConnectSecret = Math.random().toString(36).substring(7);
285
170
 
@@ -291,7 +176,7 @@ class AuthNostrService extends EventEmitter implements Signer {
291
176
  perms: encodeURIComponent(this.params.optionsModal.perms || ''),
292
177
  };
293
178
 
294
- 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}`) : ''}`;
179
+ return `nostrconnect://${pubkey}?image=${meta.icon}&url=${meta.url}&name=${meta.name}&perms=${meta.perms}&secret=${this.nostrConnectSecret}${relay ? `&relay=${relay}` : ''}`;
295
180
  }
296
181
 
297
182
  public async getNostrConnectServices(): Promise<[string, ConnectionString[]]> {
@@ -311,35 +196,25 @@ class AuthNostrService extends EventEmitter implements Signer {
311
196
  // }
312
197
 
313
198
  for (const a of apps) {
314
- let relays: string[] = [...DEFAULT_NOSTRCONNECT_RELAYS];
315
-
199
+ let relay = DEFAULT_NOSTRCONNECT_RELAY;
316
200
  if (a.link.startsWith('https://')) {
317
- const domain = a.domain || new URL(a.link).hostname;
201
+ let domain = a.domain || new URL(a.link).hostname;
318
202
  try {
319
203
  const info = await (await fetch(`https://${domain}/.well-known/nostr.json`)).json();
320
204
  const pubkey = info.names['_'];
321
- const appRelays = info.nip46?.[pubkey] as string[] | undefined;
322
-
323
- if (Array.isArray(appRelays) && appRelays.length > 0) {
324
- relays = appRelays;
325
- }
326
-
327
- a.iframeUrl = info.nip46?.iframe_url || '';
205
+ const relays = info.nip46[pubkey] as string[];
206
+ if (relays && relays.length) relay = relays[0];
207
+ a.iframeUrl = info.nip46.iframe_url || '';
328
208
  } catch (e) {
329
209
  console.log('Bad app info', e, a);
330
210
  }
331
211
  }
332
-
333
- const relayParams = relays
334
- .map(r => r.replace(/['"]/g, ''))
335
- .map(r => `&relay=${encodeURIComponent(r)}`)
336
- .join('');
337
-
338
- const nc = nostrconnect + relayParams;
339
-
212
+ const nc = nostrconnect + '&relay=' + relay;
340
213
  if (a.iframeUrl) {
214
+ // pass plain nc url for iframe-based flow
341
215
  a.link = nc;
342
216
  } else {
217
+ // we will open popup ourselves
343
218
  a.link = a.link.replace('<nostrconnect>', nc);
344
219
  }
345
220
  }
@@ -365,7 +240,7 @@ class AuthNostrService extends EventEmitter implements Signer {
365
240
  this.releaseSigner();
366
241
  this.localSigner = new PrivateKeySigner(info.sk!);
367
242
 
368
- if (signup) await createProfile(info, this.localSigner, this.params.optionsModal.signupRelays, this.params.optionsModal.outboxRelays);
243
+ if (signup) await createProfile(info, this.profileNdk, this.localSigner, this.params.optionsModal.signupRelays, this.params.optionsModal.outboxRelays);
369
244
 
370
245
  this.onAuth(signup ? 'signup' : 'login', info);
371
246
  }
@@ -380,18 +255,19 @@ class AuthNostrService extends EventEmitter implements Signer {
380
255
  }
381
256
 
382
257
  public async importAndConnect(cs: ConnectionString) {
383
- const { relays, domain, link, iframeUrl } = cs;
258
+ const { relay, domain, link, iframeUrl } = cs;
384
259
  if (!domain) throw new Error('Domain required');
385
260
 
386
- const info = await this.nostrConnect(relays, {
387
- domain,
388
- link,
389
- importConnect: true,
390
- iframeUrl,
391
- });
261
+ const info = await this.nostrConnect(relay, { domain, link, importConnect: true, iframeUrl });
262
+
263
+ // logout to remove local keys from storage
264
+ // but keep the connect signer
265
+ await this.logout(/*keepSigner*/ true);
392
266
 
393
- await this.logout(true);
267
+ // release local one
394
268
  this.localSigner = null;
269
+
270
+ // notify app that we've switched to 'connect' keys
395
271
  this.onAuth('login', info);
396
272
  }
397
273
 
@@ -418,12 +294,6 @@ class AuthNostrService extends EventEmitter implements Signer {
418
294
  await this.endAuth();
419
295
  }
420
296
 
421
- public async setAmber(info: Info) {
422
- this.releaseSigner();
423
- this.amberSigner = new AmberDirectSigner(info.pubkey);
424
- this.onAuth('login', info);
425
- }
426
-
427
297
  public async createAccount(nip05: string) {
428
298
  const [name, domain] = nip05.split('@');
429
299
 
@@ -443,8 +313,8 @@ class AuthNostrService extends EventEmitter implements Signer {
443
313
  const userPubkey = await this.signer!.createAccount2({ bunkerPubkey: info.signerPubkey!, name, domain, perms: this.params.optionsModal.perms });
444
314
 
445
315
  return {
446
- bunkerUrl: `bunker://${userPubkey}?` + (info.relays ?? []).map((r: string) => `relay=${encodeURIComponent(r)}`).join('&'),
447
- sk: info.sk,
316
+ bunkerUrl: `bunker://${userPubkey}?relay=${info.relays?.[0]}`,
317
+ sk: info.sk, // reuse the same local key
448
318
  };
449
319
  }
450
320
 
@@ -452,7 +322,11 @@ class AuthNostrService extends EventEmitter implements Signer {
452
322
  this.signer = null;
453
323
  this.signerErrCallback?.('cancelled');
454
324
  this.localSigner = null;
455
- this.amberSigner = null;
325
+
326
+ // disconnect from signer relays
327
+ for (const r of this.ndk.pool.relays.keys()) {
328
+ this.ndk.pool.removeRelay(r);
329
+ }
456
330
  }
457
331
 
458
332
  public async logout(keepSigner = false) {
@@ -497,7 +371,7 @@ class AuthNostrService extends EventEmitter implements Signer {
497
371
 
498
372
  if (info) {
499
373
  // async profile fetch
500
- fetchProfile(info, info.relays).then(p => {
374
+ fetchProfile(info, this.profileNdk).then(p => {
501
375
  if (this.params.userInfo !== info) return;
502
376
 
503
377
  const userInfo = {
@@ -626,7 +500,7 @@ class AuthNostrService extends EventEmitter implements Signer {
626
500
  return !!this.readyCallback;
627
501
  }
628
502
 
629
- public startAuth() {
503
+ public async startAuth() {
630
504
  console.log('startAuth');
631
505
  if (this.readyCallback) throw new Error('Already started');
632
506
 
@@ -687,71 +561,24 @@ class AuthNostrService extends EventEmitter implements Signer {
687
561
  // FIXME shouldn't this come from nostrconnect service list?
688
562
  this.emit('onIframeUrl', info.iframeUrl);
689
563
 
564
+ // AbortControllerでキャンセル可能にする
565
+ this.signerAbortController = new AbortController();
566
+ const abortPromise = new Promise<never>((_, reject) => {
567
+ this.signerAbortController!.signal.addEventListener('abort', () => {
568
+ reject(new Error('Cancelled by user'));
569
+ });
570
+ });
571
+
690
572
  this.signerPromise = new Promise<void>(async (ok, err) => {
691
573
  this.signerErrCallback = err;
692
574
  try {
693
- // create and prepare the signer
694
- const localSigner = new PrivateKeySigner(info.sk!);
695
-
696
- if (info.iframeUrl) {
697
- // use NDK-free iframe signer implementation (MessagePort + SimplePool)
698
- this.signer = new Nip46Signer(localSigner, info.signerPubkey!, iframeOrigin, info.relays || []);
699
-
700
- // we should notify the banner the same way as the onAuthUrl does
701
- this.signer.on(`iframeRestart`, async () => {
702
- const iframeUrl = info.iframeUrl + (info.iframeUrl!.includes('?') ? '&' : '?') + 'pubkey=' + info.pubkey + '&rebind=' + localSigner.pubkey;
703
- this.emit('iframeRestart', { pubkey: info.pubkey, iframeUrl });
704
- });
705
-
706
- // OAuth flow
707
- this.signer.on('authUrl', (url: string) => {
708
- console.log('nostr login auth url', url);
709
-
710
- // notify our UI
711
- this.emit('onAuthUrl', { url, iframeUrl: info.iframeUrl, eventToAddAccount });
712
- });
713
- } else {
714
- // New SimplePool-based NIP-46 flow
715
- const client = new Nip46Client({
716
- localPrivateKey: info.sk!,
717
- remotePubkey: info.signerPubkey!,
718
- relays: info.relays || [],
719
- timeoutMs: 30000,
720
- });
721
-
722
- const adapter = new Nip46Adapter(client, localSigner);
723
- this.signer = adapter;
724
-
725
- // OAuth flow: forward authUrl events
726
- this.signer.on('authUrl', (url: string) => {
727
- console.log('nostr login auth url', url);
728
- this.emit('onAuthUrl', { url, iframeUrl: info.iframeUrl, eventToAddAccount });
729
- });
730
- }
731
- if (listen) {
732
- // nostrconnect: flow
733
- // wait for the incoming message from signer
734
- await this.listen(info);
735
- } else if (connect) {
736
- // bunker: flow
737
- // send 'connect' message to signer
738
- await this.connect(info, this.params.optionsModal.perms);
739
- } else {
740
- // provide saved pubkey as a hint
741
- await this.signer!.initUserPubkey(info.pubkey);
742
- }
743
-
744
- // ensure, we're using it in callbacks above
745
- // and expect info to be valid after this call
746
- info.pubkey = this.signer!.userPubkey;
747
- // learned after nostrconnect flow
748
- info.signerPubkey = this.signer!.remotePubkey;
749
-
750
- ok();
575
+ // タイムアウトとキャンセルの両方に対応
576
+ await Promise.race([this.initSignerInternal(info, listen, connect, eventToAddAccount, ok), abortPromise]);
751
577
  } catch (e) {
752
578
  console.log('initSigner failure', e);
753
579
  // make sure signer isn't set
754
580
  this.signer = null;
581
+ this.signerAbortController = undefined;
755
582
  err(e);
756
583
  }
757
584
  });
@@ -759,9 +586,73 @@ class AuthNostrService extends EventEmitter implements Signer {
759
586
  return this.signerPromise;
760
587
  }
761
588
 
589
+ private async initSignerInternal(info: Info, listen: boolean, connect: boolean, eventToAddAccount: boolean, resolve: () => void) {
590
+ // pre-connect if we're creating the connection (listen|connect) or
591
+ // not iframe mode
592
+ if (info.relays && !info.iframeUrl) {
593
+ for (const r of info.relays) {
594
+ this.ndk.addExplicitRelay(r, undefined);
595
+ }
596
+ }
597
+
598
+ // wait until we connect, otherwise
599
+ // signer won't start properly
600
+ await this.ndk.connect();
601
+
602
+ // create and prepare the signer
603
+ const localSigner = new PrivateKeySigner(info.sk!);
604
+ this.signer = new Nip46Signer(this.ndk, localSigner, info.signerPubkey!, info.iframeUrl ? new URL(info.iframeUrl!).origin : undefined);
605
+
606
+ // we should notify the banner the same way as
607
+ // the onAuthUrl does
608
+ this.signer.on(`iframeRestart`, async () => {
609
+ const iframeUrl = info.iframeUrl + (info.iframeUrl!.includes('?') ? '&' : '?') + 'pubkey=' + info.pubkey + '&rebind=' + localSigner.pubkey;
610
+ this.emit('iframeRestart', { pubkey: info.pubkey, iframeUrl });
611
+ });
612
+
613
+ // OAuth flow
614
+ // if (!listen) {
615
+ this.signer.on('authUrl', (url: string) => {
616
+ console.log('nostr login auth url', url);
617
+
618
+ // notify our UI
619
+ this.emit('onAuthUrl', { url, iframeUrl: info.iframeUrl, eventToAddAccount });
620
+ });
621
+ // }
622
+
623
+ if (listen) {
624
+ // nostrconnect: flow
625
+ // wait for the incoming message from signer
626
+ await this.listen(info);
627
+ } else if (connect) {
628
+ // bunker: flow
629
+ // send 'connect' message to signer
630
+ await this.connect(info, this.params.optionsModal.perms);
631
+ } else {
632
+ // provide saved pubkey as a hint
633
+ await this.signer!.initUserPubkey(info.pubkey);
634
+ }
635
+
636
+ // ensure, we're using it in callbacks above
637
+ // and expect info to be valid after this call
638
+ info.pubkey = this.signer!.userPubkey;
639
+ // learned after nostrconnect flow
640
+ info.signerPubkey = this.signer!.remotePubkey;
641
+
642
+ this.signerAbortController = undefined;
643
+ resolve();
644
+ }
645
+
762
646
  public async authNip46(
763
647
  type: 'login' | 'signup',
764
- { name, bunkerUrl, sk = '', domain = '', iframeUrl = '' }: { name: string; bunkerUrl: string; sk?: string; domain?: string; iframeUrl?: string },
648
+ {
649
+ name,
650
+ bunkerUrl,
651
+ sk = '',
652
+ domain = '',
653
+ iframeUrl = '',
654
+ customRelays,
655
+ }: { name: string; bunkerUrl: string; sk?: string; domain?: string; iframeUrl?: string; customRelays?: string[] },
765
656
  ) {
766
657
  try {
767
658
  const info = bunkerUrlToInfo(bunkerUrl, sk);
@@ -773,8 +664,13 @@ class AuthNostrService extends EventEmitter implements Signer {
773
664
  if (domain) info.domain = domain;
774
665
  if (iframeUrl) info.iframeUrl = iframeUrl;
775
666
 
667
+ // カスタムリレーが指定されていれば使用する
668
+ if (customRelays && customRelays.length > 0) {
669
+ info.relays = customRelays;
670
+ }
671
+
776
672
  // console.log('nostr login auth info', info);
777
- if (!info.signerPubkey || !info.sk || !info.relays || info.relays.length === 0) {
673
+ if (!info.signerPubkey || !info.sk || !info.relays?.[0]) {
778
674
  throw new Error(`Bad bunker url ${bunkerUrl}`);
779
675
  }
780
676
 
@@ -795,37 +691,22 @@ class AuthNostrService extends EventEmitter implements Signer {
795
691
  }
796
692
 
797
693
  public async signEvent(event: any) {
798
- const timeoutMs = 20000;
799
-
800
- const signPromise = (async () => {
801
- if (this.localSigner) {
802
- event.pubkey = getPublicKey(this.localSigner.privateKey!);
803
- event.id = getEventHash(event);
804
- event.sig = await this.localSigner.sign(event);
805
- } else if (this.params.userInfo?.authMethod === ('amber' as any)) {
806
- const userInfo = this.params.userInfo!;
807
- if (!this.amberSigner) this.amberSigner = new AmberDirectSigner(userInfo.pubkey);
808
- return this.amberSigner.signEvent(event);
809
- } else {
810
- event.pubkey = this.signer?.remotePubkey;
811
- event.id = getEventHash(event);
812
- event.sig = await this.signer?.sign(event);
813
- }
814
- return event;
815
- })();
816
-
817
- const timeoutPromise = new Promise((_, reject) => {
818
- setTimeout(() => reject(new Error('Sign timeout')), timeoutMs);
819
- });
820
-
821
- const result = await Promise.race([signPromise, timeoutPromise]);
822
- console.log('signed', { event: result });
823
- return result;
694
+ if (this.localSigner) {
695
+ event.pubkey = getPublicKey(this.localSigner.privateKey!);
696
+ event.id = getEventHash(event);
697
+ event.sig = await this.localSigner.sign(event);
698
+ } else {
699
+ event.pubkey = this.signer?.remotePubkey;
700
+ event.id = getEventHash(event);
701
+ event.sig = await this.signer?.sign(event);
702
+ }
703
+ console.log('signed', { event });
704
+ return event;
824
705
  }
825
706
 
826
707
  private async codec_call(method: string, pubkey: string, param: string) {
827
708
  return new Promise<string>((resolve, reject) => {
828
- this.signer!.rpc.sendRequest(this.signer!.remotePubkey!, method, [pubkey, param], 24133, (response: RpcResponse) => {
709
+ this.signer!.rpc.sendRequest(this.signer!.remotePubkey!, method, [pubkey, param], 24133, (response: NDKRpcResponse) => {
829
710
  if (!response.error) {
830
711
  resolve(response.result);
831
712
  } else {
@@ -837,35 +718,20 @@ class AuthNostrService extends EventEmitter implements Signer {
837
718
 
838
719
  public async encrypt04(pubkey: string, plaintext: string) {
839
720
  if (this.localSigner) {
840
- return this.localSigner.encrypt(pubkey, plaintext);
841
- } else if (this.params.userInfo?.authMethod === ('amber' as any)) {
842
- const userInfo = this.params.userInfo!;
843
- if (!this.amberSigner) this.amberSigner = new AmberDirectSigner(userInfo.pubkey);
844
- return this.amberSigner.encrypt04(pubkey, plaintext);
721
+ return this.localSigner.encrypt(new NDKUser({ pubkey }), plaintext);
845
722
  } else {
846
- // adapter supports encrypt(pubkey, plaintext)
847
- if (this.signer && typeof this.signer.encrypt === 'function') {
848
- return this.signer.encrypt(pubkey, plaintext);
849
- }
850
- // fallback to remote codec via signer RPC
851
- return this.codec_call('nip04_encrypt', pubkey, plaintext);
723
+ return this.signer!.encrypt(new NDKUser({ pubkey }), plaintext);
852
724
  }
853
725
  }
854
726
 
855
727
  public async decrypt04(pubkey: string, ciphertext: string) {
856
728
  if (this.localSigner) {
857
- return this.localSigner.decrypt(pubkey, ciphertext);
858
- } else if (this.params.userInfo?.authMethod === ('amber' as any)) {
859
- const userInfo = this.params.userInfo!;
860
- if (!this.amberSigner) this.amberSigner = new AmberDirectSigner(userInfo.pubkey);
861
- return this.amberSigner.decrypt04(pubkey, ciphertext);
729
+ return this.localSigner.decrypt(new NDKUser({ pubkey }), ciphertext);
862
730
  } else {
863
- // If signer supports direct decrypt(pubkey, ciphertext), use it
864
- if (this.signer && typeof this.signer.decrypt === 'function') {
865
- return this.signer.decrypt(pubkey, ciphertext);
866
- }
731
+ // decrypt is broken in ndk v2.3.1, and latest
732
+ // ndk v2.8.1 doesn't allow to override connect easily,
733
+ // so we reimplement and fix decrypt here as a temporary fix
867
734
 
868
- // fallback to remote codec via signer RPC
869
735
  return this.codec_call('nip04_decrypt', pubkey, ciphertext);
870
736
  }
871
737
  }
@@ -873,12 +739,8 @@ class AuthNostrService extends EventEmitter implements Signer {
873
739
  public async encrypt44(pubkey: string, plaintext: string) {
874
740
  if (this.localSigner) {
875
741
  return this.nip44Codec.encrypt(this.localSigner.privateKey!, pubkey, plaintext);
876
- } else if (this.params.userInfo?.authMethod === ('amber' as any)) {
877
- const userInfo = this.params.userInfo!;
878
- if (!this.amberSigner) this.amberSigner = new AmberDirectSigner(userInfo.pubkey);
879
- return this.amberSigner.encrypt44(pubkey, plaintext);
880
742
  } else {
881
- // no support of nip44 in legacy signer implementation
743
+ // no support of nip44 in ndk yet
882
744
  return this.codec_call('nip44_encrypt', pubkey, plaintext);
883
745
  }
884
746
  }
@@ -886,12 +748,8 @@ class AuthNostrService extends EventEmitter implements Signer {
886
748
  public async decrypt44(pubkey: string, ciphertext: string) {
887
749
  if (this.localSigner) {
888
750
  return this.nip44Codec.decrypt(this.localSigner.privateKey!, pubkey, ciphertext);
889
- } else if (this.params.userInfo?.authMethod === ('amber' as any)) {
890
- const userInfo = this.params.userInfo!;
891
- if (!this.amberSigner) this.amberSigner = new AmberDirectSigner(userInfo.pubkey);
892
- return this.amberSigner.decrypt44(pubkey, ciphertext);
893
751
  } else {
894
- // no support of nip44 in legacy signer implementation
752
+ // no support of nip44 in ndk yet
895
753
  return this.codec_call('nip44_decrypt', pubkey, ciphertext);
896
754
  }
897
755
  }