@konemono/nostr-login 1.7.61 → 1.7.62

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.7.61",
3
+ "version": "1.7.62",
4
4
  "description": "",
5
5
  "main": "./dist/index.esm.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1,242 +1,142 @@
1
+ import { AmberResponse } from '../types';
1
2
  import { Signer } from './Nostr';
2
3
 
3
- const PENDING_KEY = 'amber_pending_request';
4
-
5
4
  export class AmberDirectSigner implements Signer {
6
- private _pubkey: string = '';
7
- private static pendingResolves: Map<string, (result: any) => void> = new Map();
5
+ private _pubkey: string;
6
+ private static pendingResolves: Map<string, (result: string) => void> = new Map();
8
7
 
9
- constructor(pubkey?: string) {
10
- this._pubkey = pubkey || '';
8
+ constructor(pubkey: string = '') {
9
+ this._pubkey = pubkey;
11
10
  }
12
11
 
13
12
  get pubkey() {
14
13
  return this._pubkey;
15
14
  }
16
15
 
17
- public set pubkey(v: string) {
16
+ set pubkey(v: string) {
18
17
  this._pubkey = v;
19
18
  }
20
19
 
21
- nip04 = {
20
+ public nip04 = {
22
21
  encrypt: (pubkey: string, plaintext: string) => this.encrypt04(pubkey, plaintext),
23
22
  decrypt: (pubkey: string, ciphertext: string) => this.decrypt04(pubkey, ciphertext),
24
23
  };
25
24
 
26
- nip44 = {
25
+ public nip44 = {
27
26
  encrypt: (pubkey: string, plaintext: string) => this.encrypt44(pubkey, plaintext),
28
27
  decrypt: (pubkey: string, ciphertext: string) => this.decrypt44(pubkey, ciphertext),
29
28
  };
30
29
 
31
- private getHash(content: string): string {
32
- let hash = 0;
33
- for (let i = 0; i < content.length; i++) {
34
- const char = content.charCodeAt(i);
35
- hash = (hash << 5) - hash + char;
36
- hash |= 0;
37
- }
38
- return hash.toString(36);
39
- }
40
-
41
- private checkCache(content: string, type: string): string | null {
42
- const hash = this.getHash(content);
43
- const cacheKey = `amber_result_${type}_${hash}`;
44
- const cached = sessionStorage.getItem(cacheKey);
45
- if (cached) {
46
- sessionStorage.removeItem(cacheKey);
47
- return cached;
48
- }
49
- return null;
50
- }
51
-
52
- private setPending(id: string, content: string, type: string) {
53
- const hash = this.getHash(content);
54
- const data = JSON.stringify({ id, type, hash, timestamp: Date.now() });
55
- sessionStorage.setItem(`amber_pending_${id}`, data);
56
- localStorage.setItem(PENDING_KEY, data); // Backup for reloads
57
- }
58
-
59
- public static resolvePending(id: string, type: string, result: string) {
60
- console.log('AmberDirectSigner: resolvePending', { id, type, pendingCount: this.pendingResolves.size });
61
-
62
- let resolve = this.pendingResolves.get(id);
63
-
64
- // Fallback by type if only one pending of that type
65
- if (!resolve) {
66
- console.log('AmberDirectSigner: Falling back to resolve by type', type);
67
- for (const [pendingId, pendingResolve] of this.pendingResolves.entries()) {
68
- const pending = sessionStorage.getItem(`amber_pending_${pendingId}`) || localStorage.getItem(PENDING_KEY);
69
- if (pending) {
70
- try {
71
- const { type: pendingType } = JSON.parse(pending);
72
- if (pendingType === type || type.startsWith(pendingType)) {
73
- resolve = pendingResolve;
74
- id = pendingId;
75
- break;
76
- }
77
- } catch (e) {}
78
- }
79
- }
80
- }
81
-
82
- if (resolve) {
83
- console.log('AmberDirectSigner: Promise resolved', id);
84
- resolve(result);
85
- this.pendingResolves.delete(id);
86
- return true;
87
- }
88
- return false;
89
- }
90
-
91
- async signEvent(event: any): Promise<any> {
92
- const content = JSON.stringify(event);
93
- const cached = this.checkCache(content, 'sign_event');
94
- if (cached) return JSON.parse(cached);
95
-
30
+ public async getPublicKey(): Promise<string> {
96
31
  const id = Math.random().toString(36).substring(7);
97
- this.setPending(id, content, 'sign_event');
98
- const url = this.generateUrl(content, 'sign_event', id);
32
+ const url = this.generateUrl('', 'get_public_key', id);
99
33
  window.location.href = url;
100
34
  return new Promise((resolve) => {
101
- AmberDirectSigner.pendingResolves.set(id, (res) => resolve(JSON.parse(res)));
35
+ AmberDirectSigner.pendingResolves.set(id, resolve);
102
36
  });
103
37
  }
104
38
 
105
- async getPublicKey(): Promise<string> {
39
+ public async signEvent(event: any): Promise<any> {
106
40
  const id = Math.random().toString(36).substring(7);
107
- this.setPending(id, '', 'get_public_key');
108
- const url = this.generateUrl('', 'get_public_key', id);
41
+ const url = this.generateUrl(JSON.stringify(event), 'sign_event', id);
109
42
  window.location.href = url;
110
43
  return new Promise((resolve) => {
111
- AmberDirectSigner.pendingResolves.set(id, resolve);
44
+ AmberDirectSigner.pendingResolves.set(id, (result: string) => {
45
+ resolve(JSON.parse(result));
46
+ });
112
47
  });
113
48
  }
114
49
 
115
- async encrypt04(pubkey: string, plaintext: string): Promise<string> {
116
- const cached = this.checkCache(plaintext, 'nip04_encrypt');
117
- if (cached) return cached;
118
-
50
+ public async encrypt04(pubkey: string, plaintext: string): Promise<string> {
119
51
  const id = Math.random().toString(36).substring(7);
120
- this.setPending(id, plaintext, 'nip04_encrypt');
121
52
  const url = this.generateUrl(plaintext, 'nip04_encrypt', id, pubkey);
122
53
  window.location.href = url;
123
54
  return new Promise((resolve) => {
124
- AmberDirectSigner.pendingResolves.set(id, resolve);
55
+ AmberDirectSigner.pendingResolves.set(id, resolve);
125
56
  });
126
57
  }
127
58
 
128
- async decrypt04(pubkey: string, ciphertext: string): Promise<string> {
129
- const cached = this.checkCache(ciphertext, 'nip04_decrypt');
130
- if (cached) return cached;
131
-
59
+ public async decrypt04(pubkey: string, ciphertext: string): Promise<string> {
132
60
  const id = Math.random().toString(36).substring(7);
133
- this.setPending(id, ciphertext, 'nip04_decrypt');
134
61
  const url = this.generateUrl(ciphertext, 'nip04_decrypt', id, pubkey);
135
62
  window.location.href = url;
136
63
  return new Promise((resolve) => {
137
- AmberDirectSigner.pendingResolves.set(id, resolve);
64
+ AmberDirectSigner.pendingResolves.set(id, resolve);
138
65
  });
139
66
  }
140
67
 
141
- async encrypt44(pubkey: string, plaintext: string): Promise<string> {
142
- const cached = this.checkCache(plaintext, 'nip44_encrypt');
143
- if (cached) return cached;
144
-
68
+ public async encrypt44(pubkey: string, plaintext: string): Promise<string> {
145
69
  const id = Math.random().toString(36).substring(7);
146
- this.setPending(id, plaintext, 'nip44_encrypt');
147
70
  const url = this.generateUrl(plaintext, 'nip44_encrypt', id, pubkey);
148
71
  window.location.href = url;
149
72
  return new Promise((resolve) => {
150
- AmberDirectSigner.pendingResolves.set(id, resolve);
73
+ AmberDirectSigner.pendingResolves.set(id, resolve);
151
74
  });
152
75
  }
153
76
 
154
- async decrypt44(pubkey: string, ciphertext: string): Promise<string> {
155
- const cached = this.checkCache(ciphertext, 'nip44_decrypt');
156
- if (cached) return cached;
157
-
77
+ public async decrypt44(pubkey: string, ciphertext: string): Promise<string> {
158
78
  const id = Math.random().toString(36).substring(7);
159
- this.setPending(id, ciphertext, 'nip44_decrypt');
160
79
  const url = this.generateUrl(ciphertext, 'nip44_decrypt', id, pubkey);
161
80
  window.location.href = url;
162
81
  return new Promise((resolve) => {
163
- AmberDirectSigner.pendingResolves.set(id, resolve);
82
+ AmberDirectSigner.pendingResolves.set(id, resolve);
164
83
  });
165
84
  }
166
85
 
167
- private generateUrl(content: string, type: string, id: string, recipient?: string): string {
168
- const callbackUrl = new URL(window.location.origin + window.location.pathname);
169
-
170
- // Minimal parameters in callbackUrl to avoid conflicts
171
- callbackUrl.searchParams.set('amberType', type);
172
- callbackUrl.searchParams.set('amberId', id);
173
-
174
- const name = document.title || window.location.hostname || 'Nostr Login';
175
-
176
- const params: string[] = [];
177
- params.push(`type=${encodeURIComponent(type)}`);
178
- params.push(`id=${encodeURIComponent(id)}`);
179
- params.push(`callbackUrl=${encodeURIComponent(callbackUrl.toString())}`);
180
- params.push(`name=${encodeURIComponent(name)}`);
181
- params.push(`app_name=${encodeURIComponent(name)}`);
182
-
183
- if (recipient) {
184
- params.push(`pubkey=${encodeURIComponent(recipient)}`);
185
- } else if (this._pubkey) {
186
- params.push(`pubkey=${encodeURIComponent(this._pubkey)}`);
187
- }
188
86
 
189
- const dataPart = content ? encodeURIComponent(content) : 'get_public_key';
190
- // Use nostrsigner: scheme as primary which is standard NIP-55
191
- return `nostrsigner:${dataPart}?${params.join('&')}`;
192
- }
87
+ private generateUrl(content: string, type: string, id: string, pubkey?: string): string {
88
+ const callbackUrl = window.location.href.split('#')[0].split('?')[0];
89
+ const encodedContent = encodeURIComponent(content || '');
90
+ const encodedCallback = encodeURIComponent(callbackUrl);
91
+
92
+ // ★ 追加: sessionStorageに保存(これが無い)
93
+ sessionStorage.setItem('amber_last_type', type);
94
+ sessionStorage.setItem('amber_last_id', id);
95
+ sessionStorage.setItem('amber_last_timestamp', Date.now().toString());
96
+
97
+ // NIP-55準拠: nostrsigner: スキーム
98
+ let url = `nostrsigner:${encodedContent}`;
99
+ url += `?compressionType=none`;
100
+ url += `&returnType=signature`;
101
+ url += `&type=${type}`;
102
+ url += `&callbackUrl=${encodedCallback}`;
103
+ if (pubkey) url += `&pubkey=${pubkey}`;
104
+ if (this._pubkey) url += `&current_user=${this._pubkey}`; // ★ 追加
105
+
106
+ return url;
107
+ }
193
108
 
194
- public static parseResponse(): { type: string; id: string; result: string } | null {
195
- const url = new URL(window.location.href);
196
- const params = new URLSearchParams(url.search);
197
- const hashParams = new URLSearchParams(url.hash.substring(1));
198
-
199
- const getParam = (name: string) => params.get(name) || hashParams.get(name);
200
-
201
- let type = getParam('amberType') || getParam('type');
202
- let id = getParam('amberId') || getParam('id');
203
- const result = getParam('signature') || getParam('result') || getParam('pubKey') || getParam('pubkey');
204
-
205
- // Fallback to localStorage pending if params are missing/merged
206
- if (result && (!type || !id)) {
207
- const pending = localStorage.getItem(PENDING_KEY);
208
- if (pending) {
209
- try {
210
- const { id: pId, type: pType } = JSON.parse(pending);
211
- if (!type) type = pType;
212
- if (!id) id = pId;
213
- console.log('AmberDirectSigner: Recovered type/id from localStorage');
214
- } catch (e) {}
215
- }
216
- }
109
+ static parseResponse(): AmberResponse | null {
110
+ // NIP-55準拠: クエリパラメータから取得
111
+ const url = new URL(window.location.href);
112
+ const result = url.searchParams.get('event');
113
+
114
+ if (!result) return null;
115
+
116
+ // sessionStorageから id type を取得
117
+ const id = sessionStorage.getItem('amber_last_id');
118
+ const type = sessionStorage.getItem('amber_last_type');
119
+
120
+ // sessionStorage をクリーンアップ
121
+ sessionStorage.removeItem('amber_last_id');
122
+ sessionStorage.removeItem('amber_last_type');
123
+ sessionStorage.removeItem('amber_last_timestamp');
124
+
125
+ // id type が存在する場合のみ返す
126
+ if (id && type) {
127
+ return { id, type, result };
128
+ }
129
+
130
+ return null;
131
+ }
217
132
 
218
- if (type && result) {
219
- // Handle "ID stuck to type" or typo cases
220
- let finalType = type.toLowerCase();
221
- if (finalType.includes('get_pub')) finalType = 'get_public_key';
222
- else if (finalType.includes('sign_event')) finalType = 'sign_event';
223
- else if (finalType.includes('encrypt')) finalType = finalType.includes('44') ? 'nip44_encrypt' : 'nip04_encrypt';
224
- else if (finalType.includes('decrypt')) finalType = finalType.includes('44') ? 'nip44_decrypt' : 'nip04_decrypt';
225
-
226
- const finalId = id || '';
227
-
228
- // Clean up URL
229
- const newUrl = new URL(window.location.href);
230
- ['amberType', 'amberId', 'signature', 'result', 'pubKey', 'pubkey', 'type', 'id'].forEach(p => {
231
- newUrl.searchParams.delete(p);
232
- });
233
- newUrl.hash = '';
234
- window.history.replaceState({}, '', newUrl.toString());
235
-
236
- localStorage.removeItem(PENDING_KEY); // Done
237
-
238
- return { type: finalType, id: finalId, result };
133
+ static resolvePending(id: string, type: string, result: string): boolean {
134
+ const resolve = this.pendingResolves.get(id);
135
+ if (resolve) {
136
+ this.pendingResolves.delete(id);
137
+ resolve(result);
138
+ return true;
239
139
  }
240
- return null;
140
+ return false;
241
141
  }
242
142
  }
@@ -12,6 +12,7 @@ import { PrivateKeySigner } from './Signer';
12
12
  import { AmberDirectSigner } from './AmberDirectSigner';
13
13
 
14
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/'];
17
18
  const CONNECT_TIMEOUT = 5000;
@@ -94,7 +95,7 @@ class AuthNostrService extends EventEmitter implements Signer {
94
95
 
95
96
  window.addEventListener('focus', check);
96
97
  window.addEventListener('visibilitychange', () => {
97
- if (document.visibilityState === 'visible') check();
98
+ if (document.visibilityState === 'visible') check();
98
99
  });
99
100
  window.addEventListener('popstate', check);
100
101
  window.addEventListener('hashchange', check);
@@ -114,7 +115,7 @@ class AuthNostrService extends EventEmitter implements Signer {
114
115
  // Resolve pending promises if any (for non-reload cases)
115
116
  const resolved = AmberDirectSigner.resolvePending(response.id, response.type, response.result);
116
117
  if (resolved) {
117
- console.log('Resolved pending Amber promise via resolvePending');
118
+ console.log('Resolved pending Amber promise via resolvePending');
118
119
  }
119
120
 
120
121
  if (response.type === 'get_public_key' || response.type.includes('pub')) {
@@ -125,18 +126,14 @@ class AuthNostrService extends EventEmitter implements Signer {
125
126
  console.log('Amber login success', info);
126
127
  this.onAuth('login', info);
127
128
  } else {
128
- const pendingKey = `amber_pending_${response.id}`;
129
- const pending = sessionStorage.getItem(pendingKey);
130
- if (pending) {
131
- const { hash, type } = JSON.parse(pending);
132
- sessionStorage.removeItem(pendingKey);
133
- if (type === response.type) {
134
- const cacheKey = `amber_result_${type}_${hash}`;
135
- sessionStorage.setItem(cacheKey, response.result);
136
- console.log('Stored Amber result', cacheKey);
137
- }
138
- }
129
+ // 不要: この部分は削除または簡略化
130
+ // NIP-55では直接resultを使用するため、追加のキャッシュは不要
139
131
  }
132
+
133
+ // ★ 追加: URLクエリパラメータをクリーンアップ
134
+ const url = new URL(window.location.href);
135
+ url.searchParams.delete('event');
136
+ window.history.replaceState({}, '', url.toString());
140
137
  }
141
138
  }
142
139
 
package/src/types.ts CHANGED
@@ -122,3 +122,10 @@ export interface Response {
122
122
  result?: string;
123
123
  error?: string;
124
124
  }
125
+
126
+
127
+ export interface AmberResponse {
128
+ id: string;
129
+ type: string;
130
+ result: string;
131
+ }