@konemono/nostr-login 1.10.16 → 1.11.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.10.16",
3
+ "version": "1.11.1",
4
4
  "description": "",
5
5
  "main": "./dist/index.esm.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1,15 +1,16 @@
1
1
  import { localStorageAddAccount, bunkerUrlToInfo, isBunkerUrl, fetchProfile, getBunkerUrl, localStorageRemoveCurrentAccount, createProfile, getIcon } from '../utils';
2
2
  import { ConnectionString, Info } from 'nostr-login-components/dist/types/types';
3
- import { generatePrivateKey, getEventHash, getPublicKey, nip19 } from 'nostr-tools';
3
+ import { generateSecretKey, 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 } from './Nip46';
10
+ import { IframeNostrRpc, Nip46Signer, ReadyListener, ensureNIP46Connection } from './Nip46';
11
11
  import { PrivateKeySigner } from './Signer';
12
12
  import { DEFAULT_NIP46_RELAYS } from '../const';
13
+ import { bytesToHex, hexToBytes } from 'nostr-tools/lib/types/utils';
13
14
 
14
15
  const OUTBOX_RELAYS = ['wss://user.kindpag.es', 'wss://purplepag.es', 'wss://relay.nos.social'];
15
16
  const NOSTRCONNECT_APPS: ConnectionString[] = [
@@ -63,6 +64,8 @@ class AuthNostrService extends EventEmitter implements Signer {
63
64
  this.params = params;
64
65
  this.ndk = new NDK({
65
66
  enableOutboxModel: false,
67
+ autoConnectUserRelays: false,
68
+ autoFetchUserMutelist: false,
66
69
  });
67
70
 
68
71
  this.profileNdk = new NDK({
@@ -89,13 +92,13 @@ class AuthNostrService extends EventEmitter implements Signer {
89
92
  if (this.signerPromise) {
90
93
  try {
91
94
  await this.signerPromise;
92
- } catch {}
95
+ } catch { }
93
96
  }
94
97
 
95
98
  if (this.readyPromise) {
96
99
  try {
97
100
  await this.readyPromise;
98
- } catch {}
101
+ } catch { }
99
102
  }
100
103
  }
101
104
 
@@ -169,10 +172,10 @@ class AuthNostrService extends EventEmitter implements Signer {
169
172
  }
170
173
 
171
174
  public async createNostrConnect() {
172
- this.nostrConnectKey = generatePrivateKey();
175
+ this.nostrConnectKey = bytesToHex(generateSecretKey());
173
176
  this.nostrConnectSecret = Math.random().toString(36).substring(7);
174
177
 
175
- const pubkey = getPublicKey(this.nostrConnectKey);
178
+ const pubkey = getPublicKey(hexToBytes(this.nostrConnectKey));
176
179
  const meta = {
177
180
  name: encodeURIComponent(document.location.host),
178
181
  url: encodeURIComponent(document.location.origin),
@@ -231,8 +234,8 @@ class AuthNostrService extends EventEmitter implements Signer {
231
234
 
232
235
  public async localSignup(name: string, sk?: string) {
233
236
  const signup = !sk;
234
- sk = sk || generatePrivateKey();
235
- const pubkey = getPublicKey(sk);
237
+ sk = sk || bytesToHex(generateSecretKey());
238
+ const pubkey = getPublicKey(hexToBytes(sk));
236
239
  const info: Info = {
237
240
  pubkey,
238
241
  sk,
@@ -258,7 +261,7 @@ class AuthNostrService extends EventEmitter implements Signer {
258
261
 
259
262
  // for local we export our existing key
260
263
  if (!this.localSigner || this.params.userInfo?.authMethod !== 'local') throw new Error('Most be local keys');
261
- return url + '#import=' + nip19.nsecEncode(this.localSigner.privateKey!);
264
+ return url + '#import=' + nip19.nsecEncode(hexToBytes(this.localSigner.privateKey!));
262
265
  }
263
266
 
264
267
  public async importAndConnect(cs: ConnectionString) {
@@ -361,7 +364,7 @@ class AuthNostrService extends EventEmitter implements Signer {
361
364
  public exportKeys() {
362
365
  if (!this.params.userInfo) return '';
363
366
  if (this.params.userInfo.authMethod !== 'local') return '';
364
- return nip19.nsecEncode(this.params.userInfo.sk!);
367
+ return nip19.nsecEncode(hexToBytes(this.params.userInfo.sk!));
365
368
  }
366
369
 
367
370
  private onAuth(type: 'login' | 'signup' | 'logout', info: Info | null = null) {
@@ -411,7 +414,7 @@ class AuthNostrService extends EventEmitter implements Signer {
411
414
  options.name = info!.name;
412
415
 
413
416
  if (info!.sk) {
414
- options.localNsec = nip19.nsecEncode(info!.sk);
417
+ options.localNsec = nip19.nsecEncode(hexToBytes(info!.sk));
415
418
  }
416
419
 
417
420
  if (info!.relays) {
@@ -552,7 +555,7 @@ class AuthNostrService extends EventEmitter implements Signer {
552
555
  if (this.signerPromise) {
553
556
  try {
554
557
  await this.signerPromise;
555
- } catch {}
558
+ } catch { }
556
559
  }
557
560
 
558
561
  // we remove support for iframe from nip05 and bunker-url methods,
@@ -604,7 +607,8 @@ class AuthNostrService extends EventEmitter implements Signer {
604
607
 
605
608
  // wait until we connect, otherwise
606
609
  // signer won't start properly
607
- await this.ndk.connect();
610
+ // NOTE: Deferred connection. ensureNIP46Connection will be called on demand.
611
+ // await this.ndk.connect();
608
612
 
609
613
  // create and prepare the signer
610
614
  const localSigner = new PrivateKeySigner(info.sk!);
@@ -699,12 +703,15 @@ class AuthNostrService extends EventEmitter implements Signer {
699
703
 
700
704
  public async signEvent(event: any) {
701
705
  if (this.localSigner) {
702
- event.pubkey = getPublicKey(this.localSigner.privateKey!);
706
+ event.pubkey = getPublicKey(hexToBytes(this.localSigner.privateKey!));
703
707
  event.id = getEventHash(event);
704
708
  event.sig = await this.localSigner.sign(event);
705
709
  } else {
706
710
  await this.ensureSigner();
707
711
 
712
+ // 署名前に接続を確認 (rpc.sendRequest内でも行われるが、安全のため)
713
+ await ensureNIP46Connection(this.ndk, 5000);
714
+
708
715
  event.pubkey = this.signer!.remotePubkey;
709
716
  event.id = getEventHash(event);
710
717
  event.sig = await this.signer!.sign(event);
@@ -718,41 +725,15 @@ class AuthNostrService extends EventEmitter implements Signer {
718
725
  if (!this.signer && this.params.userInfo) {
719
726
  console.log('Signer was destroyed, reinitializing...');
720
727
  await this.initSigner(this.params.userInfo);
721
- return; // initSignerで接続も行われるので終了
728
+ return;
722
729
  }
723
730
 
724
731
  if (!this.signer) {
725
732
  throw new Error('No signer available');
726
733
  }
727
734
 
728
- // リレー接続を確認・再接続
729
- const stats = this.ndk.pool.stats();
730
- console.log('NDK pool stats:', stats);
731
-
732
- if (stats.connected === 0) {
733
- console.log('NDK relays disconnected, reinitializing signer...');
734
-
735
- // リレーが完全に切断されている場合、signerも再初期化する必要がある
736
- // (RPCサブスクリプションも切断されているため)
737
- if (this.params.userInfo) {
738
- // 古いsignerを破棄
739
- this.signer = null;
740
-
741
- // 既存のリレーを一度切断
742
- for (const relay of this.ndk.pool.relays.values()) {
743
- try {
744
- relay.disconnect();
745
- } catch (e) {
746
- console.log('Error disconnecting relay:', e);
747
- }
748
- }
749
-
750
- // signerを再初期化(リレー接続も含む)
751
- await this.initSigner(this.params.userInfo);
752
- } else {
753
- throw new Error('Cannot reconnect: no user info');
754
- }
755
- }
735
+ // 接続確立は署名時またはRPCリクエスト時に行われるため、
736
+ // ここでの冗長な接続チェックは削除
756
737
  }
757
738
 
758
739
  private async codec_call(method: string, pubkey: string, param: string) {
@@ -5,6 +5,7 @@ import { EventEmitter } from 'tseep';
5
5
  import { ConnectionString, Info, RecentType } from 'nostr-login-components/dist/types/types';
6
6
  import { nip19 } from 'nostr-tools';
7
7
  import { setDarkMode } from '..';
8
+ import { bytesToHex } from 'nostr-tools/lib/types/utils';
8
9
 
9
10
  class ModalManager extends EventEmitter {
10
11
  private modal: TypeModal | null = null;
@@ -28,7 +29,7 @@ class ModalManager extends EventEmitter {
28
29
  if (this.launcherPromise) {
29
30
  try {
30
31
  await this.launcherPromise;
31
- } catch {}
32
+ } catch { }
32
33
  this.launcherPromise = undefined;
33
34
  }
34
35
  }
@@ -281,7 +282,7 @@ class ModalManager extends EventEmitter {
281
282
  throw new Error('Bad nsec value');
282
283
  }
283
284
  if (decoded.type !== 'nsec') throw new Error('Bad bech32 type');
284
- await this.authNostrService.localSignup('', decoded.data);
285
+ await this.authNostrService.localSignup('', bytesToHex(decoded.data));
285
286
  ok();
286
287
  } else if (nsecOrBunker.startsWith('bunker:')) {
287
288
  await this.authNostrService.authNip46('login', { name: '', bunkerUrl: nsecOrBunker });
@@ -1,5 +1,5 @@
1
1
  import NDK, { NDKEvent, NDKFilter, NDKNip46Signer, NDKNostrRpc, NDKRpcRequest, NDKRpcResponse, NDKSubscription, NDKSubscriptionCacheUsage, NostrEvent } from '@nostr-dev-kit/ndk';
2
- import { validateEvent, verifySignature } from 'nostr-tools';
2
+ import { validateEvent } from 'nostr-tools';
3
3
  import { PrivateKeySigner } from './Signer';
4
4
  import { NIP46_REQUEST_TIMEOUT, NIP46_CONNECT_TIMEOUT } from '../const';
5
5
 
@@ -8,6 +8,82 @@ function withTimeout<T>(promise: Promise<T>, timeoutMs: number, errorMessage: st
8
8
  return Promise.race([promise, new Promise<T>((_, reject) => setTimeout(() => reject(new Error(errorMessage)), timeoutMs))]);
9
9
  }
10
10
 
11
+ /**
12
+ * NDKのリレー接続を待機する
13
+ */
14
+ export function waitForConnection(ndk: NDK, timeout: number): Promise<void> {
15
+ return new Promise((resolve, reject) => {
16
+ const start = Date.now();
17
+
18
+ const check = () => {
19
+ // 接続済みリレーがあるか確認
20
+ const connected = Array.from(ndk.pool.relays.values()).some(relay => relay.status === 1); // 1 = CONNECTED
21
+
22
+ if (connected) {
23
+ resolve();
24
+ } else if (Date.now() - start > timeout) {
25
+ reject(new Error(`接続タイムアウト: ${timeout}ms`));
26
+ } else {
27
+ setTimeout(check, 100);
28
+ }
29
+ };
30
+
31
+ check();
32
+ });
33
+ }
34
+
35
+ /**
36
+ * NDKの接続を強制的に確立する
37
+ */
38
+ export async function ensureNDKConnection(ndk: NDK, timeout: number): Promise<void> {
39
+ // connect()は冪等なので安全
40
+ await ndk.connect();
41
+ await waitForConnection(ndk, timeout);
42
+ }
43
+
44
+ /**
45
+ * NIP-46専用リレーも含めて接続を確立する
46
+ */
47
+ export async function ensureNIP46Connection(ndk: NDK, timeout: number): Promise<void> {
48
+ try {
49
+ await ensureNDKConnection(ndk, timeout);
50
+ } catch (e) {
51
+ console.warn('Initial connection attempt failed, retrying with force disconnect...', e);
52
+ // 接続数0なら明示的に全切断して再試行
53
+ if (Array.from(ndk.pool.relays.values()).filter(r => r.status === 1).length === 0) {
54
+ ndk.pool.relays.forEach(relay => {
55
+ try {
56
+ relay.disconnect();
57
+ } catch (err) {
58
+ console.error('Error disconnecting relay:', err);
59
+ }
60
+ });
61
+ // タイムアウトを延長して再試行
62
+ await ensureNDKConnection(ndk, timeout * 2);
63
+ } else {
64
+ throw e;
65
+ }
66
+ }
67
+
68
+ // NIP-46用リレーが個別に定義されている場合の接続確認
69
+ // @ts-ignore
70
+ const signerRelays = ndk.signer?.relayUrls;
71
+ if (signerRelays && Array.isArray(signerRelays)) {
72
+ for (const url of signerRelays) {
73
+ const relay = ndk.pool.getRelay(url);
74
+ if (relay && relay.status !== 1) {
75
+ await relay.connect();
76
+ }
77
+ }
78
+ }
79
+
80
+ // 最終チェック
81
+ const connectedRelays = Array.from(ndk.pool.relays.values()).filter(r => r.status === 1);
82
+ if (connectedRelays.length === 0) {
83
+ throw new Error('リレー接続に失敗しました');
84
+ }
85
+ }
86
+
11
87
  class NostrRpc extends NDKNostrRpc {
12
88
  protected _ndk: NDK;
13
89
  protected _signer: PrivateKeySigner;
@@ -235,6 +311,9 @@ class NostrRpc extends NDKNostrRpc {
235
311
  }
236
312
 
237
313
  public async sendRequest(remotePubkey: string, method: string, params: string[] = [], kind = 24133, cb?: (res: NDKRpcResponse) => void): Promise<NDKRpcResponse> {
314
+ // リクエスト送信前に接続を確認
315
+ await ensureNIP46Connection(this._ndk, 5000);
316
+
238
317
  const id = this.getId();
239
318
 
240
319
  // response handler will deduplicate auth urls and responses
@@ -376,7 +455,6 @@ export class IframeNostrRpc extends NostrRpc {
376
455
  const event = ev.data;
377
456
 
378
457
  if (!validateEvent(event)) throw new Error('Invalid event from iframe');
379
- if (!verifySignature(event)) throw new Error('Invalid event signature from iframe');
380
458
  const nevent = new NDKEvent(this._ndk, event);
381
459
  const parsedEvent = await this.parseEvent(nevent);
382
460
  // レスポンス受信時にタイムスタンプを更新
@@ -393,6 +471,11 @@ export class IframeNostrRpc extends NostrRpc {
393
471
  }
394
472
 
395
473
  public async sendRequest(remotePubkey: string, method: string, params: string[] = [], kind = 24133, cb?: (res: NDKRpcResponse) => void): Promise<NDKRpcResponse> {
474
+ // リクエスト送信前に接続を確認 (relayを使用する場合に備えて)
475
+ if (!this.iframePort) {
476
+ await ensureNIP46Connection(this._ndk, 5000);
477
+ }
478
+
396
479
  const id = this.getId();
397
480
 
398
481
  // create and sign request event
@@ -1,6 +1,7 @@
1
1
  import { NDKPrivateKeySigner, NDKUser } from '@nostr-dev-kit/ndk';
2
2
  import { Nip44 } from '../utils/nip44';
3
3
  import { getPublicKey } from 'nostr-tools';
4
+ import { hexToBytes } from 'nostr-tools/lib/types/utils';
4
5
 
5
6
  export class PrivateKeySigner extends NDKPrivateKeySigner {
6
7
  private nip44: Nip44 = new Nip44();
@@ -8,7 +9,7 @@ export class PrivateKeySigner extends NDKPrivateKeySigner {
8
9
 
9
10
  constructor(privateKey: string) {
10
11
  super(privateKey);
11
- this._pubkey = getPublicKey(privateKey);
12
+ this._pubkey = getPublicKey(hexToBytes(privateKey));
12
13
  }
13
14
 
14
15
  get pubkey() {
@@ -1,7 +1,8 @@
1
1
  import { Info, RecentType } from 'nostr-login-components/dist/types/types';
2
2
  import NDK, { NDKEvent, NDKRelaySet, NDKSigner, NDKUser } from '@nostr-dev-kit/ndk';
3
- import { generatePrivateKey } from 'nostr-tools';
3
+ import { generateSecretKey } from 'nostr-tools';
4
4
  import { NostrLoginOptions } from '../types';
5
+ import { bytesToHex } from 'nostr-tools/lib/types/utils';
5
6
 
6
7
  const LOCAL_STORE_KEY = '__nostrlogin_nip46';
7
8
  const LOGGED_IN_ACCOUNTS = '__nostrlogin_accounts';
@@ -19,7 +20,7 @@ export const localStorageGetItem = (key: string) => {
19
20
  if (value) {
20
21
  try {
21
22
  return JSON.parse(value);
22
- } catch {}
23
+ } catch { }
23
24
  }
24
25
 
25
26
  return null;
@@ -92,7 +93,7 @@ export const bunkerUrlToInfo = (bunkerUrl: string, sk = ''): Info => {
92
93
  return {
93
94
  pubkey: '',
94
95
  signerPubkey: url.hostname || url.pathname.split('//')[1],
95
- sk: sk || generatePrivateKey(),
96
+ sk: sk || bytesToHex(generateSecretKey()),
96
97
  relays: url.searchParams.getAll('relay'),
97
98
  token: url.searchParams.get('secret') || '',
98
99
  authMethod: 'connect',
@@ -165,7 +166,7 @@ export const checkNip05 = async (nip05: string) => {
165
166
  pubkey = d.names[name];
166
167
  return;
167
168
  }
168
- } catch {}
169
+ } catch { }
169
170
 
170
171
  available = true;
171
172
  })();
@@ -10,6 +10,7 @@ import { sha256 } from "@noble/hashes/sha256"
10
10
  import { hmac } from "@noble/hashes/hmac";
11
11
  import { base64 } from "@scure/base";
12
12
  import { getPublicKey } from 'nostr-tools'
13
+ import { hexToBytes } from "nostr-tools/lib/types/utils";
13
14
 
14
15
  // from https://github.com/nbd-wtf/nostr-tools
15
16
 
@@ -164,7 +165,7 @@ export class Nip44 {
164
165
  }
165
166
 
166
167
  private getKey(privkey: string, pubkey: string, extractable?: boolean) {
167
- const id = getPublicKey(privkey) + pubkey
168
+ const id = getPublicKey(hexToBytes(privkey)) + pubkey
168
169
  let cryptoKey = this.cache.get(id)
169
170
  if (cryptoKey) return cryptoKey
170
171