@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/dist/index.esm.js +2 -2
- package/dist/modules/AmberDirectSigner.d.ts +4 -10
- package/dist/types.d.ts +5 -0
- package/dist/unpkg.js +2 -2
- package/package.json +1 -1
- package/src/modules/AmberDirectSigner.ts +75 -175
- package/src/modules/AuthNostrService.ts +10 -13
- package/src/types.ts +7 -0
package/package.json
CHANGED
|
@@ -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:
|
|
5
|
+
private _pubkey: string;
|
|
6
|
+
private static pendingResolves: Map<string, (result: string) => void> = new Map();
|
|
8
7
|
|
|
9
|
-
constructor(pubkey
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
35
|
+
AmberDirectSigner.pendingResolves.set(id, resolve);
|
|
102
36
|
});
|
|
103
37
|
}
|
|
104
38
|
|
|
105
|
-
async
|
|
39
|
+
public async signEvent(event: any): Promise<any> {
|
|
106
40
|
const id = Math.random().toString(36).substring(7);
|
|
107
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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 += `¤t_user=${this._pubkey}`; // ★ 追加
|
|
105
|
+
|
|
106
|
+
return url;
|
|
107
|
+
}
|
|
193
108
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
129
|
-
|
|
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
|
|