@konemono/nostr-login 1.9.14 → 1.10.0
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/const/index.d.ts +3 -0
- package/dist/index.esm.js +12 -10
- package/dist/index.esm.js.map +1 -1
- package/dist/modules/AuthNostrService.d.ts +13 -12
- package/dist/modules/ModalManager.d.ts +1 -0
- package/dist/modules/Nip46.d.ts +44 -47
- package/dist/modules/NostrExtensionService.d.ts +0 -5
- package/dist/modules/Signer.d.ts +4 -11
- package/dist/types.d.ts +0 -5
- package/dist/unpkg.js +12 -10
- package/dist/utils/index.d.ts +3 -2
- package/package.json +4 -7
- package/src/const/index.ts +7 -0
- package/src/index.ts +2 -8
- package/src/modules/AuthNostrService.ts +169 -311
- package/src/modules/ModalManager.ts +14 -10
- package/src/modules/Nip46.ts +400 -208
- package/src/modules/NostrExtensionService.ts +0 -6
- package/src/modules/Signer.ts +8 -39
- package/src/types.ts +0 -7
- package/src/utils/index.ts +31 -79
- package/tsconfig.json +1 -1
- package/src/modules/AmberDirectSigner.ts +0 -228
- package/src/modules/Nip46.iframe.test.ts +0 -124
- package/src/modules/Nip46.test.ts +0 -31
- package/src/modules/nip46/Nip46Adapter.ts +0 -123
- package/src/modules/nip46/Nip46Client.ts +0 -248
- package/src/modules/nip46/types.ts +0 -26
- package/vitest.config.ts +0 -9
|
@@ -2,20 +2,16 @@ import { localStorageAddAccount, bunkerUrlToInfo, isBunkerUrl, fetchProfile, get
|
|
|
2
2
|
import { ConnectionString, Info } from 'nostr-login-components/dist/types/types';
|
|
3
3
|
import { generatePrivateKey, 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
|
|
11
|
-
import { Nip46Client } from './nip46/Nip46Client';
|
|
12
|
-
import { Nip46Adapter } from './nip46/Nip46Adapter';
|
|
10
|
+
import { IframeNostrRpc, Nip46Signer, ReadyListener } from './Nip46';
|
|
13
11
|
import { PrivateKeySigner } from './Signer';
|
|
14
|
-
import { AmberDirectSigner } from './AmberDirectSigner';
|
|
15
12
|
|
|
16
13
|
const OUTBOX_RELAYS = ['wss://user.kindpag.es', 'wss://purplepag.es', 'wss://relay.nos.social'];
|
|
17
|
-
const
|
|
18
|
-
const CONNECT_TIMEOUT = 5000;
|
|
14
|
+
const DEFAULT_NOSTRCONNECT_RELAY = 'wss://relay.nsec.app/';
|
|
19
15
|
const NOSTRCONNECT_APPS: ConnectionString[] = [
|
|
20
16
|
{
|
|
21
17
|
name: 'Nsec.app',
|
|
@@ -23,29 +19,31 @@ const NOSTRCONNECT_APPS: ConnectionString[] = [
|
|
|
23
19
|
canImport: true,
|
|
24
20
|
img: 'https://nsec.app/assets/favicon.ico',
|
|
25
21
|
link: 'https://use.nsec.app/<nostrconnect>',
|
|
26
|
-
|
|
22
|
+
relay: 'wss://relay.nsec.app/',
|
|
27
23
|
},
|
|
28
24
|
{
|
|
29
25
|
name: 'Amber',
|
|
30
26
|
img: 'https://raw.githubusercontent.com/greenart7c3/Amber/refs/heads/master/assets/android-icon.svg',
|
|
31
|
-
link: '
|
|
32
|
-
|
|
27
|
+
link: '<nostrconnect>',
|
|
28
|
+
relay: 'wss://relay.nsec.app/',
|
|
33
29
|
},
|
|
34
30
|
{
|
|
35
31
|
name: 'Other key stores',
|
|
36
32
|
img: '',
|
|
37
33
|
link: '<nostrconnect>',
|
|
38
|
-
|
|
34
|
+
relay: 'wss://relay.nsec.app/',
|
|
39
35
|
},
|
|
40
36
|
];
|
|
41
37
|
|
|
42
38
|
class AuthNostrService extends EventEmitter implements Signer {
|
|
43
|
-
private
|
|
44
|
-
private
|
|
39
|
+
private ndk: NDK;
|
|
40
|
+
private profileNdk: NDK;
|
|
41
|
+
private signer: Nip46Signer | null = null;
|
|
45
42
|
private localSigner: PrivateKeySigner | null = null;
|
|
46
43
|
private params: NostrParams;
|
|
47
44
|
private signerPromise?: Promise<void>;
|
|
48
45
|
private signerErrCallback?: (err: string) => void;
|
|
46
|
+
private signerAbortController?: AbortController;
|
|
49
47
|
private readyPromise?: Promise<void>;
|
|
50
48
|
private readyCallback?: () => void;
|
|
51
49
|
private nip44Codec = new Nip44();
|
|
@@ -66,6 +64,15 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
66
64
|
constructor(params: NostrParams) {
|
|
67
65
|
super();
|
|
68
66
|
this.params = params;
|
|
67
|
+
this.ndk = new NDK({
|
|
68
|
+
enableOutboxModel: false,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
this.profileNdk = new NDK({
|
|
72
|
+
enableOutboxModel: true,
|
|
73
|
+
explicitRelayUrls: OUTBOX_RELAYS,
|
|
74
|
+
});
|
|
75
|
+
this.profileNdk.connect();
|
|
69
76
|
|
|
70
77
|
this.nip04 = {
|
|
71
78
|
encrypt: this.encrypt04.bind(this),
|
|
@@ -75,96 +82,6 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
75
82
|
encrypt: this.encrypt44.bind(this),
|
|
76
83
|
decrypt: this.decrypt44.bind(this),
|
|
77
84
|
};
|
|
78
|
-
|
|
79
|
-
setTimeout(() => this.checkAmberResponse(), 100);
|
|
80
|
-
|
|
81
|
-
const check = () => {
|
|
82
|
-
this.checkAmberResponse();
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
window.addEventListener('focus', check);
|
|
86
|
-
window.addEventListener('visibilitychange', () => {
|
|
87
|
-
if (document.visibilityState === 'visible') check();
|
|
88
|
-
});
|
|
89
|
-
window.addEventListener('popstate', check);
|
|
90
|
-
window.addEventListener('hashchange', check);
|
|
91
|
-
|
|
92
|
-
// Periodic check as a safety net
|
|
93
|
-
setInterval(check, 1000);
|
|
94
|
-
|
|
95
|
-
window.addEventListener('message', event => {
|
|
96
|
-
if (event.data && event.data.method === 'amberResponse') {
|
|
97
|
-
const { id, type, result } = event.data;
|
|
98
|
-
console.log('Amber response received via message', { id, type, result });
|
|
99
|
-
this.handleAmberResponse({ id, type, result });
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
private checkAmberResponse() {
|
|
105
|
-
const response = AmberDirectSigner.parseResponse();
|
|
106
|
-
if (response) {
|
|
107
|
-
// If we have an opener and it's not the same window, we are in a popup
|
|
108
|
-
if (window.opener && window.opener !== window) {
|
|
109
|
-
console.log('Amber response in popup, sending back to opener');
|
|
110
|
-
window.opener.postMessage({ method: 'amberResponse', ...response }, window.location.origin);
|
|
111
|
-
window.close();
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
this.handleAmberResponse(response);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
private handledAmberIds: Set<string> = new Set();
|
|
120
|
-
|
|
121
|
-
private handleAmberResponse(response: { id: string; type: string; result: string }) {
|
|
122
|
-
if (this.handledAmberIds.has(response.id)) return;
|
|
123
|
-
this.handledAmberIds.add(response.id);
|
|
124
|
-
|
|
125
|
-
console.log('Handling Amber response', response);
|
|
126
|
-
|
|
127
|
-
// Stop the "Connecting..." spinner
|
|
128
|
-
this.emit('onAuthUrl', { url: '' });
|
|
129
|
-
|
|
130
|
-
// Resolve pending promises if any (for non-reload cases)
|
|
131
|
-
const resolved = AmberDirectSigner.resolvePending(response.id, response.type, response.result);
|
|
132
|
-
if (resolved) {
|
|
133
|
-
console.log('Resolved pending Amber promise via resolvePending');
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (response.type === 'get_public_key' || response.type.includes('pub')) {
|
|
137
|
-
const info: Info = {
|
|
138
|
-
pubkey: response.result,
|
|
139
|
-
name: nip19.npubEncode(response.result),
|
|
140
|
-
authMethod: 'amber' as any,
|
|
141
|
-
relays: [],
|
|
142
|
-
signerPubkey: '',
|
|
143
|
-
};
|
|
144
|
-
console.log('Amber login success', info);
|
|
145
|
-
this.onAuth('login', info);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// URLクリーンアップをより徹底的に
|
|
149
|
-
const url = new URL(window.location.href);
|
|
150
|
-
let changed = false;
|
|
151
|
-
if (url.searchParams.has('event')) {
|
|
152
|
-
url.searchParams.delete('event');
|
|
153
|
-
changed = true;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// パス末尾が結果と一致する場合はパスもクリア
|
|
157
|
-
const pathParts = url.pathname.split('/');
|
|
158
|
-
if (pathParts.length > 0 && pathParts[pathParts.length - 1] === response.result) {
|
|
159
|
-
pathParts.pop();
|
|
160
|
-
url.pathname = pathParts.join('/') || '/';
|
|
161
|
-
changed = true;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (changed) {
|
|
165
|
-
console.log('Cleaning up Amber response URL', url.toString());
|
|
166
|
-
window.history.replaceState({}, '', url.toString());
|
|
167
|
-
}
|
|
168
85
|
}
|
|
169
86
|
|
|
170
87
|
public isIframe() {
|
|
@@ -190,8 +107,20 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
190
107
|
this.resetAuth();
|
|
191
108
|
}
|
|
192
109
|
|
|
110
|
+
public cancelSignerInit() {
|
|
111
|
+
if (this.signerAbortController) {
|
|
112
|
+
this.signerAbortController.abort();
|
|
113
|
+
this.signerAbortController = undefined;
|
|
114
|
+
}
|
|
115
|
+
if (this.signerErrCallback) {
|
|
116
|
+
this.signerErrCallback('Cancelled by user');
|
|
117
|
+
this.signerErrCallback = undefined;
|
|
118
|
+
}
|
|
119
|
+
this.emit('signerCancelled');
|
|
120
|
+
}
|
|
121
|
+
|
|
193
122
|
public async nostrConnect(
|
|
194
|
-
|
|
123
|
+
relay?: string,
|
|
195
124
|
{
|
|
196
125
|
domain = '',
|
|
197
126
|
link = '',
|
|
@@ -203,8 +132,8 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
203
132
|
importConnect?: boolean;
|
|
204
133
|
iframeUrl?: string;
|
|
205
134
|
} = {},
|
|
206
|
-
)
|
|
207
|
-
|
|
135
|
+
) {
|
|
136
|
+
relay = relay || DEFAULT_NOSTRCONNECT_RELAY;
|
|
208
137
|
|
|
209
138
|
const info: Info = {
|
|
210
139
|
authMethod: 'connect',
|
|
@@ -212,51 +141,14 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
212
141
|
signerPubkey: '', // unknown too!
|
|
213
142
|
sk: this.nostrConnectKey,
|
|
214
143
|
domain: domain,
|
|
215
|
-
relays:
|
|
144
|
+
relays: [relay],
|
|
216
145
|
iframeUrl,
|
|
217
146
|
};
|
|
218
147
|
|
|
219
148
|
console.log('nostrconnect info', info, link);
|
|
220
149
|
|
|
221
150
|
// non-iframe flow
|
|
222
|
-
if (link && !iframeUrl)
|
|
223
|
-
if (link === 'amber') {
|
|
224
|
-
const signer = new AmberDirectSigner();
|
|
225
|
-
this.amberSigner = signer;
|
|
226
|
-
|
|
227
|
-
const id = Math.random().toString(36).substring(7);
|
|
228
|
-
const url = signer.generateUrl('', 'get_public_key', id);
|
|
229
|
-
|
|
230
|
-
// Emit for the "Connecting..." spinner
|
|
231
|
-
this.emit('onAuthUrl', { url });
|
|
232
|
-
|
|
233
|
-
try {
|
|
234
|
-
const pubkey = await signer.getPublicKey(id);
|
|
235
|
-
const info: Info = {
|
|
236
|
-
pubkey,
|
|
237
|
-
name: nip19.npubEncode(pubkey),
|
|
238
|
-
authMethod: 'amber' as any,
|
|
239
|
-
relays: [],
|
|
240
|
-
signerPubkey: '',
|
|
241
|
-
};
|
|
242
|
-
this.onAuth('login', info);
|
|
243
|
-
return info;
|
|
244
|
-
} catch (e) {
|
|
245
|
-
console.log('Amber getPublicKey failed or was blocked', e);
|
|
246
|
-
// Fallback: wait for onAuth to be called (e.g. via user clicking Continue)
|
|
247
|
-
return new Promise(resolve => {
|
|
248
|
-
const handler = (info: Info | null) => {
|
|
249
|
-
if (info && info.authMethod === ('amber' as any)) {
|
|
250
|
-
this.off('onUserInfo', handler); // Use onUserInfo as a proxy for onAuth
|
|
251
|
-
resolve(info);
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
this.on('onUserInfo', handler);
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
window.open(link, '_blank', 'width=400,height=700');
|
|
259
|
-
}
|
|
151
|
+
if (link && !iframeUrl) window.open(link, '_blank', 'width=400,height=700');
|
|
260
152
|
|
|
261
153
|
// init nip46 signer
|
|
262
154
|
await this.initSigner(info, { listen: true });
|
|
@@ -264,7 +156,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
264
156
|
// signer learns the remote pubkey
|
|
265
157
|
if (!info.pubkey || !info.signerPubkey) throw new Error('Bad remote pubkey');
|
|
266
158
|
|
|
267
|
-
info.bunkerUrl = `bunker://${info.signerPubkey}
|
|
159
|
+
info.bunkerUrl = `bunker://${info.signerPubkey}?relay=${relay}`;
|
|
268
160
|
|
|
269
161
|
// callback
|
|
270
162
|
if (!importConnect) this.onAuth('login', info);
|
|
@@ -272,14 +164,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
272
164
|
return info;
|
|
273
165
|
}
|
|
274
166
|
|
|
275
|
-
public async createNostrConnect(
|
|
276
|
-
/*const relayList = relays
|
|
277
|
-
? relays
|
|
278
|
-
.split(",")
|
|
279
|
-
.map(r => r.trim().replace(/['"]/g, ""))
|
|
280
|
-
.filter(r => r.length > 0)
|
|
281
|
-
: [];*/
|
|
282
|
-
|
|
167
|
+
public async createNostrConnect(relay?: string) {
|
|
283
168
|
this.nostrConnectKey = generatePrivateKey();
|
|
284
169
|
this.nostrConnectSecret = Math.random().toString(36).substring(7);
|
|
285
170
|
|
|
@@ -291,7 +176,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
291
176
|
perms: encodeURIComponent(this.params.optionsModal.perms || ''),
|
|
292
177
|
};
|
|
293
178
|
|
|
294
|
-
return `nostrconnect://${pubkey}?image=${meta.icon}&url=${meta.url}&name=${meta.name}&perms=${meta.perms}&secret=${this.nostrConnectSecret}${
|
|
179
|
+
return `nostrconnect://${pubkey}?image=${meta.icon}&url=${meta.url}&name=${meta.name}&perms=${meta.perms}&secret=${this.nostrConnectSecret}${relay ? `&relay=${relay}` : ''}`;
|
|
295
180
|
}
|
|
296
181
|
|
|
297
182
|
public async getNostrConnectServices(): Promise<[string, ConnectionString[]]> {
|
|
@@ -311,35 +196,25 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
311
196
|
// }
|
|
312
197
|
|
|
313
198
|
for (const a of apps) {
|
|
314
|
-
let
|
|
315
|
-
|
|
199
|
+
let relay = DEFAULT_NOSTRCONNECT_RELAY;
|
|
316
200
|
if (a.link.startsWith('https://')) {
|
|
317
|
-
|
|
201
|
+
let domain = a.domain || new URL(a.link).hostname;
|
|
318
202
|
try {
|
|
319
203
|
const info = await (await fetch(`https://${domain}/.well-known/nostr.json`)).json();
|
|
320
204
|
const pubkey = info.names['_'];
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
relays = appRelays;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
a.iframeUrl = info.nip46?.iframe_url || '';
|
|
205
|
+
const relays = info.nip46[pubkey] as string[];
|
|
206
|
+
if (relays && relays.length) relay = relays[0];
|
|
207
|
+
a.iframeUrl = info.nip46.iframe_url || '';
|
|
328
208
|
} catch (e) {
|
|
329
209
|
console.log('Bad app info', e, a);
|
|
330
210
|
}
|
|
331
211
|
}
|
|
332
|
-
|
|
333
|
-
const relayParams = relays
|
|
334
|
-
.map(r => r.replace(/['"]/g, ''))
|
|
335
|
-
.map(r => `&relay=${encodeURIComponent(r)}`)
|
|
336
|
-
.join('');
|
|
337
|
-
|
|
338
|
-
const nc = nostrconnect + relayParams;
|
|
339
|
-
|
|
212
|
+
const nc = nostrconnect + '&relay=' + relay;
|
|
340
213
|
if (a.iframeUrl) {
|
|
214
|
+
// pass plain nc url for iframe-based flow
|
|
341
215
|
a.link = nc;
|
|
342
216
|
} else {
|
|
217
|
+
// we will open popup ourselves
|
|
343
218
|
a.link = a.link.replace('<nostrconnect>', nc);
|
|
344
219
|
}
|
|
345
220
|
}
|
|
@@ -365,7 +240,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
365
240
|
this.releaseSigner();
|
|
366
241
|
this.localSigner = new PrivateKeySigner(info.sk!);
|
|
367
242
|
|
|
368
|
-
if (signup) await createProfile(info, this.localSigner, this.params.optionsModal.signupRelays, this.params.optionsModal.outboxRelays);
|
|
243
|
+
if (signup) await createProfile(info, this.profileNdk, this.localSigner, this.params.optionsModal.signupRelays, this.params.optionsModal.outboxRelays);
|
|
369
244
|
|
|
370
245
|
this.onAuth(signup ? 'signup' : 'login', info);
|
|
371
246
|
}
|
|
@@ -380,18 +255,19 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
380
255
|
}
|
|
381
256
|
|
|
382
257
|
public async importAndConnect(cs: ConnectionString) {
|
|
383
|
-
const {
|
|
258
|
+
const { relay, domain, link, iframeUrl } = cs;
|
|
384
259
|
if (!domain) throw new Error('Domain required');
|
|
385
260
|
|
|
386
|
-
const info = await this.nostrConnect(
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
});
|
|
261
|
+
const info = await this.nostrConnect(relay, { domain, link, importConnect: true, iframeUrl });
|
|
262
|
+
|
|
263
|
+
// logout to remove local keys from storage
|
|
264
|
+
// but keep the connect signer
|
|
265
|
+
await this.logout(/*keepSigner*/ true);
|
|
392
266
|
|
|
393
|
-
|
|
267
|
+
// release local one
|
|
394
268
|
this.localSigner = null;
|
|
269
|
+
|
|
270
|
+
// notify app that we've switched to 'connect' keys
|
|
395
271
|
this.onAuth('login', info);
|
|
396
272
|
}
|
|
397
273
|
|
|
@@ -418,12 +294,6 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
418
294
|
await this.endAuth();
|
|
419
295
|
}
|
|
420
296
|
|
|
421
|
-
public async setAmber(info: Info) {
|
|
422
|
-
this.releaseSigner();
|
|
423
|
-
this.amberSigner = new AmberDirectSigner(info.pubkey);
|
|
424
|
-
this.onAuth('login', info);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
297
|
public async createAccount(nip05: string) {
|
|
428
298
|
const [name, domain] = nip05.split('@');
|
|
429
299
|
|
|
@@ -443,8 +313,8 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
443
313
|
const userPubkey = await this.signer!.createAccount2({ bunkerPubkey: info.signerPubkey!, name, domain, perms: this.params.optionsModal.perms });
|
|
444
314
|
|
|
445
315
|
return {
|
|
446
|
-
bunkerUrl: `bunker://${userPubkey}
|
|
447
|
-
sk: info.sk,
|
|
316
|
+
bunkerUrl: `bunker://${userPubkey}?relay=${info.relays?.[0]}`,
|
|
317
|
+
sk: info.sk, // reuse the same local key
|
|
448
318
|
};
|
|
449
319
|
}
|
|
450
320
|
|
|
@@ -452,7 +322,11 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
452
322
|
this.signer = null;
|
|
453
323
|
this.signerErrCallback?.('cancelled');
|
|
454
324
|
this.localSigner = null;
|
|
455
|
-
|
|
325
|
+
|
|
326
|
+
// disconnect from signer relays
|
|
327
|
+
for (const r of this.ndk.pool.relays.keys()) {
|
|
328
|
+
this.ndk.pool.removeRelay(r);
|
|
329
|
+
}
|
|
456
330
|
}
|
|
457
331
|
|
|
458
332
|
public async logout(keepSigner = false) {
|
|
@@ -497,7 +371,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
497
371
|
|
|
498
372
|
if (info) {
|
|
499
373
|
// async profile fetch
|
|
500
|
-
fetchProfile(info,
|
|
374
|
+
fetchProfile(info, this.profileNdk).then(p => {
|
|
501
375
|
if (this.params.userInfo !== info) return;
|
|
502
376
|
|
|
503
377
|
const userInfo = {
|
|
@@ -626,7 +500,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
626
500
|
return !!this.readyCallback;
|
|
627
501
|
}
|
|
628
502
|
|
|
629
|
-
public startAuth() {
|
|
503
|
+
public async startAuth() {
|
|
630
504
|
console.log('startAuth');
|
|
631
505
|
if (this.readyCallback) throw new Error('Already started');
|
|
632
506
|
|
|
@@ -687,71 +561,24 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
687
561
|
// FIXME shouldn't this come from nostrconnect service list?
|
|
688
562
|
this.emit('onIframeUrl', info.iframeUrl);
|
|
689
563
|
|
|
564
|
+
// AbortControllerでキャンセル可能にする
|
|
565
|
+
this.signerAbortController = new AbortController();
|
|
566
|
+
const abortPromise = new Promise<never>((_, reject) => {
|
|
567
|
+
this.signerAbortController!.signal.addEventListener('abort', () => {
|
|
568
|
+
reject(new Error('Cancelled by user'));
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
|
|
690
572
|
this.signerPromise = new Promise<void>(async (ok, err) => {
|
|
691
573
|
this.signerErrCallback = err;
|
|
692
574
|
try {
|
|
693
|
-
//
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
if (info.iframeUrl) {
|
|
697
|
-
// use NDK-free iframe signer implementation (MessagePort + SimplePool)
|
|
698
|
-
this.signer = new Nip46Signer(localSigner, info.signerPubkey!, iframeOrigin, info.relays || []);
|
|
699
|
-
|
|
700
|
-
// we should notify the banner the same way as the onAuthUrl does
|
|
701
|
-
this.signer.on(`iframeRestart`, async () => {
|
|
702
|
-
const iframeUrl = info.iframeUrl + (info.iframeUrl!.includes('?') ? '&' : '?') + 'pubkey=' + info.pubkey + '&rebind=' + localSigner.pubkey;
|
|
703
|
-
this.emit('iframeRestart', { pubkey: info.pubkey, iframeUrl });
|
|
704
|
-
});
|
|
705
|
-
|
|
706
|
-
// OAuth flow
|
|
707
|
-
this.signer.on('authUrl', (url: string) => {
|
|
708
|
-
console.log('nostr login auth url', url);
|
|
709
|
-
|
|
710
|
-
// notify our UI
|
|
711
|
-
this.emit('onAuthUrl', { url, iframeUrl: info.iframeUrl, eventToAddAccount });
|
|
712
|
-
});
|
|
713
|
-
} else {
|
|
714
|
-
// New SimplePool-based NIP-46 flow
|
|
715
|
-
const client = new Nip46Client({
|
|
716
|
-
localPrivateKey: info.sk!,
|
|
717
|
-
remotePubkey: info.signerPubkey!,
|
|
718
|
-
relays: info.relays || [],
|
|
719
|
-
timeoutMs: 30000,
|
|
720
|
-
});
|
|
721
|
-
|
|
722
|
-
const adapter = new Nip46Adapter(client, localSigner);
|
|
723
|
-
this.signer = adapter;
|
|
724
|
-
|
|
725
|
-
// OAuth flow: forward authUrl events
|
|
726
|
-
this.signer.on('authUrl', (url: string) => {
|
|
727
|
-
console.log('nostr login auth url', url);
|
|
728
|
-
this.emit('onAuthUrl', { url, iframeUrl: info.iframeUrl, eventToAddAccount });
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
|
-
if (listen) {
|
|
732
|
-
// nostrconnect: flow
|
|
733
|
-
// wait for the incoming message from signer
|
|
734
|
-
await this.listen(info);
|
|
735
|
-
} else if (connect) {
|
|
736
|
-
// bunker: flow
|
|
737
|
-
// send 'connect' message to signer
|
|
738
|
-
await this.connect(info, this.params.optionsModal.perms);
|
|
739
|
-
} else {
|
|
740
|
-
// provide saved pubkey as a hint
|
|
741
|
-
await this.signer!.initUserPubkey(info.pubkey);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
// ensure, we're using it in callbacks above
|
|
745
|
-
// and expect info to be valid after this call
|
|
746
|
-
info.pubkey = this.signer!.userPubkey;
|
|
747
|
-
// learned after nostrconnect flow
|
|
748
|
-
info.signerPubkey = this.signer!.remotePubkey;
|
|
749
|
-
|
|
750
|
-
ok();
|
|
575
|
+
// タイムアウトとキャンセルの両方に対応
|
|
576
|
+
await Promise.race([this.initSignerInternal(info, listen, connect, eventToAddAccount, ok), abortPromise]);
|
|
751
577
|
} catch (e) {
|
|
752
578
|
console.log('initSigner failure', e);
|
|
753
579
|
// make sure signer isn't set
|
|
754
580
|
this.signer = null;
|
|
581
|
+
this.signerAbortController = undefined;
|
|
755
582
|
err(e);
|
|
756
583
|
}
|
|
757
584
|
});
|
|
@@ -759,9 +586,73 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
759
586
|
return this.signerPromise;
|
|
760
587
|
}
|
|
761
588
|
|
|
589
|
+
private async initSignerInternal(info: Info, listen: boolean, connect: boolean, eventToAddAccount: boolean, resolve: () => void) {
|
|
590
|
+
// pre-connect if we're creating the connection (listen|connect) or
|
|
591
|
+
// not iframe mode
|
|
592
|
+
if (info.relays && !info.iframeUrl) {
|
|
593
|
+
for (const r of info.relays) {
|
|
594
|
+
this.ndk.addExplicitRelay(r, undefined);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// wait until we connect, otherwise
|
|
599
|
+
// signer won't start properly
|
|
600
|
+
await this.ndk.connect();
|
|
601
|
+
|
|
602
|
+
// create and prepare the signer
|
|
603
|
+
const localSigner = new PrivateKeySigner(info.sk!);
|
|
604
|
+
this.signer = new Nip46Signer(this.ndk, localSigner, info.signerPubkey!, info.iframeUrl ? new URL(info.iframeUrl!).origin : undefined);
|
|
605
|
+
|
|
606
|
+
// we should notify the banner the same way as
|
|
607
|
+
// the onAuthUrl does
|
|
608
|
+
this.signer.on(`iframeRestart`, async () => {
|
|
609
|
+
const iframeUrl = info.iframeUrl + (info.iframeUrl!.includes('?') ? '&' : '?') + 'pubkey=' + info.pubkey + '&rebind=' + localSigner.pubkey;
|
|
610
|
+
this.emit('iframeRestart', { pubkey: info.pubkey, iframeUrl });
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// OAuth flow
|
|
614
|
+
// if (!listen) {
|
|
615
|
+
this.signer.on('authUrl', (url: string) => {
|
|
616
|
+
console.log('nostr login auth url', url);
|
|
617
|
+
|
|
618
|
+
// notify our UI
|
|
619
|
+
this.emit('onAuthUrl', { url, iframeUrl: info.iframeUrl, eventToAddAccount });
|
|
620
|
+
});
|
|
621
|
+
// }
|
|
622
|
+
|
|
623
|
+
if (listen) {
|
|
624
|
+
// nostrconnect: flow
|
|
625
|
+
// wait for the incoming message from signer
|
|
626
|
+
await this.listen(info);
|
|
627
|
+
} else if (connect) {
|
|
628
|
+
// bunker: flow
|
|
629
|
+
// send 'connect' message to signer
|
|
630
|
+
await this.connect(info, this.params.optionsModal.perms);
|
|
631
|
+
} else {
|
|
632
|
+
// provide saved pubkey as a hint
|
|
633
|
+
await this.signer!.initUserPubkey(info.pubkey);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// ensure, we're using it in callbacks above
|
|
637
|
+
// and expect info to be valid after this call
|
|
638
|
+
info.pubkey = this.signer!.userPubkey;
|
|
639
|
+
// learned after nostrconnect flow
|
|
640
|
+
info.signerPubkey = this.signer!.remotePubkey;
|
|
641
|
+
|
|
642
|
+
this.signerAbortController = undefined;
|
|
643
|
+
resolve();
|
|
644
|
+
}
|
|
645
|
+
|
|
762
646
|
public async authNip46(
|
|
763
647
|
type: 'login' | 'signup',
|
|
764
|
-
{
|
|
648
|
+
{
|
|
649
|
+
name,
|
|
650
|
+
bunkerUrl,
|
|
651
|
+
sk = '',
|
|
652
|
+
domain = '',
|
|
653
|
+
iframeUrl = '',
|
|
654
|
+
customRelays,
|
|
655
|
+
}: { name: string; bunkerUrl: string; sk?: string; domain?: string; iframeUrl?: string; customRelays?: string[] },
|
|
765
656
|
) {
|
|
766
657
|
try {
|
|
767
658
|
const info = bunkerUrlToInfo(bunkerUrl, sk);
|
|
@@ -773,8 +664,13 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
773
664
|
if (domain) info.domain = domain;
|
|
774
665
|
if (iframeUrl) info.iframeUrl = iframeUrl;
|
|
775
666
|
|
|
667
|
+
// カスタムリレーが指定されていれば使用する
|
|
668
|
+
if (customRelays && customRelays.length > 0) {
|
|
669
|
+
info.relays = customRelays;
|
|
670
|
+
}
|
|
671
|
+
|
|
776
672
|
// console.log('nostr login auth info', info);
|
|
777
|
-
if (!info.signerPubkey || !info.sk || !info.relays
|
|
673
|
+
if (!info.signerPubkey || !info.sk || !info.relays?.[0]) {
|
|
778
674
|
throw new Error(`Bad bunker url ${bunkerUrl}`);
|
|
779
675
|
}
|
|
780
676
|
|
|
@@ -795,37 +691,22 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
795
691
|
}
|
|
796
692
|
|
|
797
693
|
public async signEvent(event: any) {
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
} else {
|
|
810
|
-
event.pubkey = this.signer?.remotePubkey;
|
|
811
|
-
event.id = getEventHash(event);
|
|
812
|
-
event.sig = await this.signer?.sign(event);
|
|
813
|
-
}
|
|
814
|
-
return event;
|
|
815
|
-
})();
|
|
816
|
-
|
|
817
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
818
|
-
setTimeout(() => reject(new Error('Sign timeout')), timeoutMs);
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
const result = await Promise.race([signPromise, timeoutPromise]);
|
|
822
|
-
console.log('signed', { event: result });
|
|
823
|
-
return result;
|
|
694
|
+
if (this.localSigner) {
|
|
695
|
+
event.pubkey = getPublicKey(this.localSigner.privateKey!);
|
|
696
|
+
event.id = getEventHash(event);
|
|
697
|
+
event.sig = await this.localSigner.sign(event);
|
|
698
|
+
} else {
|
|
699
|
+
event.pubkey = this.signer?.remotePubkey;
|
|
700
|
+
event.id = getEventHash(event);
|
|
701
|
+
event.sig = await this.signer?.sign(event);
|
|
702
|
+
}
|
|
703
|
+
console.log('signed', { event });
|
|
704
|
+
return event;
|
|
824
705
|
}
|
|
825
706
|
|
|
826
707
|
private async codec_call(method: string, pubkey: string, param: string) {
|
|
827
708
|
return new Promise<string>((resolve, reject) => {
|
|
828
|
-
this.signer!.rpc.sendRequest(this.signer!.remotePubkey!, method, [pubkey, param], 24133, (response:
|
|
709
|
+
this.signer!.rpc.sendRequest(this.signer!.remotePubkey!, method, [pubkey, param], 24133, (response: NDKRpcResponse) => {
|
|
829
710
|
if (!response.error) {
|
|
830
711
|
resolve(response.result);
|
|
831
712
|
} else {
|
|
@@ -837,35 +718,20 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
837
718
|
|
|
838
719
|
public async encrypt04(pubkey: string, plaintext: string) {
|
|
839
720
|
if (this.localSigner) {
|
|
840
|
-
return this.localSigner.encrypt(pubkey, plaintext);
|
|
841
|
-
} else if (this.params.userInfo?.authMethod === ('amber' as any)) {
|
|
842
|
-
const userInfo = this.params.userInfo!;
|
|
843
|
-
if (!this.amberSigner) this.amberSigner = new AmberDirectSigner(userInfo.pubkey);
|
|
844
|
-
return this.amberSigner.encrypt04(pubkey, plaintext);
|
|
721
|
+
return this.localSigner.encrypt(new NDKUser({ pubkey }), plaintext);
|
|
845
722
|
} else {
|
|
846
|
-
|
|
847
|
-
if (this.signer && typeof this.signer.encrypt === 'function') {
|
|
848
|
-
return this.signer.encrypt(pubkey, plaintext);
|
|
849
|
-
}
|
|
850
|
-
// fallback to remote codec via signer RPC
|
|
851
|
-
return this.codec_call('nip04_encrypt', pubkey, plaintext);
|
|
723
|
+
return this.signer!.encrypt(new NDKUser({ pubkey }), plaintext);
|
|
852
724
|
}
|
|
853
725
|
}
|
|
854
726
|
|
|
855
727
|
public async decrypt04(pubkey: string, ciphertext: string) {
|
|
856
728
|
if (this.localSigner) {
|
|
857
|
-
return this.localSigner.decrypt(pubkey, ciphertext);
|
|
858
|
-
} else if (this.params.userInfo?.authMethod === ('amber' as any)) {
|
|
859
|
-
const userInfo = this.params.userInfo!;
|
|
860
|
-
if (!this.amberSigner) this.amberSigner = new AmberDirectSigner(userInfo.pubkey);
|
|
861
|
-
return this.amberSigner.decrypt04(pubkey, ciphertext);
|
|
729
|
+
return this.localSigner.decrypt(new NDKUser({ pubkey }), ciphertext);
|
|
862
730
|
} else {
|
|
863
|
-
//
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
}
|
|
731
|
+
// decrypt is broken in ndk v2.3.1, and latest
|
|
732
|
+
// ndk v2.8.1 doesn't allow to override connect easily,
|
|
733
|
+
// so we reimplement and fix decrypt here as a temporary fix
|
|
867
734
|
|
|
868
|
-
// fallback to remote codec via signer RPC
|
|
869
735
|
return this.codec_call('nip04_decrypt', pubkey, ciphertext);
|
|
870
736
|
}
|
|
871
737
|
}
|
|
@@ -873,12 +739,8 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
873
739
|
public async encrypt44(pubkey: string, plaintext: string) {
|
|
874
740
|
if (this.localSigner) {
|
|
875
741
|
return this.nip44Codec.encrypt(this.localSigner.privateKey!, pubkey, plaintext);
|
|
876
|
-
} else if (this.params.userInfo?.authMethod === ('amber' as any)) {
|
|
877
|
-
const userInfo = this.params.userInfo!;
|
|
878
|
-
if (!this.amberSigner) this.amberSigner = new AmberDirectSigner(userInfo.pubkey);
|
|
879
|
-
return this.amberSigner.encrypt44(pubkey, plaintext);
|
|
880
742
|
} else {
|
|
881
|
-
// no support of nip44 in
|
|
743
|
+
// no support of nip44 in ndk yet
|
|
882
744
|
return this.codec_call('nip44_encrypt', pubkey, plaintext);
|
|
883
745
|
}
|
|
884
746
|
}
|
|
@@ -886,12 +748,8 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
886
748
|
public async decrypt44(pubkey: string, ciphertext: string) {
|
|
887
749
|
if (this.localSigner) {
|
|
888
750
|
return this.nip44Codec.decrypt(this.localSigner.privateKey!, pubkey, ciphertext);
|
|
889
|
-
} else if (this.params.userInfo?.authMethod === ('amber' as any)) {
|
|
890
|
-
const userInfo = this.params.userInfo!;
|
|
891
|
-
if (!this.amberSigner) this.amberSigner = new AmberDirectSigner(userInfo.pubkey);
|
|
892
|
-
return this.amberSigner.decrypt44(pubkey, ciphertext);
|
|
893
751
|
} else {
|
|
894
|
-
// no support of nip44 in
|
|
752
|
+
// no support of nip44 in ndk yet
|
|
895
753
|
return this.codec_call('nip44_decrypt', pubkey, ciphertext);
|
|
896
754
|
}
|
|
897
755
|
}
|