@konemono/nostr-login 1.11.9 → 1.11.11

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.11.9",
3
+ "version": "1.11.11",
4
4
  "description": "",
5
5
  "main": "./dist/index.esm.js",
6
6
  "types": "./dist/index.d.ts",
@@ -32,6 +32,7 @@ const NOSTRCONNECT_APPS: ConnectionString[] = [
32
32
  },
33
33
  ];
34
34
 
35
+
35
36
  class AuthNostrService extends EventEmitter implements Signer {
36
37
  private ndk: NDK;
37
38
  private profileNdk: NDK;
@@ -48,6 +49,12 @@ class AuthNostrService extends EventEmitter implements Signer {
48
49
  private nostrConnectSecret: string = '';
49
50
  private iframe?: HTMLIFrameElement;
50
51
  private starterReady?: ReadyListener;
52
+ // ★ 追加: 再接続関連のプロパティ
53
+ private reconnectAttempts: number = 0;
54
+ private readonly MAX_RECONNECT_ATTEMPTS = 3;
55
+ private currentInfo?: Info;
56
+ private isReconnecting: boolean = false;
57
+
51
58
 
52
59
  nip04: {
53
60
  encrypt: (pubkey: string, plaintext: string) => Promise<string>;
@@ -594,116 +601,165 @@ class AuthNostrService extends EventEmitter implements Signer {
594
601
  }
595
602
 
596
603
  private async initSignerInternal(info: Info, listen: boolean, connect: boolean, eventToAddAccount: boolean, resolve: () => void) {
597
- // pre-connect if we're creating the connection (listen|connect) or
598
- // not iframe mode
604
+ // リレー接続
599
605
  if (info.relays && !info.iframeUrl) {
600
606
  for (const r of info.relays) {
601
607
  this.ndk.addExplicitRelay(r, undefined);
602
608
  }
603
609
  }
604
610
 
605
- // wait until we connect, otherwise
606
- // signer won't start properly
607
611
  await this.ndk.connect();
608
612
 
609
- // create and prepare the signer
610
613
  const localSigner = new PrivateKeySigner(info.sk!);
611
- this.signer = new Nip46Signer(this.ndk, localSigner, info.signerPubkey!, info.iframeUrl ? new URL(info.iframeUrl!).origin : undefined);
612
-
613
- // ★ once を使う - 1回だけ実行される ★
614
- this.signer.once('connectionLost', async () => {
615
- console.log('Connection lost, attempting to reconnect...');
616
- this.signer = null;
614
+ this.signer = new Nip46Signer(
615
+ this.ndk,
616
+ localSigner,
617
+ info.signerPubkey!,
618
+ info.iframeUrl ? new URL(info.iframeUrl!).origin : undefined
619
+ );
620
+
621
+ // ★ 修正: connectionLost イベントハンドリングを統一
622
+ this.signer.removeAllListeners?.('connectionLost');
623
+ this.signer.once('connectionLost', () => {
624
+ console.log('Connection lost detected');
617
625
 
618
- if (this.params.userInfo) {
619
- await this.initSigner(this.params.userInfo);
620
- }
626
+ // 再接続処理を呼び出す(非同期で実行、エラーは握りつぶさない)
627
+ this.handleReconnection(info).catch(err => {
628
+ console.error('Reconnection handling failed:', err);
629
+ this.emit('reconnectFailed', err);
630
+ });
621
631
  });
622
632
 
623
- // we should notify the banner the same way as
624
- // the onAuthUrl does
625
- this.signer.on(`iframeRestart`, async () => {
626
- const iframeUrl = info.iframeUrl + (info.iframeUrl!.includes('?') ? '&' : '?') + 'pubkey=' + info.pubkey + '&rebind=' + localSigner.pubkey;
633
+ // iframe restart は既存のまま
634
+ this.signer.removeAllListeners?.('iframeRestart');
635
+ this.signer.on('iframeRestart', async () => {
636
+ const iframeUrl = info.iframeUrl +
637
+ (info.iframeUrl!.includes('?') ? '&' : '?') +
638
+ 'pubkey=' + info.pubkey + '&rebind=' + localSigner.pubkey;
627
639
  this.emit('iframeRestart', { pubkey: info.pubkey, iframeUrl });
628
640
  });
629
641
 
630
- // OAuth flow
631
- // if (!listen) {
642
+ // authUrl は既存のまま
643
+ this.signer.removeAllListeners?.('authUrl');
632
644
  this.signer.on('authUrl', (url: string) => {
633
645
  console.log('nostr login auth url', url);
634
-
635
- // notify our UI
636
646
  this.emit('onAuthUrl', { url, iframeUrl: info.iframeUrl, eventToAddAccount });
637
647
  });
638
- // }
639
648
 
649
+ // 認証フロー
640
650
  if (listen) {
641
- // nostrconnect: flow
642
- // wait for the incoming message from signer
643
651
  await this.listen(info);
644
652
  } else if (connect) {
645
- // bunker: flow
646
- // send 'connect' message to signer
647
653
  await this.connect(info, this.params.optionsModal.perms);
648
654
  } else {
649
- // provide saved pubkey as a hint
650
655
  await this.signer!.initUserPubkey(info.pubkey);
651
656
  }
652
657
 
653
- // ensure, we're using it in callbacks above
654
- // and expect info to be valid after this call
655
658
  info.pubkey = this.signer!.userPubkey;
656
- // learned after nostrconnect flow
657
659
  info.signerPubkey = this.signer!.remotePubkey;
658
660
 
661
+ // ★ 追加: 接続情報を保持
662
+ this.currentInfo = info;
663
+
659
664
  this.signerAbortController = undefined;
660
665
  resolve();
661
666
  }
662
667
 
663
- public async authNip46(
664
- type: 'login' | 'signup',
665
- {
666
- name,
667
- bunkerUrl,
668
- sk = '',
669
- domain = '',
670
- iframeUrl = '',
671
- customRelays,
672
- }: { name: string; bunkerUrl: string; sk?: string; domain?: string; iframeUrl?: string; customRelays?: string[] },
673
- ) {
668
+ // 新規追加: 再接続処理の一元化
669
+ private async handleReconnection(info: Info): Promise<void> {
670
+ if (this.isReconnecting) {
671
+ console.log('Already reconnecting, skipping...');
672
+ return;
673
+ }
674
+
675
+ if (this.reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) {
676
+ console.error('Max reconnection attempts reached');
677
+ this.emit('reconnectFailed');
678
+ this.reconnectAttempts = 0;
679
+ return;
680
+ }
681
+
682
+ this.isReconnecting = true;
683
+ this.reconnectAttempts++;
684
+
674
685
  try {
675
- const info = bunkerUrlToInfo(bunkerUrl, sk);
676
- if (isBunkerUrl(name)) info.bunkerUrl = name;
677
- else {
678
- info.nip05 = name;
679
- info.domain = name.split('@')[1];
686
+ console.log(`Reconnection attempt ${this.reconnectAttempts}/${this.MAX_RECONNECT_ATTEMPTS}`);
687
+
688
+ // 1. リレー再接続
689
+ const stats = this.ndk.pool.stats();
690
+ if (stats.connected === 0) {
691
+ console.log('Reconnecting to relays...');
692
+
693
+ // 既存のリレーを切断
694
+ for (const relay of this.ndk.pool.relays.values()) {
695
+ try {
696
+ relay.disconnect();
697
+ } catch (e) {
698
+ console.log('Error disconnecting relay:', e);
699
+ }
700
+ }
701
+
702
+ // リレー再追加
703
+ if (info.relays) {
704
+ for (const r of info.relays) {
705
+ this.ndk.addExplicitRelay(r, undefined);
706
+ }
707
+ }
708
+
709
+ // 接続待機
710
+ await this.ndk.connect();
680
711
  }
681
- if (domain) info.domain = domain;
682
- if (iframeUrl) info.iframeUrl = iframeUrl;
683
712
 
684
- // カスタムリレーが指定されていれば使用する
685
- if (customRelays && customRelays.length > 0) {
686
- info.relays = customRelays;
713
+ // 2. Signer ping確認
714
+ if (this.signer) {
715
+ await this.signer.reconnect(info);
687
716
  }
688
717
 
689
- // console.log('nostr login auth info', info);
690
- if (!info.signerPubkey || !info.sk || !info.relays?.[0]) {
691
- throw new Error(`Bad bunker url ${bunkerUrl}`);
718
+ // 成功
719
+ this.reconnectAttempts = 0;
720
+ this.isReconnecting = false;
721
+ console.log('Reconnection successful');
722
+ this.emit('reconnected');
723
+
724
+ } catch (error) {
725
+ console.error(`Reconnection attempt ${this.reconnectAttempts} failed:`, error);
726
+ this.isReconnecting = false;
727
+
728
+ // リトライ
729
+ if (this.reconnectAttempts < this.MAX_RECONNECT_ATTEMPTS) {
730
+ const delay = 2000 * this.reconnectAttempts; // 2秒, 4秒, 6秒
731
+ console.log(`Retrying in ${delay}ms...`);
732
+
733
+ setTimeout(() => {
734
+ this.handleReconnection(info).catch(err => {
735
+ console.error('Retry failed:', err);
736
+ });
737
+ }, delay);
738
+ } else {
739
+ this.emit('reconnectFailed');
740
+ this.reconnectAttempts = 0;
692
741
  }
742
+ }
743
+ }
693
744
 
694
- const eventToAddAccount = Boolean(this.params.userInfo);
695
- console.log('authNip46', type, info);
745
+ // 修正: ensureSigner の簡素化(リトライロジック削除)
746
+ private async ensureSigner() {
747
+ // signerがnullの場合のみ再初期化
748
+ if (!this.signer && this.currentInfo) {
749
+ console.log('Signer was destroyed, reinitializing...');
750
+ await this.initSigner(this.currentInfo);
751
+ return;
752
+ }
696
753
 
697
- // updates the info
698
- await this.initSigner(info, { connect: true, eventToAddAccount });
754
+ if (!this.signer) {
755
+ throw new Error('No signer available');
756
+ }
699
757
 
700
- // callback
701
- this.onAuth(type, info);
702
- } catch (e) {
703
- console.log('nostr login auth failed', e);
704
- // make ure it's closed
705
- // this.popupManager.closePopup();
706
- throw e;
758
+ // リレー接続確認(切断されていれば再接続試行)
759
+ const stats = this.ndk.pool.stats();
760
+ if (stats.connected === 0 && this.currentInfo) {
761
+ console.log('NDK relays disconnected, attempting reconnection...');
762
+ await this.handleReconnection(this.currentInfo);
707
763
  }
708
764
  }
709
765
 
@@ -723,47 +779,7 @@ class AuthNostrService extends EventEmitter implements Signer {
723
779
  return event;
724
780
  }
725
781
 
726
- private async ensureSigner() {
727
- // signerがキャンセル等で破棄されている場合は再初期化
728
- if (!this.signer && this.params.userInfo) {
729
- console.log('Signer was destroyed, reinitializing...');
730
- await this.initSigner(this.params.userInfo);
731
- return; // initSignerで接続も行われるので終了
732
- }
733
-
734
- if (!this.signer) {
735
- throw new Error('No signer available');
736
- }
737
-
738
- // リレー接続を確認・再接続
739
- const stats = this.ndk.pool.stats();
740
- console.log('NDK pool stats:', stats);
741
-
742
- if (stats.connected === 0) {
743
- console.log('NDK relays disconnected, reinitializing signer...');
744
-
745
- // リレーが完全に切断されている場合、signerも再初期化する必要がある
746
- // (RPCサブスクリプションも切断されているため)
747
- if (this.params.userInfo) {
748
- // 古いsignerを破棄
749
- this.signer = null;
750
-
751
- // 既存のリレーを一度切断
752
- for (const relay of this.ndk.pool.relays.values()) {
753
- try {
754
- relay.disconnect();
755
- } catch (e) {
756
- console.log('Error disconnecting relay:', e);
757
- }
758
- }
759
-
760
- // signerを再初期化(リレー接続も含む)
761
- await this.initSigner(this.params.userInfo);
762
- } else {
763
- throw new Error('Cannot reconnect: no user info');
764
- }
765
- }
766
- }
782
+
767
783
 
768
784
  private async codec_call(method: string, pubkey: string, param: string) {
769
785
  return new Promise<string>((resolve, reject) => {
@@ -15,7 +15,7 @@ class NostrRpc extends NDKNostrRpc {
15
15
  protected requests: Set<string> = new Set();
16
16
  private sub?: NDKSubscription;
17
17
  protected _useNip44: boolean = false;
18
- protected eventEmitter: EventEmitter = new EventEmitter();
18
+ public eventEmitter: EventEmitter = new EventEmitter();
19
19
 
20
20
  public constructor(ndk: NDK, signer: PrivateKeySigner) {
21
21
  super(ndk, signer, ndk.debug.extend('nip46:signer:rpc'));
@@ -386,7 +386,8 @@ export class Nip46Signer extends NDKNip46Signer {
386
386
  private _rpc: IframeNostrRpc;
387
387
  private lastPingTime: number = 0;
388
388
  private pingCacheDuration: number = 30000; // 30秒
389
-
389
+ // ★ 追加: 再接続中フラグ
390
+ private isReconnecting: boolean = false;
390
391
 
391
392
  constructor(ndk: NDK, localSigner: PrivateKeySigner, signerPubkey: string, iframeOrigin?: string) {
392
393
  super(ndk, signerPubkey, localSigner);
@@ -407,36 +408,54 @@ export class Nip46Signer extends NDKNip46Signer {
407
408
 
408
409
 
409
410
 
410
- // Nip46.tsのNip46Signerクラス内
411
- // 接続確認(必要時のみping リトライ付き 最大10秒)
412
- // リトライ回数: 2回(計3回試行)
413
- //合計最大時間: 2秒(ping) × 3回 + 2秒(待機) × 2回 = 10秒
414
- private async ensureConnection(retries: number = 2): Promise<void> {
411
+ // ★ 既存メソッドを修正: リトライロジックを削除
412
+ private async ensureConnection(): Promise<void> {
415
413
  if (!this.remotePubkey) return;
416
414
 
417
415
  const now = Date.now();
418
416
 
419
- // 最近ping成功していればスキップ
417
+ // キャッシュチェック
420
418
  if (now - this.lastPingTime < this.pingCacheDuration) {
421
419
  return;
422
420
  }
423
421
 
424
- for (let i = 0; i <= retries; i++) {
425
- try {
426
- await this._rpc.pingWithTimeout(this.remotePubkey, 2000); // 2秒タイムアウト
427
- this.lastPingTime = now;
428
- console.log('Connection check OK');
429
- return;
430
- } catch (error) {
431
- if (i === retries) {
432
- console.error('Connection check failed after retries', error);
433
- throw new Error('NIP-46 connection lost');
434
- }
422
+ try {
423
+ await this._rpc.pingWithTimeout(this.remotePubkey, 2000);
424
+ this.lastPingTime = now;
425
+ console.log('Connection check OK');
426
+ } catch (error) {
427
+ console.error('Connection check failed', error);
428
+ // 修正: リトライは行わず、接続喪失イベントのみ発火
429
+ this.emit('connectionLost');
430
+ throw new Error('NIP-46 connection lost');
431
+ }
432
+ }
433
+
434
+
435
+ // ★ 新規追加: 再接続メソッド(外部から呼ばれる)
436
+ public async reconnect(info: any): Promise<void> {
437
+ if (this.isReconnecting) {
438
+ console.log('Already reconnecting, skipping...');
439
+ return;
440
+ }
435
441
 
436
- const delay = 2000; // 2秒間隔で再送
437
- console.log(`Ping failed (${i + 1}/${retries + 1}), retrying in ${delay}ms...`);
438
- await new Promise(resolve => setTimeout(resolve, delay));
442
+ this.isReconnecting = true;
443
+
444
+ try {
445
+ console.log('Reconnecting signer...');
446
+
447
+ // リレー再接続は AuthNostrService 側で実施済みと仮定
448
+ // ここでは ping のみ実施
449
+ if (this.remotePubkey) {
450
+ await this._rpc.pingWithTimeout(this.remotePubkey, 2000);
451
+ this.lastPingTime = Date.now();
452
+ console.log('Reconnection successful');
439
453
  }
454
+
455
+ this.isReconnecting = false;
456
+ } catch (error) {
457
+ this.isReconnecting = false;
458
+ throw error;
440
459
  }
441
460
  }
442
461
 
@@ -542,6 +561,17 @@ export class Nip46Signer extends NDKNip46Signer {
542
561
  return r.result;
543
562
  }
544
563
 
564
+ // ★ 追加: removeAllListeners メソッド
565
+ public removeAllListeners = (event?: string | symbol): this => {
566
+ if (event) {
567
+ this._rpc.eventEmitter.removeAllListeners(event as string);
568
+ } else {
569
+ this._rpc.eventEmitter.removeAllListeners();
570
+ }
571
+ return this;
572
+ }
573
+
574
+
545
575
  // EventEmitter互換メソッド
546
576
  // ★ ここに once を追加 ★
547
577
  public override on = <EventKey extends string | symbol = string>(