@konemono/nostr-login 1.11.10 → 1.11.12
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/dist/index.esm.js +4 -4
- package/dist/index.esm.js.map +1 -1
- package/dist/modules/AuthNostrService.d.ts +6 -9
- package/dist/modules/Nip46.d.ts +4 -1
- package/dist/unpkg.js +2 -2
- package/package.json +1 -1
- package/src/modules/AuthNostrService.ts +123 -107
- package/src/modules/Nip46.ts +52 -22
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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
|
-
|
|
619
|
-
|
|
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
|
-
//
|
|
624
|
-
|
|
625
|
-
this.signer.on(
|
|
626
|
-
const iframeUrl = info.iframeUrl +
|
|
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
|
-
//
|
|
631
|
-
|
|
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
|
-
|
|
664
|
-
|
|
665
|
-
{
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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 (
|
|
686
|
-
info
|
|
713
|
+
// 2. Signer ping確認
|
|
714
|
+
if (this.signer) {
|
|
715
|
+
await this.signer.reconnect(info);
|
|
687
716
|
}
|
|
688
717
|
|
|
689
|
-
//
|
|
690
|
-
|
|
691
|
-
|
|
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
|
-
|
|
695
|
-
|
|
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
|
-
|
|
698
|
-
|
|
754
|
+
if (!this.signer) {
|
|
755
|
+
throw new Error('No signer available');
|
|
756
|
+
}
|
|
699
757
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
console.log('
|
|
704
|
-
|
|
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
|
-
|
|
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) => {
|
package/src/modules/Nip46.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
411
|
-
|
|
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
|
-
//
|
|
417
|
+
// キャッシュチェック
|
|
420
418
|
if (now - this.lastPingTime < this.pingCacheDuration) {
|
|
421
419
|
return;
|
|
422
420
|
}
|
|
423
421
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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>(
|