@konemono/nostr-login 1.15.3 → 1.15.5

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.15.3",
3
+ "version": "1.15.5",
4
4
  "description": "Extended fork of nostr-login with multi-relay support, QR scanner, and improved stability",
5
5
  "main": "./dist/index.esm.js",
6
6
  "types": "./dist/index.d.ts",
@@ -27,7 +27,7 @@
27
27
  "tseep": "^1.2.1"
28
28
  },
29
29
  "devDependencies": {
30
- "@konemono/nostr-login-components": "^1.4.3",
30
+ "@konemono/nostr-login-components": "^1.4.5",
31
31
  "@rollup/plugin-commonjs": "^25.0.7",
32
32
  "@rollup/plugin-node-resolve": "^15.2.3",
33
33
  "@rollup/plugin-terser": "^0.4.4",
@@ -96,6 +96,7 @@ class AuthNostrService extends EventEmitter implements Signer {
96
96
  public cancelNostrConnect() {
97
97
  console.log('cancelNostrConnect called');
98
98
  this.releaseSigner();
99
+ this.resetNostrConnectKeys();
99
100
 
100
101
  // readyCallbackのみ解放
101
102
  this.resetAuth();
@@ -144,7 +145,21 @@ class AuthNostrService extends EventEmitter implements Signer {
144
145
  ) {
145
146
  console.log('[nostrConnect] Called', { relay, domain, link, iframeUrl, importConnect, customRelays });
146
147
 
147
- const relays = customRelays && customRelays.length > 0 ? customRelays : relay ? [relay] : DEFAULT_NIP46_RELAYS;
148
+ // linkのnostrconnect URLからリレーヒントを抽出
149
+ let linkRelays: string[] = [];
150
+ if (link) {
151
+ try {
152
+ const ncMatch = link.match(/nostrconnect:\/\/[^?]*\?(.*)/);
153
+ if (ncMatch) {
154
+ const params = new URLSearchParams(ncMatch[1]);
155
+ linkRelays = params.getAll('relay');
156
+ }
157
+ } catch (e) {
158
+ console.warn('[nostrConnect] Failed to parse relay hints from link', e);
159
+ }
160
+ }
161
+
162
+ const relays = customRelays && customRelays.length > 0 ? customRelays : linkRelays.length > 0 ? linkRelays : relay ? [relay] : DEFAULT_NIP46_RELAYS;
148
163
 
149
164
  const info: Info = {
150
165
  authMethod: 'connect',
@@ -194,14 +209,35 @@ class AuthNostrService extends EventEmitter implements Signer {
194
209
  this.onAuth('login', info);
195
210
  }
196
211
 
212
+ // 接続成功後にkey/secretをリセット(次回のモーダル表示で新規生成される)
213
+ this.resetNostrConnectKeys();
214
+
197
215
  console.log('[nostrConnect] Completed successfully');
198
216
  return info;
199
217
  }
200
218
 
201
- public async createNostrConnect() {
202
- this.nostrConnectKey = generatePrivateKey();
203
- this.nostrConnectSecret = Array.from(crypto.getRandomValues(new Uint8Array(16)), b => b.toString(16).padStart(2, '0')).join('');
219
+ public async createNostrConnect(relays: string[]) {
220
+ this.ensureNostrConnectKeys();
221
+ return this.buildNostrConnectUrl(relays);
222
+ }
223
+
224
+ /**
225
+ * key/secret が未生成なら新規生成する。既存なら再利用。
226
+ * モーダル表示中にリレーが変更されても key/secret は維持される。
227
+ */
228
+ private ensureNostrConnectKeys() {
229
+ if (!this.nostrConnectKey) {
230
+ this.nostrConnectKey = generatePrivateKey();
231
+ }
232
+ if (!this.nostrConnectSecret) {
233
+ this.nostrConnectSecret = Array.from(crypto.getRandomValues(new Uint8Array(16)), b => b.toString(16).padStart(2, '0')).join('');
234
+ }
235
+ }
204
236
 
237
+ /**
238
+ * 現在の key/secret を使って nostrconnect:// URL を構築する。
239
+ */
240
+ private async buildNostrConnectUrl(relays: string[]): Promise<string> {
205
241
  const pubkey = getPublicKey(hexToBytes(this.nostrConnectKey));
206
242
  const meta = {
207
243
  name: encodeURIComponent(document.location.host),
@@ -210,27 +246,34 @@ class AuthNostrService extends EventEmitter implements Signer {
210
246
  perms: encodeURIComponent(this.params.optionsModal.perms || ''),
211
247
  };
212
248
 
213
- return `nostrconnect://${pubkey}?image=${meta.icon}&url=${meta.url}&name=${meta.name}&perms=${meta.perms}&secret=${this.nostrConnectSecret}`;
249
+ const relayParams = relays.map(r => `&relay=${encodeURIComponent(r)}`).join('');
250
+ return `nostrconnect://${pubkey}?image=${meta.icon}&url=${meta.url}&name=${meta.name}&perms=${meta.perms}&secret=${this.nostrConnectSecret}${relayParams}`;
251
+ }
252
+
253
+ /**
254
+ * nostrconnect セッションをリセットし、key/secret を再生成可能にする。
255
+ */
256
+ public resetNostrConnectKeys() {
257
+ this.nostrConnectKey = '';
258
+ this.nostrConnectSecret = '';
214
259
  }
215
260
 
216
261
  public async getNostrConnectServices(customRelays?: string[], onUpdate?: (apps: ConnectionString[]) => void): Promise<[string, ConnectionString[]]> {
217
- const nostrconnect = await this.createNostrConnect();
262
+ const defaultRelays = customRelays && customRelays.length > 0 ? customRelays : DEFAULT_NIP46_RELAYS;
263
+ // ベースURLにリレーヒントを含める
264
+ const nostrconnect = await this.createNostrConnect(defaultRelays);
218
265
 
219
266
  const apps: ConnectionString[] = NOSTRCONNECT_APPS.map(a => ({ ...a }));
220
- const defaultRelays = customRelays && customRelays.length > 0 ? customRelays : DEFAULT_NIP46_RELAYS;
221
267
 
222
268
  // Build initial list: https services are 'loading', others are immediately available
223
269
  for (const a of apps) {
224
270
  if (a.link.startsWith('https://')) {
225
271
  a.available = 'loading';
226
- // Set link with default relays for now
227
- const relayParams = defaultRelays.map(r => `&relay=${encodeURIComponent(r)}`).join('');
228
- a.link = nostrconnect + relayParams;
272
+ // nostrconnect URL にはすでにリレーヒントが含まれている
273
+ a.link = nostrconnect;
229
274
  } else {
230
275
  a.available = true;
231
- const relayParams = defaultRelays.map(r => `&relay=${encodeURIComponent(r)}`).join('');
232
- const nc = nostrconnect + relayParams;
233
- a.link = a.link.replace('<nostrconnect>', nc);
276
+ a.link = a.link.replace('<nostrconnect>', nostrconnect);
234
277
  }
235
278
  }
236
279
 
@@ -245,15 +288,15 @@ class AuthNostrService extends EventEmitter implements Signer {
245
288
  try {
246
289
  const info = await fetchNostrJson(domain);
247
290
  const pubkey = info.names['_'];
248
- let relays = defaultRelays;
249
291
  const fetchedRelays = info.nip46[pubkey] as string[];
292
+ a.iframeUrl = info.nip46.iframe_url || '';
293
+ // サービス固有のリレーがあればURLを再構築
250
294
  if (fetchedRelays && fetchedRelays.length && (!customRelays || customRelays.length === 0)) {
251
- relays = fetchedRelays;
295
+ const serviceNostrconnect = this.replaceRelayHints(nostrconnect, fetchedRelays);
296
+ a.link = a.iframeUrl ? serviceNostrconnect : a.link;
297
+ } else {
298
+ a.link = a.iframeUrl ? nostrconnect : a.link;
252
299
  }
253
- a.iframeUrl = info.nip46.iframe_url || '';
254
- const relayParams = relays.map(r => `&relay=${encodeURIComponent(r)}`).join('');
255
- const nc = nostrconnect + relayParams;
256
- a.link = a.iframeUrl ? nc : a.link;
257
300
  a.available = true;
258
301
  } catch (e) {
259
302
  console.log('Service unavailable', domain, e);
@@ -267,6 +310,16 @@ class AuthNostrService extends EventEmitter implements Signer {
267
310
  return [nostrconnect, apps];
268
311
  }
269
312
 
313
+ private replaceRelayHints(nostrconnectUrl: string, newRelays: string[]): string {
314
+ // 既存のrelay=パラメータを除去して新しいリレーに置換
315
+ const url = new URL(nostrconnectUrl);
316
+ url.searchParams.delete('relay');
317
+ for (const r of newRelays) {
318
+ url.searchParams.append('relay', r);
319
+ }
320
+ return url.toString();
321
+ }
322
+
270
323
  public async localSignup(name: string, sk?: string) {
271
324
  const signup = !sk;
272
325
  sk = sk || generatePrivateKey();