@konemono/nostr-login 1.7.61 → 1.7.63

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.63",
4
4
  "description": "",
5
5
  "main": "./dist/index.esm.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1,242 +1,162 @@
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();
8
-
9
- constructor(pubkey?: string) {
10
- this._pubkey = pubkey || '';
11
- }
12
-
13
- get pubkey() {
14
- return this._pubkey;
15
- }
16
-
17
- public set pubkey(v: string) {
18
- this._pubkey = v;
19
- }
20
-
21
- nip04 = {
22
- encrypt: (pubkey: string, plaintext: string) => this.encrypt04(pubkey, plaintext),
23
- decrypt: (pubkey: string, ciphertext: string) => this.decrypt04(pubkey, ciphertext),
24
- };
25
-
26
- nip44 = {
27
- encrypt: (pubkey: string, plaintext: string) => this.encrypt44(pubkey, plaintext),
28
- decrypt: (pubkey: string, ciphertext: string) => this.decrypt44(pubkey, ciphertext),
29
- };
30
-
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;
5
+ private _pubkey: string;
6
+ private static pendingResolves: Map<string, (result: string) => void> = new Map();
7
+
8
+ constructor(pubkey: string = '') {
9
+ this._pubkey = pubkey;
37
10
  }
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;
11
+
12
+ get pubkey() {
13
+ return this._pubkey;
48
14
  }
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
- }
15
+
16
+ set pubkey(v: string) {
17
+ this._pubkey = v;
80
18
  }
81
19
 
