@konemono/nostr-login 1.9.13 → 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.
@@ -1,11 +1,12 @@
1
1
  import { Info, RecentType } from 'nostr-login-components/dist/types/types';
2
+ import NDK, { NDKSigner } from '@nostr-dev-kit/ndk';
2
3
  import { NostrLoginOptions } from '../types';
3
4
  export declare const localStorageSetItem: (key: string, value: string) => void;
4
5
  export declare const localStorageGetItem: (key: string) => any;
5
6
  export declare const localStorageRemoveItem: (key: string) => void;
6
- export declare const fetchProfile: (info: Info, profileNdkOrRelays?: any) => Promise<any>;
7
+ export declare const fetchProfile: (info: Info, profileNdk: NDK) => Promise<import("@nostr-dev-kit/ndk").NDKUserProfile | null>;
7
8
  export declare const prepareSignupRelays: (signupRelays?: string) => string[];
8
- export declare const createProfile: (info: Info, signer: any, signupRelays?: string, outboxRelays?: string[]) => Promise<void>;
9
+ export declare const createProfile: (info: Info, profileNdk: NDK, signer: NDKSigner, signupRelays?: string, outboxRelays?: string[]) => Promise<void>;
9
10
  export declare const bunkerUrlToInfo: (bunkerUrl: string, sk?: string) => Info;
10
11
  export declare const isBunkerUrl: (value: string) => boolean;
11
12
  export declare const getBunkerUrl: (value: string, optionsModal: NostrLoginOptions) => Promise<string>;
package/package.json CHANGED
@@ -1,18 +1,17 @@
1
1
  {
2
2
  "name": "@konemono/nostr-login",
3
- "version": "1.9.13",
3
+ "version": "1.10.0",
4
4
  "description": "",
5
5
  "main": "./dist/index.esm.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "type": "module",
8
8
  "scripts": {
9
9
  "build": "rollup -c",
10
- "format": "npx prettier --write src",
11
- "test": "vitest run",
12
- "test:watch": "vitest"
10
+ "format": "npx prettier --write src"
13
11
  },
14
12
  "author": "a-fralou",
15
13
  "dependencies": {
14
+ "@nostr-dev-kit/ndk": "^2.3.1",
16
15
  "nostr-tools": "^1.17.0",
17
16
  "tseep": "^1.2.1"
18
17
  },
@@ -23,9 +22,7 @@
23
22
  "nostr-login-components": "^1.0.3",
24
23
  "prettier": "^3.2.2",
25
24
  "rollup": "^4.9.6",
26
- "rollup-plugin-typescript2": "^0.36.0",
27
- "vitest": "^1.0.0",
28
- "@types/node": "^18.0.0"
25
+ "rollup-plugin-typescript2": "^0.36.0"
29
26
  },
30
27
  "license": "MIT"
31
28
  }
@@ -1 +1,8 @@
1
1
  export const CALL_TIMEOUT = 5000;
2
+
3
+ // デフォルトのNip46リレー
4
+ export const DEFAULT_NIP46_RELAYS = ['wss://relay.nsec.app/', 'wss://ephemeral.snowflare.cc/'];
5
+
6
+ // Nip46タイムアウト設定
7
+ export const NIP46_REQUEST_TIMEOUT = 30000; // 30秒
8
+ export const NIP46_CONNECT_TIMEOUT = 60000; // 60秒
package/src/index.ts CHANGED
@@ -148,11 +148,7 @@ export class NostrLoginInitializer {
148
148
  }
149
149
 
150
150
  private openPopup(url: string) {
151
- if (url.startsWith('nostrsigner:')) {
152
- window.open(url, '_blank', 'width=400,height=600');
153
- } else {
154
- this.popupManager.openPopup(url);
155
- }
151
+ this.popupManager.openPopup(url);
156
152
  }
157
153
 
