@konemono/nostr-login 1.9.13 → 1.9.14

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.9.13",
3
+ "version": "1.9.14",
4
4
  "description": "",
5
5
  "main": "./dist/index.esm.js",
6
6
  "types": "./dist/index.d.ts",
@@ -30,14 +30,12 @@ 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('[AmberDirectSigner] Requesting public key, URL:', url.substring(0, 100) + '...');
33
+ console.log('Amber redirecting to:', url);
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
- const error = new Error('[AmberDirectSigner] getPublicKey timeout after 20s');
39
- console.error(error.message);
40
- reject(error);
38
+ reject(new Error('AmberDirectSigner timeout'));
41
39
  }, 20000);
42
40
  AmberDirectSigner.pendingResolves.set(id!, { resolve, timer });
43
41
  });
@@ -123,12 +121,9 @@ export class AmberDirectSigner implements Signer {
123
121
  }
124
122
 
125
123
  public generateUrl(content: string, type: string, id: string, pubkey?: string): string {
126
- const baseUrl = window.location.href.split('#')[0].split('?')[0];
124
+ const callbackUrl = window.location.href.split('#')[0].split('?')[0];
127
125
  const encodedContent = encodeURIComponent(content || '');
128
-
129
- // callbackUrl にはベースURLだけを渡す
130
- // Amberが自動的に ?event=<result> を追加する (NIP-55)
131
- const callbackUrl = baseUrl;
126
+ const encodedCallback = encodeURIComponent(callbackUrl);
132
127
 
133
128
  localStorage.setItem('amber_last_type', type);
134
129
  localStorage.setItem('amber_last_id', id);
@@ -170,13 +165,6 @@ export class AmberDirectSigner implements Signer {
170
165
  // パターン1: NIP-55標準 (?event=...)
171
166
  let result = url.searchParams.get('event');
172
167
 
173
- // Amberが壊れた{result}プレースホルダーを返した場合のクリーンアップ
174
- // 例: ?event=result}5650178... → 5650178...
175
- if (result && result.includes('result}')) {
176
- result = result.replace(/^result\}/, '');
177
- console.log('[AmberDirectSigner] Cleaned up malformed result:', result);
178
- }
179
-
180
168
  // パターン2: パス末尾にhexId (/<hexId>)
181
169
  if (!result) {
182
170
  const pathParts = url.pathname.split('/').filter(Boolean);
@@ -220,11 +208,9 @@ export class AmberDirectSigner implements Signer {
220
208
  if (entry) {
221
209
  this.pendingResolves.delete(id);
222
210
  if (entry.timer) clearTimeout(entry.timer);
223
- console.log('[AmberDirectSigner] Resolving pending:', { id, type, resultLen: result.length });
224
211
  entry.resolve(result);
225
212
  return true;
226
213
  }
227
- console.warn('[AmberDirectSigner] No pending resolve found for:', id);
228
214
  return false;
229
215
  }
230
216
 
@@ -11,6 +11,7 @@ import { IframeNostrRpc, Nip46Signer, ReadyListener, RpcResponse } from './Nip46
11
11
  import { Nip46Client } from './nip46/Nip46Client';
12
12
  import { Nip46Adapter } from './nip46/Nip46Adapter';
13
13
  import { PrivateKeySigner } from './Signer';
14
+ import { AmberDirectSigner } from './AmberDirectSigner';
14
15
 
15
16
  const OUTBOX_RELAYS = ['wss://user.kindpag.es', 'wss://purplepag.es', 'wss://relay.nos.social'];
16
17
  const DEFAULT_NOSTRCONNECT_RELAYS = ['wss://relay.nsec.app/', 'wss://ephemeral.snowflare.cc/'];
@@ -27,7 +28,7 @@ const NOSTRCONNECT_APPS: ConnectionString[] = [
27
28
  {
28
29
  name: 'Amber',
29
30
  img: 'https://raw.githubusercontent.com/greenart7c3/Amber/refs/heads/master/assets/android-icon.svg',
30
- link: '<nostrconnect>',
31
+ link: 'amber',
31
32
  relays: DEFAULT_NOSTRCONNECT_RELAYS,
32
33
  },
33
34
  {
@@ -40,6 +41,7 @@ const NOSTRCONNECT_APPS: ConnectionString[] = [
40
41
 
41
42
  class AuthNostrService extends EventEmitter implements Signer {
42
43
  private signer: any = null;
44
+ private amberSigner: AmberDirectSigner | null = null;
43
45
  private localSigner: PrivateKeySigner | null = null;
44
46
  private params: NostrParams;
45
47
  private signerPromise?: Promise<void>;
@@ -73,6 +75,96 @@ class AuthNostrService extends EventEmitter implements Signer {
73
75
  encrypt: this.encrypt44.bind(this),
74
76
  decrypt: this.decrypt44.bind(this),
75
77
  };
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
+ }
76
168
  }
77
169
 
78
170
  public isIframe() {
@@ -128,6 +220,41 @@ class AuthNostrService extends EventEmitter implements Signer {
128
220
 
129
221
  // non-iframe flow
130
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
+ }
131
258
  window.open(link, '_blank', 'width=400,height=700');
132
259
  }
133
260
 
@@ -164,7 +291,7 @@ class AuthNostrService extends EventEmitter implements Signer {
164
291
  perms: encodeURIComponent(this.params.optionsModal.perms || ''),
165
292
  };
166
293
 
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('') : ''}`;
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}`) : ''}`;
168
295
  }
169
296
 
170
297
  public async getNostrConnectServices(): Promise<[string, ConnectionString[]]> {
@@ -292,12 +419,9 @@ class AuthNostrService extends EventEmitter implements Signer {
292
419
  }
293
420
 
294
421
  public async setAmber(info: Info) {
295
- // Amber now uses NIP-46 (nostrconnect) like other signers
296
422
  this.releaseSigner();
297
- await this.startAuth();
298
- await this.initSigner(info);
423
+ this.amberSigner = new AmberDirectSigner(info.pubkey);
299
424
  this.onAuth('login', info);
300
- await this.endAuth();
301
425
  }
302
426
 
303
427
  public async createAccount(nip05: string) {
@@ -328,6 +452,7 @@ class AuthNostrService extends EventEmitter implements Signer {
328
452
  this.signer = null;
329
453
  this.signerErrCallback?.('cancelled');
330
454
  this.localSigner = null;
455
+ this.amberSigner = null;
331
456
  }
332
457
 
333
458
  public async logout(keepSigner = false) {
@@ -677,6 +802,10 @@ class AuthNostrService extends EventEmitter implements Signer {
677
802
  event.pubkey = getPublicKey(this.localSigner.privateKey!);
678
803
  event.id = getEventHash(event);
679
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);
680
809
  } else {
681
810
  event.pubkey = this.signer?.remotePubkey;
682
811
  event.id = getEventHash(event);
@@ -709,6 +838,10 @@ class AuthNostrService extends EventEmitter implements Signer {
709
838
  public async encrypt04(pubkey: string, plaintext: string) {
710
839
  if (this.localSigner) {
711
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);
712
845
  } else {
713
846
  // adapter supports encrypt(pubkey, plaintext)
714
847
  if (this.signer && typeof this.signer.encrypt === 'function') {
@@ -722,6 +855,10 @@ class AuthNostrService extends EventEmitter implements Signer {
722
855
  public async decrypt04(pubkey: string, ciphertext: string) {
723
856
  if (this.localSigner) {
724
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);
725
862
  } else {
726
863
  // If signer supports direct decrypt(pubkey, ciphertext), use it
727
864
  if (this.signer && typeof this.signer.decrypt === 'function') {
@@ -736,6 +873,10 @@ class AuthNostrService extends EventEmitter implements Signer {
736
873
  public async encrypt44(pubkey: string, plaintext: string) {
737
874
  if (this.localSigner) {
738
875
  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);
739
880
  } else {
740
881
  // no support of nip44 in legacy signer implementation
741
882
  return this.codec_call('nip44_encrypt', pubkey, plaintext);
@@ -745,6 +886,10 @@ class AuthNostrService extends EventEmitter implements Signer {
745
886
  public async decrypt44(pubkey: string, ciphertext: string) {
746
887
  if (this.localSigner) {
747
888
  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);
748
893
  } else {
749
894
  // no support of nip44 in legacy signer implementation
750
895
  return this.codec_call('nip44_decrypt', pubkey, ciphertext);
@@ -27,83 +27,38 @@ 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);
31
30
  return;
32
31
  }
33
32
 
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
- }
33
+ const res = await this.client.sendRequest('get_public_key', []);
34
+ if (!res) throw new Error('No public key returned');
35
+ this.userPubkey = res;
44
36
  }
45
37
 
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
-
38
+ async listen(nostrConnectSecret: string): Promise<string> {
39
+ return new Promise<string>((ok, err) => {
66
40
  const onResponse = ({ response, pubkey }: any) => {
67
41
  if (!response) return;
68
-
69
- // auth_urlは無視(別のハンドラが処理)
70
42
  if (response.result === 'auth_url') return;
71
-
72
- // 成功: ack または secret が返される
73
43
  if (response.result === 'ack' || response.result === nostrConnectSecret) {
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));
44
+ this.client.off('response', onResponse);
45
+ ok(pubkey);
81
46
  }
82
47
  };
83
48
 
84
49
  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);
85
56
  });
86
57
  }
87
58
 
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
- }
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');
107
62
  }
108
63
 
109
64
  async setListenReply(reply: any, nostrConnectSecret: string) {
@@ -125,17 +80,10 @@ export class Nip46Adapter extends EventEmitter {
125
80
  async createAccount2({ bunkerPubkey, name, domain, perms = '' }: { bunkerPubkey: string; name: string; domain: string; perms?: string }) {
126
81
  const params = [name, domain, '', perms];
127
82
 
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
- }
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;
139
87
  }
140
88
 
141
89
  async encrypt(recipientPubkey: string, plaintext: string) {
@@ -149,19 +97,14 @@ export class Nip46Adapter extends EventEmitter {
149
97
  }
150
98
 
151
99
  async sign(event: any) {
100
+ const r = await this.client.sendRequest('sign_event', [JSON.stringify(event)]);
152
101
  try {
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;
102
+ const parsed = typeof r === 'string' ? JSON.parse(r) : r;
103
+ if (parsed && parsed.sig) return parsed.sig;
104
+ } catch (e) {
105
+ // not JSON
164
106
  }
107
+ return r;
165
108
  }
166
109
 
167
110
  // provide rpc compatibility
@@ -4,13 +4,6 @@ import { EventEmitter } from 'tseep';
4
4
  import { Nip46Request, Nip46Response, PendingRequest, Nip46ClientOptions } from './types';
5
5
  import { getEventHash, getSignature } from 'nostr-tools';
6
6
 
7
- // Ensure crypto.subtle is available
8
- if (typeof globalThis.crypto === 'undefined' || !globalThis.crypto.subtle) {
9
- if (typeof globalThis.crypto !== 'undefined' && (globalThis.crypto as any).webcrypto) {
10
- (globalThis.crypto as any).subtle = (globalThis.crypto as any).webcrypto.subtle;
11
- }
12
- }
13
-
14
7
  export class Nip46Client extends EventEmitter {
15
8
  private pool: SimplePool;
16
9
  private localPrivateKey: string;
@@ -22,9 +15,6 @@ export class Nip46Client extends EventEmitter {
22
15
  private subscription: any = null;
23
16
  private isSubscribed: boolean = false;
24
17
  private nip44Codec: Nip44 = new Nip44();
25
- private iframeConfig?: { origin: string; port?: MessagePort };
26
- private retryConfig: { maxRetries: number; retryDelayMs: number };
27
- private iframeKeepaliveInterval?: NodeJS.Timeout;
28
18
 
29
19
  constructor(options: Nip46ClientOptions) {
30
20
  super();
@@ -34,13 +24,6 @@ export class Nip46Client extends EventEmitter {
34
24
  this.relays = options.relays;
35
25
  this.defaultTimeoutMs = options.timeoutMs || 30000;
36
26
  this.useNip44 = options.useNip44 || false;
37
- this.iframeConfig = options.iframeConfig;
38
- this.retryConfig = options.retryConfig || { maxRetries: 3, retryDelayMs: 1000 };
39
-
40
- // iframe用のメッセージハンドラを設定
41
- if (this.iframeConfig?.port) {
42
- this.setupIframePort(this.iframeConfig.port);
43
- }
44
27
  }
45
28
 
46
29
  get localPubkey(): string {
@@ -48,40 +31,9 @@ export class Nip46Client extends EventEmitter {
48
31
  }
49
32
 
50
33
  /**
51
- * NIP-46リクエストを送信(リトライ機能付き)
34
+ * NIP-46リクエストを送信
52
35
  */
53
36
  async sendRequest(method: string, params: string[] = [], timeoutMs?: number): Promise<string> {
54
- let lastError: Error | null = null;
55
-
56
- for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
57
- try {
58
- if (attempt > 0) {
59
- const delay = this.retryConfig.retryDelayMs * attempt;
60
- console.log(`[Nip46Client] Retry attempt ${attempt}/${this.retryConfig.maxRetries} for ${method} after ${delay}ms`);
61
- await new Promise(resolve => setTimeout(resolve, delay));
62
- }
63
-
64
- return await this.sendRequestInternal(method, params, timeoutMs);
65
- } catch (error: any) {
66
- lastError = error;
67
-
68
- // タイムアウトまたはネットワークエラーの場合のみリトライ
69
- const shouldRetry = error.message.includes('timed out') || error.message.includes('network') || error.message.includes('publish');
70
-
71
- if (!shouldRetry || attempt === this.retryConfig.maxRetries) {
72
- console.error(`[Nip46Client] Request failed after ${attempt + 1} attempts:`, error.message);
73
- throw error;
74
- }
75
- }
76
- }
77
-
78
- throw lastError || new Error('Request failed after retries');
79
- }
80
-
81
- /**
82
- * NIP-46リクエストを送信(内部実装)
83
- */
84
- private async sendRequestInternal(method: string, params: string[] = [], timeoutMs?: number): Promise<string> {
85
37
  const timeout = timeoutMs || this.defaultTimeoutMs;
86
38
  const id = this.generateId();
87
39
  const request: Nip46Request = { id, method, params };
@@ -142,25 +94,9 @@ export class Nip46Client extends EventEmitter {
142
94
  // 署名
143
95
  event.sig = getSignature(event, this.localPrivateKey);
144
96
 
145
- // iframeがある場合は優先的に使用
146
- if (this.iframeConfig?.port) {
147
- try {
148
- this.iframeConfig.port.postMessage(event);
149
- console.log('[Nip46Client] Request sent via iframe:', request.id);
150
- return;
151
- } catch (e) {
152
- console.warn('[Nip46Client] Iframe send failed, falling back to relays:', e);
153
- }
154
- }
155
-
156
97
  // リレーに送信
157
- try {
158
- await Promise.any(this.pool.publish(this.relays, event));
159
- console.log('[Nip46Client] Request published to relays:', request.id);
160
- } catch (e) {
161
- console.error('[Nip46Client] Failed to publish to relays:', e);
162
- throw new Error('Failed to publish request to relays');
163
- }
98
+ await Promise.any(this.pool.publish(this.relays, event));
99
+ console.log('[Nip46Client] Request published:', request.id);
164
100
  }
165
101
 
166
102
  /**
@@ -210,12 +146,10 @@ export class Nip46Client extends EventEmitter {
210
146
  // Emit response event for consumers (include sender pubkey)
211
147
  this.emit('response', { response, pubkey: event.pubkey });
212
148
 
213
- // auth_urlの特別処理: OAuth完了後に実際のレスポンスが来るまで待つ
149
+ // auth_urlの特別処理
214
150
  if (response.result === 'auth_url') {
215
151
  console.log('[Nip46Client] Auth URL received:', response.error);
216
152
  this.emit('authUrl', response.error);
217
- // 注意: pendingRequestsは削除しない
218
- // OAuth完了後に同じIDで実際のレスポンスが返ってくる
219
153
  return;
220
154
  }
221
155
 
@@ -298,12 +232,6 @@ export class Nip46Client extends EventEmitter {
298
232
  this.isSubscribed = false;
299
233
  }
300
234
 
301
- // iframeのkeepaliveをクリア
302
- if (this.iframeKeepaliveInterval) {
303
- clearInterval(this.iframeKeepaliveInterval);
304
- this.iframeKeepaliveInterval = undefined;
305
- }
306
-
307
235
  // リレー接続を閉じる
308
236
  this.pool.close(this.relays);
309
237
 
@@ -317,47 +245,4 @@ export class Nip46Client extends EventEmitter {
317
245
  isConnected(): boolean {
318
246
  return this.isSubscribed;
319
247
  }
320
-
321
- /**
322
- * iframeポートを設定
323
- */
324
- setIframePort(port: MessagePort): void {
325
- if (!this.iframeConfig) {
326
- throw new Error('Iframe config not set');
327
- }
328
- this.iframeConfig.port = port;
329
- this.setupIframePort(port);
330
- }
331
-
332
- /**
333
- * iframe用のメッセージハンドラを設定
334
- */
335
- private setupIframePort(port: MessagePort): void {
336
- port.onmessage = async (ev: MessageEvent) => {
337
- if (ev.data === 'ping') return;
338
-
339
- try {
340
- const event = ev.data;
341
- // validate and handle event
342
- if (!event || typeof event !== 'object') {
343
- console.warn('[Nip46Client] Invalid message from iframe');
344
- return;
345
- }
346
- await this.handleResponseEvent(event);
347
- } catch (e) {
348
- console.error('[Nip46Client] Iframe message error:', e);
349
- }
350
- };
351
-
352
- // Keep alive
353
- this.iframeKeepaliveInterval = setInterval(() => {
354
- try {
355
- port.postMessage('ping');
356
- } catch (e) {
357
- console.warn('[Nip46Client] Failed to send keepalive ping:', e);
358
- }
359
- }, 5000);
360
-
361
- console.log('[Nip46Client] Iframe port setup complete');
362
- }
363
248
  }