82
- if (resolve) {
83
- console.log('AmberDirectSigner: Promise resolved', id);
84
- resolve(result);
85
- this.pendingResolves.delete(id);
86
- return true;
20
+ public nip04 = {
21
+ encrypt: (pubkey: string, plaintext: string) => this.encrypt04(pubkey, plaintext),
22
+ decrypt: (pubkey: string, ciphertext: string) => this.decrypt04(pubkey, ciphertext),
23
+ };
24
+
25
+ public nip44 = {
26
+ encrypt: (pubkey: string, plaintext: string) => this.encrypt44(pubkey, plaintext),
27
+ decrypt: (pubkey: string, ciphertext: string) => this.decrypt44(pubkey, ciphertext),
28
+ };
29
+
30
+ public async getPublicKey(): Promise<string> {
31
+ const id = Math.random().toString(36).substring(7);
32
+ const url = this.generateUrl('', 'get_public_key', id);
33
+ window.location.href = url;
34
+ return new Promise((resolve) => {
35
+ AmberDirectSigner.pendingResolves.set(id, resolve);
36
+ });
87
37
  }
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
-
96
- const id = Math.random().toString(36).substring(7);
97
- this.setPending(id, content, 'sign_event');
98
- const url = this.generateUrl(content, 'sign_event', id);
99
- window.location.href = url;
100
- return new Promise((resolve) => {
101
- AmberDirectSigner.pendingResolves.set(id, (res) => resolve(JSON.parse(res)));
102
- });
103
- }
104
-
105
- async getPublicKey(): Promise<string> {
106
- const id = Math.random().toString(36).substring(7);
107
- this.setPending(id, '', 'get_public_key');
108
- const url = this.generateUrl('', 'get_public_key', id);
109
- window.location.href = url;
110
- return new Promise((resolve) => {
111
- AmberDirectSigner.pendingResolves.set(id, resolve);
112
- });
113
- }
114
-
115
- async encrypt04(pubkey: string, plaintext: string): Promise<string> {
116
- const cached = this.checkCache(plaintext, 'nip04_encrypt');
117
- if (cached) return cached;
118
-
119
- const id = Math.random().toString(36).substring(7);
120
- this.setPending(id, plaintext, 'nip04_encrypt');
121
- const url = this.generateUrl(plaintext, 'nip04_encrypt', id, pubkey);
122
- window.location.href = url;
123
- return new Promise((resolve) => {
124
- AmberDirectSigner.pendingResolves.set(id, resolve);
125
- });
126
- }
127
-
128
- async decrypt04(pubkey: string, ciphertext: string): Promise<string> {
129
- const cached = this.checkCache(ciphertext, 'nip04_decrypt');
130
- if (cached) return cached;
131
-
132
- const id = Math.random().toString(36).substring(7);
133
- this.setPending(id, ciphertext, 'nip04_decrypt');
134
- const url = this.generateUrl(ciphertext, 'nip04_decrypt', id, pubkey);
135
- window.location.href = url;
136
- return new Promise((resolve) => {
137
- AmberDirectSigner.pendingResolves.set(id, resolve);
138
- });
139
- }
140
-
141
- async encrypt44(pubkey: string, plaintext: string): Promise<string> {
142
- const cached = this.checkCache(plaintext, 'nip44_encrypt');
143
- if (cached) return cached;
144
-
145
- const id = Math.random().toString(36).substring(7);
146
- this.setPending(id, plaintext, 'nip44_encrypt');
147
- const url = this.generateUrl(plaintext, 'nip44_encrypt', id, pubkey);
148
- window.location.href = url;
149
- return new Promise((resolve) => {
150
- AmberDirectSigner.pendingResolves.set(id, resolve);
151
- });
152
- }
153
-
154
- async decrypt44(pubkey: string, ciphertext: string): Promise<string> {
155
- const cached = this.checkCache(ciphertext, 'nip44_decrypt');
156
- if (cached) return cached;
157
-
158
- const id = Math.random().toString(36).substring(7);
159
- this.setPending(id, ciphertext, 'nip44_decrypt');
160
- const url = this.generateUrl(ciphertext, 'nip44_decrypt', id, pubkey);
161
- window.location.href = url;
162
- return new Promise((resolve) => {
163
- AmberDirectSigner.pendingResolves.set(id, resolve);
164
- });
165
- }
166
-
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)}`);
38
+
39
+ public async signEvent(event: any): Promise<any> {
40
+ const id = Math.random().toString(36).substring(7);
41
+ const url = this.generateUrl(JSON.stringify(event), 'sign_event', id);
42
+ window.location.href = url;
43
+ return new Promise((resolve) => {
44
+ AmberDirectSigner.pendingResolves.set(id, (result: string) => {
45
+ resolve(JSON.parse(result));
46
+ });
47
+ });
187
48
  }
188
49
 
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
- }
193
-
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
- }
50
+ public async encrypt04(pubkey: string, plaintext: string): Promise<string> {
51
+ const id = Math.random().toString(36).substring(7);
52
+ const url = this.generateUrl(plaintext, 'nip04_encrypt', id, pubkey);
53
+ window.location.href = url;
54
+ return new Promise((resolve) => {
55
+ AmberDirectSigner.pendingResolves.set(id, resolve);
56
+ });
57
+ }
58
+
59
+ public async decrypt04(pubkey: string, ciphertext: string): Promise<string> {
60
+ const id = Math.random().toString(36).substring(7);
61
+ const url = this.generateUrl(ciphertext, 'nip04_decrypt', id, pubkey);
62
+ window.location.href = url;
63
+ return new Promise((resolve) => {
64
+ AmberDirectSigner.pendingResolves.set(id, resolve);
65
+ });
66
+ }
67
+
68
+ public async encrypt44(pubkey: string, plaintext: string): Promise<string> {
69
+ const id = Math.random().toString(36).substring(7);
70
+ const url = this.generateUrl(plaintext, 'nip44_encrypt', id, pubkey);
71
+ window.location.href = url;
72
+ return new Promise((resolve) => {
73
+ AmberDirectSigner.pendingResolves.set(id, resolve);
74
+ });
75
+ }
76
+
77
+ public async decrypt44(pubkey: string, ciphertext: string): Promise<string> {
78
+ const id = Math.random().toString(36).substring(7);
79
+ const url = this.generateUrl(ciphertext, 'nip44_decrypt', id, pubkey);
80
+ window.location.href = url;
81
+ return new Promise((resolve) => {
82
+ AmberDirectSigner.pendingResolves.set(id, resolve);
83
+ });
84
+ }
85
+
86
+
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;
216
107
  }
217
108
 
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';
109
+ // AmberDirectSigner.ts parseResponse を拡張
110
+
111
+ static parseResponse(): AmberResponse | null {
112
+ const url = new URL(window.location.href);
225
113
 
226
- const finalId = id || '';
114
+ // パターン1: NIP-55標準 (?event=...)
115
+ let result = url.searchParams.get('event');
227
116
 
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);
117
+ // パターン2: パス末尾にhexId (/<hexId>)
118
+ if (!result) {
119
+ const pathParts = url.pathname.split('/').filter(Boolean);
120
+ if (pathParts.length > 0) {
121
+ const lastPart = pathParts[pathParts.length - 1];
122
+ // hexIdっぽい(64文字の16進数)
123
+ if (/^[0-9a-f]{64}$/i.test(lastPart)) {
124
+ result = lastPart;
125
+ }
126
+ }
127
+ }
128
+
129
+ console.log('Amber response detection:', {
130
+ href: window.location.href,
131
+ eventParam: url.searchParams.get('event'),
132
+ pathResult: result,
133
+ sessionId: sessionStorage.getItem('amber_last_id'),
134
+ sessionType: sessionStorage.getItem('amber_last_type')
232
135
  });
233
- newUrl.hash = '';
234
- window.history.replaceState({}, '', newUrl.toString());
235
-
236
- localStorage.removeItem(PENDING_KEY); // Done
237
136
 
238
- return { type: finalType, id: finalId, result };
137
+ if (!result) return null;
138
+
139
+ const id = sessionStorage.getItem('amber_last_id');
140
+ const type = sessionStorage.getItem('amber_last_type');
141
+
142
+ sessionStorage.removeItem('amber_last_id');
143
+ sessionStorage.removeItem('amber_last_type');
144
+ sessionStorage.removeItem('amber_last_timestamp');
145
+
146
+ if (id && type) {
147
+ return { id, type, result };
148
+ }
149
+
150
+ return null;
151
+ }
152
+
153
+ static resolvePending(id: string, type: string, result: string): boolean {
154
+ const resolve = this.pendingResolves.get(id);
155
+ if (resolve) {
156
+ this.pendingResolves.delete(id);
157
+ resolve(result);
158
+ return true;
159
+ }
160
+ return false;
239
161
  }
240
- return null;
241
- }
242
162
  }
@@ -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
+ }