158
154
  private async switchAccount(info: Info, signup = false) {
@@ -173,8 +169,6 @@ export class NostrLoginInitializer {
173
169
  await this.extensionService.trySetExtensionForPubkey(info.pubkey);
174
170
  } else if (info.authMethod === 'connect' && info.sk && info.relays && info.relays[0]) {
175
171
  this.authNostrService.setConnect(info);
176
- } else if (info.authMethod === ('amber' as any)) {
177
- this.authNostrService.setAmber(info);
178
172
  } else {
179
173
  throw new Error('Bad auth info');
180
174
  }
@@ -309,7 +303,7 @@ export class NostrLoginInitializer {
309
303
  public setAuth = async (o: NostrLoginAuthOptions) => {
310
304
  if (!o.type) throw new Error('Invalid auth event');
311
305
  if (o.type !== 'login' && o.type !== 'logout' && o.type !== 'signup') throw new Error('Invalid auth event');
312
- if (o.method && o.method !== 'connect' && o.method !== 'extension' && o.method !== 'local' && o.method !== 'otp' && o.method !== 'readOnly' && o.method !== ('amber' as any))
306
+ if (o.method && o.method !== 'connect' && o.method !== 'extension' && o.method !== 'local' && o.method !== 'otp' && o.method !== 'readOnly')
313
307
  throw new Error('Invalid auth event');
314
308
 
315
309
  if (o.type === 'logout') return this.logout();
@@ -2,19 +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
12
 
15
13
  const OUTBOX_RELAYS = ['wss://user.kindpag.es', 'wss://purplepag.es', 'wss://relay.nos.social'];
16
- const DEFAULT_NOSTRCONNECT_RELAYS = ['wss://relay.nsec.app/', 'wss://ephemeral.snowflare.cc/'];
17
- const CONNECT_TIMEOUT = 5000;
14
+ const DEFAULT_NOSTRCONNECT_RELAY = 'wss://relay.nsec.app/';
18
15
  const NOSTRCONNECT_APPS: ConnectionString[] = [
19
16
  {
20
17
  name: 'Nsec.app',
@@ -22,28 +19,31 @@ const NOSTRCONNECT_APPS: ConnectionString[] = [
22
19
  canImport: true,
23
20
  img: 'https://nsec.app/assets/favicon.ico',
24
21
  link: 'https://use.nsec.app/<nostrconnect>',
25
- relays: DEFAULT_NOSTRCONNECT_RELAYS,
22
+ relay: 'wss://relay.nsec.app/',
26
23
  },
27
24
  {
28
25
  name: 'Amber',
29
26
  img: 'https://raw.githubusercontent.com/greenart7c3/Amber/refs/heads/master/assets/android-icon.svg',
30
27
  link: '<nostrconnect>',
31
- relays: DEFAULT_NOSTRCONNECT_RELAYS,
28
+ relay: 'wss://relay.nsec.app/',
32
29
  },
33
30
  {
34
31
  name: 'Other key stores',
35
32
  img: '',
36
33
  link: '<nostrconnect>',
37
- relays: DEFAULT_NOSTRCONNECT_RELAYS,
34
+ relay: 'wss://relay.nsec.app/',
38
35
  },
39
36
  ];
40
37
 
41
38
  class AuthNostrService extends EventEmitter implements Signer {
42
- private signer: any = null;
39
+ private ndk: NDK;
40
+ private profileNdk: NDK;
41
+ private signer: Nip46Signer | null = null;
43
42
  private localSigner: PrivateKeySigner | null = null;
44
43
  private params: NostrParams;
45
44
  private signerPromise?: Promise<void>;
46
45
  private signerErrCallback?: (err: string) => void;
46
+ private signerAbortController?: AbortController;
47
47
  private readyPromise?: Promise<void>;
48
48
  private readyCallback?: () => void;
49
49
  private nip44Codec = new Nip44();
@@ -64,6 +64,15 @@ class AuthNostrService extends EventEmitter implements Signer {
64
64
  constructor(params: NostrParams) {
65
65
  super();
66
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();
67
76
 
68
77
  this.nip04 = {
69
78
  encrypt: this.encrypt04.bind(this),
@@ -98,8 +107,20 @@ class AuthNostrService extends EventEmitter implements Signer {
98
107
  this.resetAuth();
99
108
  }
100
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
+
101
122
  public async nostrConnect(
102
- relays?: string[],
123
+ relay?: string,
103
124
  {
104
125
  domain = '',
105
126
  link = '',
@@ -111,8 +132,8 @@ class AuthNostrService extends EventEmitter implements Signer {
111
132
  importConnect?: boolean;
112
133
  iframeUrl?: string;
113
134
  } = {},
114
- ): Promise<Info> {
115
- relays = relays && relays.length > 0 ? relays : DEFAULT_NOSTRCONNECT_RELAYS;
135
+ ) {
136
+ relay = relay || DEFAULT_NOSTRCONNECT_RELAY;
116
137
 
117
138
  const info: Info = {
118
139
  authMethod: 'connect',
@@ -120,16 +141,14 @@ class AuthNostrService extends EventEmitter implements Signer {
120
141
  signerPubkey: '', // unknown too!
121
142
  sk: this.nostrConnectKey,
122
143
  domain: domain,
123
- relays: relays,
144
+ relays: [relay],
124
145
  iframeUrl,
125
146
  };
126
147
 
127
148
  console.log('nostrconnect info', info, link);
128
149
 
129
150
  // non-iframe flow
130
- if (link && !iframeUrl) {
131
- window.open(link, '_blank', 'width=400,height=700');
132
- }
151
+ if (link && !iframeUrl) window.open(link, '_blank', 'width=400,height=700');
133
152
 
134
153
  // init nip46 signer
135
154
  await this.initSigner(info, { listen: true });
@@ -137,7 +156,7 @@ class AuthNostrService extends EventEmitter implements Signer {
137
156
  // signer learns the remote pubkey
138
157
  if (!info.pubkey || !info.signerPubkey) throw new Error('Bad remote pubkey');
139
158
 
140
- info.bunkerUrl = `bunker://${info.signerPubkey}?${relays.map((r, i) => `${i !== 0 ? '&' : ''}relay=${r}`)}`;
159
+ info.bunkerUrl = `bunker://${info.signerPubkey}?relay=${relay}`;
141
160
 
142
161
  // callback
143
162
  if (!importConnect) this.onAuth('login', info);
@@ -145,14 +164,7 @@ class AuthNostrService extends EventEmitter implements Signer {
145
164
  return info;
146
165
  }
147
166
 
148
- public async createNostrConnect(relays?: string[]) {
149
- /*const relayList = relays
150
- ? relays
151
- .split(",")
152
- .map(r => r.trim().replace(/['"]/g, ""))
153
- .filter(r => r.length > 0)
154
- : [];*/
155
-
167
+ public async createNostrConnect(relay?: string) {
156
168
  this.nostrConnectKey = generatePrivateKey();
157
169
  this.nostrConnectSecret = Math.random().toString(36).substring(7);
158
170
 
@@ -164,7 +176,7 @@ class AuthNostrService extends EventEmitter implements Signer {
164
176
  perms: encodeURIComponent(this.params.optionsModal.perms || ''),
165
177
  };
166
178
 
167
- 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}`).join('') : ''}`;
179
+ return `nostrconnect://${pubkey}?image=${meta.icon}&url=${meta.url}&name=${meta.name}&perms=${meta.perms}&secret=${this.nostrConnectSecret}${relay ? `&relay=${relay}` : ''}`;
168
180
  }
169
181
 
170
182
  public async getNostrConnectServices(): Promise<[string, ConnectionString[]]> {
@@ -184,35 +196,25 @@ class AuthNostrService extends EventEmitter implements Signer {
184
196
  // }
185
197
 
186
198
  for (const a of apps) {
187
- let relays: string[] = [...DEFAULT_NOSTRCONNECT_RELAYS];
188
-
199
+ let relay = DEFAULT_NOSTRCONNECT_RELAY;
189
200
  if (a.link.startsWith('https://')) {
190
- const domain = a.domain || new URL(a.link).hostname;
201
+ let domain = a.domain || new URL(a.link).hostname;
191
202
  try {
192
203
  const info = await (await fetch(`https://${domain}/.well-known/nostr.json`)).json();
193
204
  const pubkey = info.names['_'];
194
- const appRelays = info.nip46?.[pubkey] as string[] | undefined;
195
-
196
- if (Array.isArray(appRelays) && appRelays.length > 0) {
197
- relays = appRelays;
198
- }
199
-
200
- 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 || '';
201
208
  } catch (e) {
202
209
  console.log('Bad app info', e, a);
203
210
  }
204
211
  }
205
-
206
- const relayParams = relays
207
- .map(r => r.replace(/['"]/g, ''))
208
- .map(r => `&relay=${encodeURIComponent(r)}`)
209
- .join('');
210
-
211
- const nc = nostrconnect + relayParams;
212
-
212
+ const nc = nostrconnect + '&relay=' + relay;
213
213
  if (a.iframeUrl) {
214
+ // pass plain nc url for iframe-based flow
214
215
  a.link = nc;
215
216
  } else {
217
+ // we will open popup ourselves
216
218
  a.link = a.link.replace('<nostrconnect>', nc);
217
219
  }
218
220
  }
@@ -238,7 +240,7 @@ class AuthNostrService extends EventEmitter implements Signer {
238
240
  this.releaseSigner();
239
241
  this.localSigner = new PrivateKeySigner(info.sk!);
240
242
 
241
- 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);
242
244
 
243
245
  this.onAuth(signup ? 'signup' : 'login', info);
244
246
  }
@@ -253,18 +255,19 @@ class AuthNostrService extends EventEmitter implements Signer {
253
255
  }
254
256
 
255
257
  public async importAndConnect(cs: ConnectionString) {
256
- const { relays, domain, link, iframeUrl } = cs;
258
+ const { relay, domain, link, iframeUrl } = cs;
257
259
  if (!domain) throw new Error('Domain required');
258
260
 
259
- const info = await this.nostrConnect(relays, {
260
- domain,
261
- link,
262
- importConnect: true,
263
- iframeUrl,
264
- });
261
+ const info = await this.nostrConnect(relay, { domain, link, importConnect: true, iframeUrl });
265
262
 
266
- await this.logout(true);
263
+ // logout to remove local keys from storage
264
+ // but keep the connect signer
265
+ await this.logout(/*keepSigner*/ true);
266
+
267
+ // release local one
267
268
  this.localSigner = null;
269
+
270
+ // notify app that we've switched to 'connect' keys
268
271
  this.onAuth('login', info);
269
272
  }
270
273
 
@@ -291,15 +294,6 @@ class AuthNostrService extends EventEmitter implements Signer {
291
294
  await this.endAuth();
292
295
  }
293
296
 
294
- public async setAmber(info: Info) {
295
- // Amber now uses NIP-46 (nostrconnect) like other signers
296
- this.releaseSigner();
297
- await this.startAuth();
298
- await this.initSigner(info);
299
- this.onAuth('login', info);
300
- await this.endAuth();
301
- }
302
-
303
297
  public async createAccount(nip05: string) {
304
298
  const [name, domain] = nip05.split('@');
305
299
 
@@ -319,8 +313,8 @@ class AuthNostrService extends EventEmitter implements Signer {
319
313
  const userPubkey = await this.signer!.createAccount2({ bunkerPubkey: info.signerPubkey!, name, domain, perms: this.params.optionsModal.perms });
320
314
 
321
315
  return {
322
- bunkerUrl: `bunker://${userPubkey}?` + (info.relays ?? []).map((r: string) => `relay=${encodeURIComponent(r)}`).join('&'),
323
- sk: info.sk,
316
+ bunkerUrl: `bunker://${userPubkey}?relay=${info.relays?.[0]}`,
317
+ sk: info.sk, // reuse the same local key
324
318
  };
325
319
  }
326
320
 
@@ -328,6 +322,11 @@ class AuthNostrService extends EventEmitter implements Signer {
328
322
  this.signer = null;
329
323
  this.signerErrCallback?.('cancelled');
330
324
  this.localSigner = null;
325
+
326
+ // disconnect from signer relays
327
+ for (const r of this.ndk.pool.relays.keys()) {
328
+ this.ndk.pool.removeRelay(r);
329
+ }
331
330
  }
332
331
 
333
332
  public async logout(keepSigner = false) {
@@ -372,7 +371,7 @@ class AuthNostrService extends EventEmitter implements Signer {
372
371
 
373
372
  if (info) {
374
373
  // async profile fetch
375
- fetchProfile(info, info.relays).then(p => {
374
+ fetchProfile(info, this.profileNdk).then(p => {
376
375
  if (this.params.userInfo !== info) return;
377
376
 
378
377
  const userInfo = {
@@ -501,7 +500,7 @@ class AuthNostrService extends EventEmitter implements Signer {
501
500
  return !!this.readyCallback;
502
501
  }
503
502
 
504
- public startAuth() {
503
+ public async startAuth() {
505
504
  console.log('startAuth');
506
505
  if (this.readyCallback) throw new Error('Already started');
507
506
 
@@ -562,71 +561,24 @@ class AuthNostrService extends EventEmitter implements Signer {
562
561
  // FIXME shouldn't this come from nostrconnect service list?
563
562
  this.emit('onIframeUrl', info.iframeUrl);
564
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
+
565
572
  this.signerPromise = new Promise<void>(async (ok, err) => {
566
573
  this.signerErrCallback = err;
567
574
  try {
568
- // create and prepare the signer
569
- const localSigner = new PrivateKeySigner(info.sk!);
570
-
571
- if (info.iframeUrl) {
572
- // use NDK-free iframe signer implementation (MessagePort + SimplePool)
573
- this.signer = new Nip46Signer(localSigner, info.signerPubkey!, iframeOrigin, info.relays || []);
574
-
575
- // we should notify the banner the same way as the onAuthUrl does
576
- this.signer.on(`iframeRestart`, async () => {
577
- const iframeUrl = info.iframeUrl + (info.iframeUrl!.includes('?') ? '&' : '?') + 'pubkey=' + info.pubkey + '&rebind=' + localSigner.pubkey;
578
- this.emit('iframeRestart', { pubkey: info.pubkey, iframeUrl });
579
- });
580
-
581
- // OAuth flow
582
- this.signer.on('authUrl', (url: string) => {
583
- console.log('nostr login auth url', url);
584
-
585
- // notify our UI
586
- this.emit('onAuthUrl', { url, iframeUrl: info.iframeUrl, eventToAddAccount });
587
- });
588
- } else {
589
- // New SimplePool-based NIP-46 flow
590
- const client = new Nip46Client({
591
- localPrivateKey: info.sk!,
592
- remotePubkey: info.signerPubkey!,
593
- relays: info.relays || [],
594
- timeoutMs: 30000,
595
- });
596
-
597
- const adapter = new Nip46Adapter(client, localSigner);
598
- this.signer = adapter;
599
-
600
- // OAuth flow: forward authUrl events
601
- this.signer.on('authUrl', (url: string) => {
602
- console.log('nostr login auth url', url);
603
- this.emit('onAuthUrl', { url, iframeUrl: info.iframeUrl, eventToAddAccount });
604
- });
605
- }
606
- if (listen) {
607
- // nostrconnect: flow
608
- // wait for the incoming message from signer
609
- await this.listen(info);
610
- } else if (connect) {
611
- // bunker: flow
612
- // send 'connect' message to signer
613
- await this.connect(info, this.params.optionsModal.perms);
614
- } else {
615
- // provide saved pubkey as a hint
616
- await this.signer!.initUserPubkey(info.pubkey);
617
- }
618
-
619
- // ensure, we're using it in callbacks above
620
- // and expect info to be valid after this call
621
- info.pubkey = this.signer!.userPubkey;
622
- // learned after nostrconnect flow
623
- info.signerPubkey = this.signer!.remotePubkey;
624
-
625
- ok();
575
+ // タイムアウトとキャンセルの両方に対応
576
+ await Promise.race([this.initSignerInternal(info, listen, connect, eventToAddAccount, ok), abortPromise]);
626
577
  } catch (e) {
627
578
  console.log('initSigner failure', e);
628
579
  // make sure signer isn't set
629
580
  this.signer = null;
581
+ this.signerAbortController = undefined;
630
582
  err(e);
631
583
  }
632
584
  });
@@ -634,9 +586,73 @@ class AuthNostrService extends EventEmitter implements Signer {
634
586
  return this.signerPromise;
635
587
  }
636
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
+
637
646
  public async authNip46(
638
647
  type: 'login' | 'signup',
639
- { 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[] },
640
656
  ) {
641
657
  try {
642
658
  const info = bunkerUrlToInfo(bunkerUrl, sk);
@@ -648,8 +664,13 @@ class AuthNostrService extends EventEmitter implements Signer {
648
664
  if (domain) info.domain = domain;
649
665
  if (iframeUrl) info.iframeUrl = iframeUrl;
650
666
 
667
+ // カスタムリレーが指定されていれば使用する
668
+ if (customRelays && customRelays.length > 0) {
669
+ info.relays = customRelays;
670
+ }
671
+
651
672
  // console.log('nostr login auth info', info);
652
- if (!info.signerPubkey || !info.sk || !info.relays || info.relays.length === 0) {
673
+ if (!info.signerPubkey || !info.sk || !info.relays?.[0]) {
653
674
  throw new Error(`Bad bunker url ${bunkerUrl}`);
654
675
  }
655
676
 
@@ -670,33 +691,22 @@ class AuthNostrService extends EventEmitter implements Signer {
670
691
  }
671
692
 
672
693
  public async signEvent(event: any) {
673
- const timeoutMs = 20000;
674
-
675
- const signPromise = (async () => {
676
- if (this.localSigner) {
677
- event.pubkey = getPublicKey(this.localSigner.privateKey!);
678
- event.id = getEventHash(event);
679
- event.sig = await this.localSigner.sign(event);
680
- } else {
681
- event.pubkey = this.signer?.remotePubkey;
682
- event.id = getEventHash(event);
683
- event.sig = await this.signer?.sign(event);
684
- }
685
- return event;
686
- })();
687
-
688
- const timeoutPromise = new Promise((_, reject) => {
689
- setTimeout(() => reject(new Error('Sign timeout')), timeoutMs);
690
- });
691
-
692
- const result = await Promise.race([signPromise, timeoutPromise]);
693
- console.log('signed', { event: result });
694
- 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;
695
705
  }
696
706
 
697
707
  private async codec_call(method: string, pubkey: string, param: string) {
698
708
  return new Promise<string>((resolve, reject) => {
699
- 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) => {
700
710
  if (!response.error) {
701
711
  resolve(response.result);
702
712
  } else {
@@ -708,27 +718,20 @@ class AuthNostrService extends EventEmitter implements Signer {
708
718
 
709
719
  public async encrypt04(pubkey: string, plaintext: string) {
710
720
  if (this.localSigner) {
711
- return this.localSigner.encrypt(pubkey, plaintext);
721
+ return this.localSigner.encrypt(new NDKUser({ pubkey }), plaintext);
712
722
  } else {
713
- // adapter supports encrypt(pubkey, plaintext)
714
- if (this.signer && typeof this.signer.encrypt === 'function') {
715
- return this.signer.encrypt(pubkey, plaintext);
716
- }
717
- // fallback to remote codec via signer RPC
718
- return this.codec_call('nip04_encrypt', pubkey, plaintext);
723
+ return this.signer!.encrypt(new NDKUser({ pubkey }), plaintext);
719
724
  }
720
725
  }
721
726
 
722
727
  public async decrypt04(pubkey: string, ciphertext: string) {
723
728
  if (this.localSigner) {
724
- return this.localSigner.decrypt(pubkey, ciphertext);
729
+ return this.localSigner.decrypt(new NDKUser({ pubkey }), ciphertext);
725
730
  } else {
726
- // If signer supports direct decrypt(pubkey, ciphertext), use it
727
- if (this.signer && typeof this.signer.decrypt === 'function') {
728
- return this.signer.decrypt(pubkey, ciphertext);
729
- }
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
730
734
 
731
- // fallback to remote codec via signer RPC
732
735
  return this.codec_call('nip04_decrypt', pubkey, ciphertext);
733
736
  }
734
737
  }
@@ -737,7 +740,7 @@ class AuthNostrService extends EventEmitter implements Signer {
737
740
  if (this.localSigner) {
738
741
  return this.nip44Codec.encrypt(this.localSigner.privateKey!, pubkey, plaintext);
739
742
  } else {
740
- // no support of nip44 in legacy signer implementation
743
+ // no support of nip44 in ndk yet
741
744
  return this.codec_call('nip44_encrypt', pubkey, plaintext);
742
745
  }
743
746
  }
@@ -746,7 +749,7 @@ class AuthNostrService extends EventEmitter implements Signer {
746
749
  if (this.localSigner) {
747
750
  return this.nip44Codec.decrypt(this.localSigner.privateKey!, pubkey, ciphertext);
748
751
  } else {
749
- // no support of nip44 in legacy signer implementation
752
+ // no support of nip44 in ndk yet
750
753
  return this.codec_call('nip44_decrypt', pubkey, ciphertext);
751
754
  }
752
755
  }