@konemono/nostr-login 1.8.0 → 1.9.1

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.8.0",
3
+ "version": "1.9.1",
4
4
  "description": "",
5
5
  "main": "./dist/index.esm.js",
6
6
  "types": "./dist/index.d.ts",
@@ -30,12 +30,14 @@ export class AmberDirectSigner implements Signer {
30
30
  public async getPublicKey(id?: string): Promise<string> {
31
31
  id = id || Math.random().toString(36).substring(7);
32
32
  const url = this.generateUrl('', 'get_public_key', id);
33
- console.log('Amber redirecting to:', url);
33
+ console.log('[AmberDirectSigner] Requesting public key, URL:', url.substring(0, 100) + '...');
34
34
  window.open(url, '_blank', 'width=400,height=600');
35
35
  return new Promise((resolve, reject) => {
36
36
  const timer = setTimeout(() => {
37
37
  AmberDirectSigner.pendingResolves.delete(id!);
38
- reject(new Error('AmberDirectSigner timeout'));
38
+ const error = new Error('[AmberDirectSigner] getPublicKey timeout after 20s');
39
+ console.error(error.message);
40
+ reject(error);
39
41
  }, 20000);
40
42
  AmberDirectSigner.pendingResolves.set(id!, { resolve, timer });
41
43
  });
@@ -208,9 +210,11 @@ export class AmberDirectSigner implements Signer {
208
210
  if (entry) {
209
211
  this.pendingResolves.delete(id);
210
212
  if (entry.timer) clearTimeout(entry.timer);
213
+ console.log('[AmberDirectSigner] Resolving pending:', { id, type, resultLen: result.length });
211
214
  entry.resolve(result);
212
215
  return true;
213
216
  }
217
+ console.warn('[AmberDirectSigner] No pending resolve found for:', id);
214
218
  return false;
215
219
  }
216
220
 
@@ -76,26 +76,31 @@ class AuthNostrService extends EventEmitter implements Signer {
76
76
  decrypt: this.decrypt44.bind(this),
77
77
  };
78
78
 
79
- setTimeout(() => this.checkAmberResponse(), 100);
80
-
79
+ // Amberレスポンス検出: ポップアップから戻った時
81
80
  const check = () => {
81
+ console.log('[AuthNostrService] Checking Amber response');
82
82
  this.checkAmberResponse();
83
83
  };
84
84
 
85
+ // 主要なイベントで検出(ポーリングは廃止)
85
86
  window.addEventListener('focus', check);
86
87
  window.addEventListener('visibilitychange', () => {
87
- if (document.visibilityState === 'visible') check();
88
+ if (document.visibilityState === 'visible') {
89
+ console.log('[AuthNostrService] Page visible, checking Amber response');
90
+ check();
91
+ }
88
92
  });
89
93
  window.addEventListener('popstate', check);
90
94
  window.addEventListener('hashchange', check);
91
95
 
92
- // Periodic check as a safety net
93
- setInterval(check, 1000);
96
+ // 初期チェック
97
+ setTimeout(check, 100);
94
98
 
99
+ // ポップアップからのメッセージ受信
95
100
  window.addEventListener('message', event => {
96
101
  if (event.data && event.data.method === 'amberResponse') {
97
102
  const { id, type, result } = event.data;
98
- console.log('Amber response received via message', { id, type, result });
103
+ console.log('[AuthNostrService] Amber response received via message', { id, type, result });
99
104
  this.handleAmberResponse({ id, type, result });
100
105
  }
101
106
  });
@@ -104,9 +109,11 @@ class AuthNostrService extends EventEmitter implements Signer {
104
109
  private checkAmberResponse() {
105
110
  const response = AmberDirectSigner.parseResponse();
106
111
  if (response) {
112
+ console.log('[AuthNostrService] Amber response detected', response);
113
+
107
114
  // If we have an opener and it's not the same window, we are in a popup
108
115
  if (window.opener && window.opener !== window) {
109
- console.log('Amber response in popup, sending back to opener');
116
+ console.log('[AuthNostrService] In popup, sending response back to opener');
110
117
  window.opener.postMessage({ method: 'amberResponse', ...response }, window.location.origin);
111
118
  window.close();
112
119
  return;
@@ -119,10 +126,13 @@ class AuthNostrService extends EventEmitter implements Signer {
119
126
  private handledAmberIds: Set<string> = new Set();
120
127
 
121
128
  private handleAmberResponse(response: { id: string; type: string; result: string }) {
122
- if (this.handledAmberIds.has(response.id)) return;
129
+ if (this.handledAmberIds.has(response.id)) {
130
+ console.log('[AuthNostrService] Amber response already handled:', response.id);
131
+ return;
132
+ }
123
133
  this.handledAmberIds.add(response.id);
124
134
 
125
- console.log('Handling Amber response', response);
135
+ console.log('[AuthNostrService] Handling Amber response:', response);
126
136
 
127
137
  // Stop the "Connecting..." spinner
128
138
  this.emit('onAuthUrl', { url: '' });
@@ -130,7 +140,7 @@ class AuthNostrService extends EventEmitter implements Signer {
130
140
  // Resolve pending promises if any (for non-reload cases)
131
141
  const resolved = AmberDirectSigner.resolvePending(response.id, response.type, response.result);
132
142
  if (resolved) {
133
- console.log('Resolved pending Amber promise via resolvePending');
143
+ console.log('[AuthNostrService] Resolved pending Amber promise');
134
144
  }
135
145
 
136
146
  if (response.type === 'get_public_key' || response.type.includes('pub')) {
@@ -141,7 +151,7 @@ class AuthNostrService extends EventEmitter implements Signer {
141
151
  relays: [],
142
152
  signerPubkey: '',
143
153
  };
144
- console.log('Amber login success', info);
154
+ console.log('[AuthNostrService] Amber login success:', info);
145
155
  this.onAuth('login', info);
146
156
  }
147
157
 
@@ -162,7 +172,7 @@ class AuthNostrService extends EventEmitter implements Signer {
162
172
  }
163
173
 
164
174
  if (changed) {
165
- console.log('Cleaning up Amber response URL', url.toString());
175
+ console.log('[AuthNostrService] Cleaning up Amber response URL');
166
176
  window.history.replaceState({}, '', url.toString());
167
177
  }
168
178
  }
@@ -419,8 +429,16 @@ class AuthNostrService extends EventEmitter implements Signer {
419
429
  }
420
430
 
421
431
  public async setAmber(info: Info) {
432
+ console.log('setAmber', info);
433
+
434
+ // クリア
422
435
  this.releaseSigner();
423
436
  this.amberSigner = new AmberDirectSigner(info.pubkey);
437
+
438
+ // Amberは署名リクエストをウォレットに投げるだけなので、特別な初期化は不要
439
+ // signEventなどが直接amberSignerを使用する
440
+
441
+ // 認証完了
424
442
  this.onAuth('login', info);
425
443
  }
426
444
 
@@ -656,7 +674,7 @@ class AuthNostrService extends EventEmitter implements Signer {
656
674
  }
657
675
 
658
676
  private async listen(info: Info) {
659
- if (!info.iframeUrl) return this.signer!.listen(this.nostrConnectSecret);
677
+ if (!info.iframeUrl) return this.signer!.listen(this.nostrConnectSecret, 60000);
660
678
  const r = await this.starterReady!.wait();
661
679
  if (r[0] === 'starterError') throw new Error(r[1]);
662
680
  return this.signer!.setListenReply(r[1], this.nostrConnectSecret);
@@ -717,6 +735,11 @@ class AuthNostrService extends EventEmitter implements Signer {
717
735
  remotePubkey: info.signerPubkey!,
718
736
  relays: info.relays || [],
719
737
  timeoutMs: 30000,
738
+ useNip44: true,
739
+ retryConfig: {
740
+ maxRetries: 3,
741
+ retryDelayMs: 1000,
742
+ },
720
743
  });
721
744
 
722
745
  const adapter = new Nip46Adapter(client, localSigner);
@@ -773,24 +796,44 @@ class AuthNostrService extends EventEmitter implements Signer {
773
796
  if (domain) info.domain = domain;
774
797
  if (iframeUrl) info.iframeUrl = iframeUrl;
775
798
 
776
- // console.log('nostr login auth info', info);
799
+ console.log('authNip46', type, info);
800
+
777
801
  if (!info.signerPubkey || !info.sk || !info.relays || info.relays.length === 0) {
778
- throw new Error(`Bad bunker url ${bunkerUrl}`);
802
+ throw new Error(`Invalid bunker URL format`);
779
803
  }
780
804
 
781
805
  const eventToAddAccount = Boolean(this.params.userInfo);
782
- console.log('authNip46', type, info);
806
+
807
+ // 接続モードに応じた処理
808
+ const connectMode = type === 'login' && !info.token ? 'listen' : 'connect';
809
+ console.log('authNip46 connection mode:', connectMode);
783
810
 
784
811
  // updates the info
785
- await this.initSigner(info, { connect: true, eventToAddAccount });
812
+ await this.initSigner(info, {
813
+ listen: connectMode === 'listen',
814
+ connect: connectMode === 'connect',
815
+ eventToAddAccount,
816
+ });
786
817
 
787
818
  // callback
788
819
  this.onAuth(type, info);
789
- } catch (e) {
790
- console.log('nostr login auth failed', e);
791
- // make ure it's closed
792
- // this.popupManager.closePopup();
793
- throw e;
820
+ } catch (error: any) {
821
+ console.error('nostr-login auth failed:', {
822
+ type,
823
+ error: error.message,
824
+ stack: error.stack,
825
+ });
826
+
827
+ // エラーの種類に応じた処理
828
+ if (error.message.includes('timeout') || error.message.includes('timed out')) {
829
+ throw new Error('接続がタイムアウトしました。ネットワークを確認してください。');
830
+ } else if (error.message.includes('Invalid bunker URL')) {
831
+ throw new Error('無効なbunker URL形式です。');
832
+ } else if (error.message.includes('publish')) {
833
+ throw new Error('リレーへの接続に失敗しました。再度お試しください。');
834
+ } else {
835
+ throw new Error(`認証に失敗しました: ${error.message}`);
836
+ }
794
837
  }
795
838
  }
796
839
 
@@ -27,38 +27,83 @@ export class Nip46Adapter extends EventEmitter {
27
27
  if (this.userPubkey) throw new Error('Already called initUserPubkey');
28
28
  if (hintPubkey) {
29
29
  this.userPubkey = hintPubkey;
30
+ console.log('[Nip46Adapter] User pubkey set from hint:', hintPubkey);
30
31
  return;
31
32
  }
32
33
 
33
- const res = await this.client.sendRequest('get_public_key', []);
34
- if (!res) throw new Error('No public key returned');
35
- this.userPubkey = res;
34
+ console.log('[Nip46Adapter] Fetching user pubkey');
35
+ try {
36
+ const res = await this.client.sendRequest('get_public_key', []);
37
+ if (!res) throw new Error('No public key returned');
38
+ this.userPubkey = res;
39
+ console.log('[Nip46Adapter] User pubkey fetched:', res);
40
+ } catch (error: any) {
41
+ console.error('[Nip46Adapter] Failed to get user pubkey:', error.message);
42
+ throw error;
43
+ }
36
44
  }
37
45
 
38
- async listen(nostrConnectSecret: string): Promise<string> {
39
- return new Promise<string>((ok, err) => {
46
+ /**
47
+ * nostrconnect:// フロー - 受信待機
48
+ * サイナーからの接続を待つ
49
+ */
50
+ async listen(nostrConnectSecret: string, timeoutMs: number = 60000): Promise<string> {
51
+ console.log('[Nip46Adapter] Starting listen mode, timeout:', timeoutMs);
52
+
53
+ return new Promise<string>((resolve, reject) => {
54
+ const timer = setTimeout(() => {
55
+ cleanup();
56
+ const error = new Error(`Listen timeout after ${timeoutMs}ms`);
57
+ console.error('[Nip46Adapter]', error.message);
58
+ reject(error);
59
+ }, timeoutMs);
60
+
61
+ const cleanup = () => {
62
+ clearTimeout(timer);
63
+ this.client.off('response', onResponse);
64
+ };
65
+
40
66
  const onResponse = ({ response, pubkey }: any) => {
41
67
  if (!response) return;
68
+
69
+ // auth_urlは無視(別のハンドラが処理)
42
70
  if (response.result === 'auth_url') return;
71
+
72
+ // 成功: ack または secret が返される
43
73
  if (response.result === 'ack' || response.result === nostrConnectSecret) {
44
- this.client.off('response', onResponse);
45
- ok(pubkey);
74
+ cleanup();
75
+ console.log('[Nip46Adapter] Listen succeeded, signer pubkey:', pubkey);
76
+ resolve(pubkey);
77
+ } else if (response.error) {
78
+ cleanup();
79
+ console.error('[Nip46Adapter] Listen failed:', response.error);
80
+ reject(new Error(response.error));
46
81
  }
47
82
  };
48
83
 
49
84
  this.client.on('response', onResponse);
50
-
51
- // also add a timeout
52
- setTimeout(() => {
53
- this.client.off('response', onResponse);
54
- err(new Error('Listen timeout'));
55
- }, 30000);
56
85
  });
57
86
  }
58
87
 
59
- async connect(token?: string, perms?: string) {
60
- const result = await this.client.sendRequest('connect', [this.localSigner.pubkey, token || '', perms || '']);
61
- if (result !== 'ack') throw new Error(result || 'connect failed');
88
+ /**
89
+ * bunker:// フロー - 能動的接続
90
+ * サイナーに接続リクエストを送る
91
+ */
92
+ async connect(token?: string, perms?: string, timeoutMs: number = 30000): Promise<void> {
93
+ console.log('[Nip46Adapter] Connecting with token, timeout:', timeoutMs);
94
+
95
+ try {
96
+ const result = await this.client.sendRequest('connect', [this.localSigner.pubkey, token || '', perms || ''], timeoutMs);
97
+
98
+ if (result !== 'ack') {
99
+ throw new Error(`Connect failed: ${result || 'unknown error'}`);
100
+ }
101
+
102
+ console.log('[Nip46Adapter] Connected successfully');
103
+ } catch (error: any) {
104
+ console.error('[Nip46Adapter] Connect failed:', error.message);
105
+ throw error;
106
+ }
62
107
  }
63
108
 
64
109
  async setListenReply(reply: any, nostrConnectSecret: string) {
@@ -80,10 +125,17 @@ export class Nip46Adapter extends EventEmitter {
80
125
  async createAccount2({ bunkerPubkey, name, domain, perms = '' }: { bunkerPubkey: string; name: string; domain: string; perms?: string }) {
81
126
  const params = [name, domain, '', perms];
82
127
 
83
- const r = await this.client.sendRequest('create_account', params);
84
- if (!r) throw new Error('create_account failed');
85
- if (r === 'error') throw new Error('create_account error');
86
- return r;
128
+ console.log('[Nip46Adapter] Creating account:', { name, domain });
129
+ try {
130
+ const r = await this.client.sendRequest('create_account', params);
131
+ if (!r) throw new Error('create_account returned empty result');
132
+ if (r === 'error') throw new Error('create_account failed');
133
+ console.log('[Nip46Adapter] Account created successfully');
134
+ return r;
135
+ } catch (error: any) {
136
+ console.error('[Nip46Adapter] Failed to create account:', error.message);
137
+ throw error;
138
+ }
87
139
  }
88
140
 
89
141
  async encrypt(recipientPubkey: string, plaintext: string) {
@@ -97,14 +149,19 @@ export class Nip46Adapter extends EventEmitter {
97
149
  }
98
150
 
99
151
  async sign(event: any) {
100
- const r = await this.client.sendRequest('sign_event', [JSON.stringify(event)]);
101
152
  try {
102
- const parsed = typeof r === 'string' ? JSON.parse(r) : r;
103
- if (parsed && parsed.sig) return parsed.sig;
104
- } catch (e) {
105
- // not JSON
153
+ const r = await this.client.sendRequest('sign_event', [JSON.stringify(event)]);
154
+ try {
155
+ const parsed = typeof r === 'string' ? JSON.parse(r) : r;
156
+ if (parsed && parsed.sig) return parsed.sig;
157
+ } catch (e) {
158
+ // not JSON
159
+ }
160
+ return r;
161
+ } catch (error: any) {
162
+ console.error('[Nip46Adapter] Failed to sign event:', error.message);
163
+ throw error;
106
164
  }
107
- return r;
108
165
  }
109
166
 
110
167
  // provide rpc compatibility
@@ -15,6 +15,9 @@ export class Nip46Client extends EventEmitter {
15
15
  private subscription: any = null;
16
16
  private isSubscribed: boolean = false;
17
17
  private nip44Codec: Nip44 = new Nip44();
18
+ private iframeConfig?: { origin: string; port?: MessagePort };
19
+ private retryConfig: { maxRetries: number; retryDelayMs: number };
20
+ private iframeKeepaliveInterval?: NodeJS.Timeout;
18
21
 
19
22
  constructor(options: Nip46ClientOptions) {
20
23
  super();
@@ -24,6 +27,13 @@ export class Nip46Client extends EventEmitter {
24
27
  this.relays = options.relays;
25
28
  this.defaultTimeoutMs = options.timeoutMs || 30000;
26
29
  this.useNip44 = options.useNip44 || false;
30
+ this.iframeConfig = options.iframeConfig;
31
+ this.retryConfig = options.retryConfig || { maxRetries: 3, retryDelayMs: 1000 };
32
+
33
+ // iframe用のメッセージハンドラを設定
34
+ if (this.iframeConfig?.port) {
35
+ this.setupIframePort(this.iframeConfig.port);
36
+ }
27
37
  }
28
38
 
29
39
  get localPubkey(): string {
@@ -31,9 +41,40 @@ export class Nip46Client extends EventEmitter {
31
41
  }
32
42
 
33
43
  /**
34
- * NIP-46リクエストを送信
44
+ * NIP-46リクエストを送信(リトライ機能付き)
35
45
  */
36
46
  async sendRequest(method: string, params: string[] = [], timeoutMs?: number): Promise<string> {
47
+ let lastError: Error | null = null;
48
+
49
+ for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
50
+ try {
51
+ if (attempt > 0) {
52
+ const delay = this.retryConfig.retryDelayMs * attempt;
53
+ console.log(`[Nip46Client] Retry attempt ${attempt}/${this.retryConfig.maxRetries} for ${method} after ${delay}ms`);
54
+ await new Promise(resolve => setTimeout(resolve, delay));
55
+ }
56
+
57
+ return await this.sendRequestInternal(method, params, timeoutMs);
58
+ } catch (error: any) {
59
+ lastError = error;
60
+
61
+ // タイムアウトまたはネットワークエラーの場合のみリトライ
62
+ const shouldRetry = error.message.includes('timed out') || error.message.includes('network') || error.message.includes('publish');
63
+
64
+ if (!shouldRetry || attempt === this.retryConfig.maxRetries) {
65
+ console.error(`[Nip46Client] Request failed after ${attempt + 1} attempts:`, error.message);
66
+ throw error;
67
+ }
68
+ }
69
+ }
70
+
71
+ throw lastError || new Error('Request failed after retries');
72
+ }
73
+
74
+ /**
75
+ * NIP-46リクエストを送信(内部実装)
76
+ */
77
+ private async sendRequestInternal(method: string, params: string[] = [], timeoutMs?: number): Promise<string> {
37
78
  const timeout = timeoutMs || this.defaultTimeoutMs;
38
79
  const id = this.generateId();
39
80
  const request: Nip46Request = { id, method, params };
@@ -94,9 +135,25 @@ export class Nip46Client extends EventEmitter {
94
135
  // 署名
95
136
  event.sig = getSignature(event, this.localPrivateKey);
96
137
 
138
+ // iframeがある場合は優先的に使用
139
+ if (this.iframeConfig?.port) {
140
+ try {
141
+ this.iframeConfig.port.postMessage(event);
142
+ console.log('[Nip46Client] Request sent via iframe:', request.id);
143
+ return;
144
+ } catch (e) {
145
+ console.warn('[Nip46Client] Iframe send failed, falling back to relays:', e);
146
+ }
147
+ }
148
+
97
149
  // リレーに送信
98
- await Promise.any(this.pool.publish(this.relays, event));
99
- console.log('[Nip46Client] Request published:', request.id);
150
+ try {
151
+ await Promise.any(this.pool.publish(this.relays, event));
152
+ console.log('[Nip46Client] Request published to relays:', request.id);
153
+ } catch (e) {
154
+ console.error('[Nip46Client] Failed to publish to relays:', e);
155
+ throw new Error('Failed to publish request to relays');
156
+ }
100
157
  }
101
158
 
102
159
  /**
@@ -146,10 +203,12 @@ export class Nip46Client extends EventEmitter {
146
203
  // Emit response event for consumers (include sender pubkey)
147
204
  this.emit('response', { response, pubkey: event.pubkey });
148
205
 
149
- // auth_urlの特別処理
206
+ // auth_urlの特別処理: OAuth完了後に実際のレスポンスが来るまで待つ
150
207
  if (response.result === 'auth_url') {
151
208
  console.log('[Nip46Client] Auth URL received:', response.error);
152
209
  this.emit('authUrl', response.error);
210
+ // 注意: pendingRequestsは削除しない
211
+ // OAuth完了後に同じIDで実際のレスポンスが返ってくる
153
212
  return;
154
213
  }
155
214
 
@@ -232,6 +291,12 @@ export class Nip46Client extends EventEmitter {
232
291
  this.isSubscribed = false;
233
292
  }
234
293
 
294
+ // iframeのkeepaliveをクリア
295
+ if (this.iframeKeepaliveInterval) {
296
+ clearInterval(this.iframeKeepaliveInterval);
297
+ this.iframeKeepaliveInterval = undefined;
298
+ }
299
+
235
300
  // リレー接続を閉じる
236
301
  this.pool.close(this.relays);
237
302
 
@@ -245,4 +310,47 @@ export class Nip46Client extends EventEmitter {
245
310
  isConnected(): boolean {
246
311
  return this.isSubscribed;
247
312
  }
313
+
314
+ /**
315
+ * iframeポートを設定
316
+ */
317
+ setIframePort(port: MessagePort): void {
318
+ if (!this.iframeConfig) {
319
+ throw new Error('Iframe config not set');
320
+ }
321
+ this.iframeConfig.port = port;
322
+ this.setupIframePort(port);
323
+ }
324
+
325
+ /**
326
+ * iframe用のメッセージハンドラを設定
327
+ */
328
+ private setupIframePort(port: MessagePort): void {
329
+ port.onmessage = async (ev: MessageEvent) => {
330
+ if (ev.data === 'ping') return;
331
+
332
+ try {
333
+ const event = ev.data;
334
+ // validate and handle event
335
+ if (!event || typeof event !== 'object') {
336
+ console.warn('[Nip46Client] Invalid message from iframe');
337
+ return;
338
+ }
339
+ await this.handleResponseEvent(event);
340
+ } catch (e) {
341
+ console.error('[Nip46Client] Iframe message error:', e);
342
+ }
343
+ };
344
+
345
+ // Keep alive
346
+ this.iframeKeepaliveInterval = setInterval(() => {
347
+ try {
348
+ port.postMessage('ping');
349
+ } catch (e) {
350
+ console.warn('[Nip46Client] Failed to send keepalive ping:', e);
351
+ }
352
+ }, 5000);
353
+
354
+ console.log('[Nip46Client] Iframe port setup complete');
355
+ }
248
356
  }
@@ -1,26 +1,34 @@
1
1
  export interface Nip46Request {
2
- id: string;
3
- method: string;
4
- params: string[];
2
+ id: string;
3
+ method: string;
4
+ params: string[];
5
5
  }
6
6
 
7
7
  export interface Nip46Response {
8
- id: string;
9
- result?: string;
10
- error?: string;
8
+ id: string;
9
+ result?: string;
10
+ error?: string;
11
11
  }
12
12
 
13
13
  export interface PendingRequest {
14
- resolve: (result: string) => void;
15
- reject: (error: Error) => void;
16
- timer: NodeJS.Timeout;
17
- method: string;
14
+ resolve: (result: string) => void;
15
+ reject: (error: Error) => void;
16
+ timer: NodeJS.Timeout;
17
+ method: string;
18
18
  }
19
19
 
20
20
  export interface Nip46ClientOptions {
21
- localPrivateKey: string;
22
- remotePubkey: string;
23
- relays: string[];
24
- timeoutMs?: number;
25
- useNip44?: boolean;
21
+ localPrivateKey: string;
22
+ remotePubkey: string;
23
+ relays: string[];
24
+ timeoutMs?: number;
25
+ useNip44?: boolean;
26
+ iframeConfig?: {
27
+ origin: string;
28
+ port?: MessagePort;
29
+ };
30
+ retryConfig?: {
31
+ maxRetries: number;
32
+ retryDelayMs: number;
33
+ };
26
34
  }