@konemono/nostr-login 1.7.36 → 1.7.37
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 +4 -4
- package/dist/index.esm.js.map +1 -1
- package/dist/modules/AuthNostrService.d.ts +3 -3
- package/dist/modules/BannerManager.d.ts +1 -1
- package/dist/modules/ModalManager.d.ts +1 -1
- package/dist/modules/Nip46.d.ts +0 -2
- package/dist/modules/Nostr.d.ts +1 -1
- package/dist/modules/NostrParams.d.ts +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/unpkg.js +2 -2
- package/dist/utils/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/const/index.ts +1 -2
- package/src/iife-module.ts +2 -7
- package/src/index.ts +2 -7
- package/src/modules/AuthNostrService.ts +170 -56
- package/src/modules/BannerManager.ts +1 -1
- package/src/modules/ModalManager.ts +58 -51
- package/src/modules/Nip46.ts +70 -249
- package/src/modules/Nostr.ts +1 -1
- package/src/modules/NostrParams.ts +1 -1
- package/src/modules/ProcessManager.ts +18 -57
- package/src/types.ts +31 -1
- package/src/utils/index.ts +1 -1
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Info, RecentType } from 'nostr-login-components';
|
|
1
|
+
import { Info, RecentType } from 'nostr-login-components/dist/types/types';
|
|
2
2
|
import NDK, { NDKSigner } from '@nostr-dev-kit/ndk';
|
|
3
3
|
import { NostrLoginOptions } from '../types';
|
|
4
4
|
export declare const localStorageSetItem: (key: string, value: string) => void;
|
package/package.json
CHANGED
package/src/const/index.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export const CALL_TIMEOUT =
|
|
2
|
-
export const NIP46_TIMEOUT = 30000;
|
|
1
|
+
export const CALL_TIMEOUT = 5000;
|
package/src/iife-module.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { init } from './index';
|
|
2
2
|
import { NostrLoginOptions, StartScreens } from './types';
|
|
3
3
|
|
|
4
|
+
// wrap to hide local vars
|
|
4
5
|
(() => {
|
|
6
|
+
// currentScript only visible in global scope code, not event handlers
|
|
5
7
|
const cs = document.currentScript;
|
|
6
8
|
const start = async () => {
|
|
7
9
|
const options: NostrLoginOptions = {};
|
|
@@ -69,13 +71,6 @@ import { NostrLoginOptions, StartScreens } from './types';
|
|
|
69
71
|
const custom = cs.getAttribute('data-custom-nostr-connect') === 'true';
|
|
70
72
|
if (custom) options.customNostrConnect = custom;
|
|
71
73
|
|
|
72
|
-
const connectRelays = cs.getAttribute('data-connect-relays');
|
|
73
|
-
if (connectRelays)
|
|
74
|
-
options.connectRelays = connectRelays
|
|
75
|
-
.split(',')
|
|
76
|
-
.map(r => r.trim())
|
|
77
|
-
.filter(r => !!r);
|
|
78
|
-
|
|
79
74
|
console.log('nostr-login options', options);
|
|
80
75
|
}
|
|
81
76
|
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import 'nostr-login-components';
|
|
|
2
2
|
import { AuthNostrService, NostrExtensionService, Popup, NostrParams, Nostr, ProcessManager, BannerManager, ModalManager } from './modules';
|
|
3
3
|
import { NostrLoginAuthOptions, NostrLoginOptions, StartScreens } from './types';
|
|
4
4
|
import { localStorageGetAccounts, localStorageGetCurrent, localStorageGetRecents, localStorageSetItem } from './utils';
|
|
5
|
-
import { Info } from 'nostr-login-components';
|
|
5
|
+
import { Info } from 'nostr-login-components/dist/types/types';
|
|
6
6
|
import { NostrObjectParams } from './modules/Nostr';
|
|
7
7
|
|
|
8
8
|
export class NostrLoginInitializer {
|
|
@@ -93,11 +93,6 @@ export class NostrLoginInitializer {
|
|
|
93
93
|
this.bannerManager.onUserInfo(info);
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
-
this.authNostrService.on('reconnecting', () => {
|
|
97
|
-
// リレー再接続中はProcessManagerのタイマーをリセット
|
|
98
|
-
this.processManager.onIframeUrl();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
96
|
this.modalManager.on('onAuthUrlClick', url => {
|
|
102
97
|
this.openPopup(url);
|
|
103
98
|
});
|
|
@@ -327,7 +322,7 @@ export class NostrLoginInitializer {
|
|
|
327
322
|
};
|
|
328
323
|
|
|
329
324
|
public cancelNeedAuth = () => {
|
|
330
|
-
console.log(
|
|
325
|
+
console.log("cancelNeedAuth");
|
|
331
326
|
this.fulfillCustomLaunchPromise();
|
|
332
327
|
this.authNostrService.cancelNostrConnect();
|
|
333
328
|
};
|
|
@@ -12,6 +12,7 @@ import { PrivateKeySigner } from './Signer';
|
|
|
12
12
|
|
|
13
13
|
const OUTBOX_RELAYS = ['wss://user.kindpag.es', 'wss://purplepag.es', 'wss://relay.nos.social'];
|
|
14
14
|
const DEFAULT_NOSTRCONNECT_RELAYS = ['wss://relay.nsec.app/', 'wss://ephemeral.snowflare.cc/'];
|
|
15
|
+
const CONNECT_TIMEOUT = 5000;
|
|
15
16
|
const NOSTRCONNECT_APPS: ConnectionString[] = [
|
|
16
17
|
{
|
|
17
18
|
name: 'Nsec.app',
|
|
@@ -19,19 +20,19 @@ const NOSTRCONNECT_APPS: ConnectionString[] = [
|
|
|
19
20
|
canImport: true,
|
|
20
21
|
img: 'https://nsec.app/assets/favicon.ico',
|
|
21
22
|
link: 'https://use.nsec.app/<nostrconnect>',
|
|
22
|
-
|
|
23
|
+
relays: DEFAULT_NOSTRCONNECT_RELAYS,
|
|
23
24
|
},
|
|
24
25
|
{
|
|
25
26
|
name: 'Amber',
|
|
26
27
|
img: 'https://raw.githubusercontent.com/greenart7c3/Amber/refs/heads/master/assets/android-icon.svg',
|
|
27
28
|
link: '<nostrconnect>',
|
|
28
|
-
|
|
29
|
+
relays: DEFAULT_NOSTRCONNECT_RELAYS,
|
|
29
30
|
},
|
|
30
31
|
{
|
|
31
32
|
name: 'Other key stores',
|
|
32
33
|
img: '',
|
|
33
34
|
link: '<nostrconnect>',
|
|
34
|
-
|
|
35
|
+
relays: DEFAULT_NOSTRCONNECT_RELAYS,
|
|
35
36
|
},
|
|
36
37
|
];
|
|
37
38
|
|
|
@@ -71,7 +72,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
71
72
|
enableOutboxModel: true,
|
|
72
73
|
explicitRelayUrls: OUTBOX_RELAYS,
|
|
73
74
|
});
|
|
74
|
-
this.profileNdk.connect();
|
|
75
|
+
this.profileNdk.connect(CONNECT_TIMEOUT);
|
|
75
76
|
|
|
76
77
|
this.nip04 = {
|
|
77
78
|
encrypt: this.encrypt04.bind(this),
|
|
@@ -91,13 +92,13 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
91
92
|
if (this.signerPromise) {
|
|
92
93
|
try {
|
|
93
94
|
await this.signerPromise;
|
|
94
|
-
} catch {}
|
|
95
|
+
} catch { }
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
if (this.readyPromise) {
|
|
98
99
|
try {
|
|
99
100
|
await this.readyPromise;
|
|
100
|
-
} catch {}
|
|
101
|
+
} catch { }
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
|
|
@@ -106,15 +107,8 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
106
107
|
this.resetAuth();
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
private getDefaultRelays(customRelays?: string[]): string[] {
|
|
110
|
-
if (customRelays && customRelays.length > 0) {
|
|
111
|
-
return customRelays;
|
|
112
|
-
}
|
|
113
|
-
return [...DEFAULT_NOSTRCONNECT_RELAYS];
|
|
114
|
-
}
|
|
115
|
-
|
|
116
110
|
public async nostrConnect(
|
|
117
|
-
|
|
111
|
+
relays?: string[],
|
|
118
112
|
{
|
|
119
113
|
domain = '',
|
|
120
114
|
link = '',
|
|
@@ -127,12 +121,13 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
127
121
|
iframeUrl?: string;
|
|
128
122
|
} = {},
|
|
129
123
|
) {
|
|
130
|
-
|
|
124
|
+
relays = relays && relays.length > 0 ? relays : DEFAULT_NOSTRCONNECT_RELAYS;
|
|
125
|
+
|
|
131
126
|
|
|
132
127
|
const info: Info = {
|
|
133
128
|
authMethod: 'connect',
|
|
134
|
-
pubkey: '',
|
|
135
|
-
signerPubkey: '',
|
|
129
|
+
pubkey: '', // unknown yet!
|
|
130
|
+
signerPubkey: '', // unknown too!
|
|
136
131
|
sk: this.nostrConnectKey,
|
|
137
132
|
domain: domain,
|
|
138
133
|
relays: relays,
|
|
@@ -141,20 +136,31 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
141
136
|
|
|
142
137
|
console.log('nostrconnect info', info, link);
|
|
143
138
|
|
|
139
|
+
// non-iframe flow
|
|
144
140
|
if (link && !iframeUrl) window.open(link, '_blank', 'width=400,height=700');
|
|
145
141
|
|
|
142
|
+
// init nip46 signer
|
|
146
143
|
await this.initSigner(info, { listen: true });
|
|
147
144
|
|
|
145
|
+
// signer learns the remote pubkey
|
|
148
146
|
if (!info.pubkey || !info.signerPubkey) throw new Error('Bad remote pubkey');
|
|
149
147
|
|
|
150
|
-
info.bunkerUrl = `bunker://${info.signerPubkey}?${relays.map(r =>
|
|
148
|
+
info.bunkerUrl = `bunker://${info.signerPubkey}?${relays.map((r, i) => `${i !== 0 ? '&' : ''}relay=${r}`)}`;
|
|
151
149
|
|
|
150
|
+
// callback
|
|
152
151
|
if (!importConnect) this.onAuth('login', info);
|
|
153
152
|
|
|
154
153
|
return info;
|
|
155
154
|
}
|
|
156
155
|
|
|
157
|
-
public async createNostrConnect(
|
|
156
|
+
public async createNostrConnect(relays?: string[]) {
|
|
157
|
+
/*const relayList = relays
|
|
158
|
+
? relays
|
|
159
|
+
.split(",")
|
|
160
|
+
.map(r => r.trim().replace(/['"]/g, ""))
|
|
161
|
+
.filter(r => r.length > 0)
|
|
162
|
+
: [];*/
|
|
163
|
+
|
|
158
164
|
this.nostrConnectKey = generatePrivateKey();
|
|
159
165
|
this.nostrConnectSecret = Math.random().toString(36).substring(7);
|
|
160
166
|
|
|
@@ -166,37 +172,52 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
166
172
|
perms: encodeURIComponent(this.params.optionsModal.perms || ''),
|
|
167
173
|
};
|
|
168
174
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const relayParams = relays.map(r => `relay=${r}`).join('&');
|
|
172
|
-
return `nostrconnect://${pubkey}?image=${meta.icon}&url=${meta.url}&name=${meta.name}&perms=${meta.perms}&secret=${this.nostrConnectSecret}&${relayParams}`;
|
|
175
|
+
return `nostrconnect://${pubkey}?image=${meta.icon}&url=${meta.url}&name=${meta.name}&perms=${meta.perms}&secret=${this.nostrConnectSecret}${(relays || []).length > 0 ? (relays || []).map((r, i) => `&relay=${r}`) : ""}`;
|
|
173
176
|
}
|
|
174
177
|
|
|
175
178
|
public async getNostrConnectServices(): Promise<[string, ConnectionString[]]> {
|
|
176
|
-
const
|
|
177
|
-
const nostrconnect = await this.createNostrConnect(customRelays);
|
|
179
|
+
const nostrconnect = await this.createNostrConnect();
|
|
178
180
|
|
|
181
|
+
// copy defaults
|
|
179
182
|
const apps = NOSTRCONNECT_APPS.map(a => ({ ...a }));
|
|
183
|
+
// if (this.params.optionsModal.dev) {
|
|
184
|
+
// apps.push({
|
|
185
|
+
// name: 'Dev.Nsec.app',
|
|
186
|
+
// domain: 'new.nsec.app',
|
|
187
|
+
// canImport: true,
|
|
188
|
+
// img: 'https://new.nsec.app/assets/favicon.ico',
|
|
189
|
+
// link: 'https://dev.nsec.app/<nostrconnect>',
|
|
190
|
+
// relay: 'wss://relay.nsec.app/',
|
|
191
|
+
// });
|
|
192
|
+
// }
|
|
180
193
|
|
|
181
194
|
for (const a of apps) {
|
|
182
|
-
let relays =
|
|
195
|
+
let relays: string[] = [...DEFAULT_NOSTRCONNECT_RELAYS];
|
|
183
196
|
|
|
184
197
|
if (a.link.startsWith('https://')) {
|
|
185
|
-
|
|
198
|
+
const domain = a.domain || new URL(a.link).hostname;
|
|
186
199
|
try {
|
|
187
200
|
const info = await (await fetch(`https://${domain}/.well-known/nostr.json`)).json();
|
|
188
201
|
const pubkey = info.names['_'];
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
202
|
+
const appRelays = info.nip46?.[pubkey] as string[] | undefined;
|
|
203
|
+
|
|
204
|
+
if (Array.isArray(appRelays) && appRelays.length > 0) {
|
|
205
|
+
relays = appRelays;
|
|
192
206
|
}
|
|
193
|
-
|
|
207
|
+
|
|
208
|
+
a.iframeUrl = info.nip46?.iframe_url || '';
|
|
194
209
|
} catch (e) {
|
|
195
210
|
console.log('Bad app info', e, a);
|
|
196
211
|
}
|
|
197
212
|
}
|
|
198
213
|
|
|
199
|
-
const
|
|
214
|
+
const relayParams = relays
|
|
215
|
+
.map(r => r.replace(/['"]/g, ''))
|
|
216
|
+
.map(r => `&relay=${encodeURIComponent(r)}`)
|
|
217
|
+
.join('');
|
|
218
|
+
|
|
219
|
+
const nc = nostrconnect + relayParams;
|
|
220
|
+
|
|
200
221
|
if (a.iframeUrl) {
|
|
201
222
|
a.link = nc;
|
|
202
223
|
} else {
|
|
@@ -231,23 +252,27 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
231
252
|
}
|
|
232
253
|
|
|
233
254
|
public prepareImportUrl(url: string) {
|
|
255
|
+
// for OTP we choose interactive import
|
|
234
256
|
if (this.params.userInfo?.authMethod === 'otp') return url + '&import=true';
|
|
235
257
|
|
|
258
|
+
// for local we export our existing key
|
|
236
259
|
if (!this.localSigner || this.params.userInfo?.authMethod !== 'local') throw new Error('Most be local keys');
|
|
237
260
|
return url + '#import=' + nip19.nsecEncode(this.localSigner.privateKey!);
|
|
238
261
|
}
|
|
239
262
|
|
|
240
263
|
public async importAndConnect(cs: ConnectionString) {
|
|
241
|
-
const {
|
|
264
|
+
const { relays, domain, link, iframeUrl } = cs;
|
|
242
265
|
if (!domain) throw new Error('Domain required');
|
|
243
266
|
|
|
244
|
-
const
|
|
245
|
-
|
|
267
|
+
const info = await this.nostrConnect(relays, {
|
|
268
|
+
domain,
|
|
269
|
+
link,
|
|
270
|
+
importConnect: true,
|
|
271
|
+
iframeUrl,
|
|
272
|
+
});
|
|
246
273
|
|
|
247
274
|
await this.logout(true);
|
|
248
|
-
|
|
249
275
|
this.localSigner = null;
|
|
250
|
-
|
|
251
276
|
this.onAuth('login', info);
|
|
252
277
|
}
|
|
253
278
|
|
|
@@ -277,20 +302,25 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
277
302
|
public async createAccount(nip05: string) {
|
|
278
303
|
const [name, domain] = nip05.split('@');
|
|
279
304
|
|
|
305
|
+
// bunker's own url
|
|
280
306
|
const bunkerUrl = await getBunkerUrl(`_@${domain}`, this.params.optionsModal);
|
|
281
307
|
console.log("create account bunker's url", bunkerUrl);
|
|
282
308
|
|
|
309
|
+
// parse bunker url and generate local nsec
|
|
283
310
|
const info = bunkerUrlToInfo(bunkerUrl);
|
|
284
311
|
if (!info.signerPubkey) throw new Error('Bad bunker url');
|
|
285
312
|
|
|
286
313
|
const eventToAddAccount = Boolean(this.params.userInfo);
|
|
287
314
|
|
|
315
|
+
// init signer to talk to the bunker (not the user!)
|
|
288
316
|
await this.initSigner(info, { eventToAddAccount });
|
|
289
317
|
|
|
290
318
|
const userPubkey = await this.signer!.createAccount2({ bunkerPubkey: info.signerPubkey!, name, domain, perms: this.params.optionsModal.perms });
|
|
291
319
|
|
|
292
320
|
return {
|
|
293
|
-
bunkerUrl:
|
|
321
|
+
bunkerUrl:
|
|
322
|
+
`bunker://${userPubkey}?` +
|
|
323
|
+
(info.relays ?? []).map((r: string) => `relay=${encodeURIComponent(r)}`).join('&'),
|
|
294
324
|
sk: info.sk,
|
|
295
325
|
};
|
|
296
326
|
}
|
|
@@ -300,6 +330,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
300
330
|
this.signerErrCallback?.('cancelled');
|
|
301
331
|
this.localSigner = null;
|
|
302
332
|
|
|
333
|
+
// disconnect from signer relays
|
|
303
334
|
for (const r of this.ndk.pool.relays.keys()) {
|
|
304
335
|
this.ndk.pool.removeRelay(r);
|
|
305
336
|
}
|
|
@@ -308,8 +339,10 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
308
339
|
public async logout(keepSigner = false) {
|
|
309
340
|
if (!keepSigner) this.releaseSigner();
|
|
310
341
|
|
|
342
|
+
// move current to recent
|
|
311
343
|
localStorageRemoveCurrentAccount();
|
|
312
344
|
|
|
345
|
+
// notify everyone
|
|
313
346
|
this.onAuth('logout');
|
|
314
347
|
|
|
315
348
|
this.emit('updateAccounts');
|
|
@@ -334,15 +367,17 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
334
367
|
private onAuth(type: 'login' | 'signup' | 'logout', info: Info | null = null) {
|
|
335
368
|
if (type !== 'logout' && !info) throw new Error('No user info in onAuth');
|
|
336
369
|
|
|
370
|
+
// make sure we emulate logout first
|
|
337
371
|
if (info && this.params.userInfo && (info.pubkey !== this.params.userInfo.pubkey || info.authMethod !== this.params.userInfo.authMethod)) {
|
|
338
372
|
const event = new CustomEvent('nlAuth', { detail: { type: 'logout' } });
|
|
339
373
|
console.log('nostr-login auth', event.detail);
|
|
340
|
-
document.dispatchEvent(event)
|
|
374
|
+
document.dispatchEvent(event)
|
|
341
375
|
}
|
|
342
376
|
|
|
343
377
|
this.setUserInfo(info);
|
|
344
378
|
|
|
345
379
|
if (info) {
|
|
380
|
+
// async profile fetch
|
|
346
381
|
fetchProfile(info, this.profileNdk).then(p => {
|
|
347
382
|
if (this.params.userInfo !== info) return;
|
|
348
383
|
|
|
@@ -350,6 +385,10 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
350
385
|
...this.params.userInfo,
|
|
351
386
|
picture: p?.image || p?.picture,
|
|
352
387
|
name: p?.name || p?.displayName || p?.nip05 || nip19.npubEncode(info.pubkey),
|
|
388
|
+
// NOTE: do not overwrite info.nip05 with the one from profile!
|
|
389
|
+
// info.nip05 refers to nip46 provider,
|
|
390
|
+
// profile.nip05 is just a fancy name that user has chosen
|
|
391
|
+
// nip05: p?.nip05
|
|
353
392
|
};
|
|
354
393
|
|
|
355
394
|
this.setUserInfo(userInfo);
|
|
@@ -364,6 +403,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
364
403
|
};
|
|
365
404
|
|
|
366
405
|
if (type === 'logout') {
|
|
406
|
+
// reset
|
|
367
407
|
if (this.iframe) this.iframe.remove();
|
|
368
408
|
this.iframe = undefined;
|
|
369
409
|
} else {
|
|
@@ -400,10 +440,12 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
400
440
|
private async createIframe(iframeUrl?: string) {
|
|
401
441
|
if (!iframeUrl) return undefined;
|
|
402
442
|
|
|
443
|
+
// ensure iframe
|
|
403
444
|
const url = new URL(iframeUrl);
|
|
404
445
|
const domain = url.hostname;
|
|
405
446
|
let iframe: HTMLIFrameElement | undefined;
|
|
406
447
|
|
|
448
|
+
// one iframe per domain
|
|
407
449
|
const did = domain.replaceAll('.', '-');
|
|
408
450
|
const id = '__nostr-login-worker-iframe-' + did;
|
|
409
451
|
iframe = document.querySelector(`#${id}`) as HTMLIFrameElement;
|
|
@@ -414,25 +456,46 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
414
456
|
iframe.setAttribute('height', '0');
|
|
415
457
|
iframe.setAttribute('border', '0');
|
|
416
458
|
iframe.style.display = 'none';
|
|
459
|
+
// iframe.setAttribute('sandbox', 'allow-forms allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts');
|
|
417
460
|
iframe.id = id;
|
|
418
461
|
document.body.append(iframe);
|
|
419
462
|
}
|
|
420
463
|
|
|
464
|
+
// wait until loaded
|
|
421
465
|
iframe.setAttribute('src', iframeUrl);
|
|
422
466
|
|
|
467
|
+
// we start listening right now to avoid races
|
|
468
|
+
// with 'load' event below
|
|
423
469
|
const ready = new ReadyListener(['workerReady', 'workerError'], url.origin);
|
|
424
470
|
|
|
425
471
|
await new Promise(ok => {
|
|
426
472
|
iframe!.addEventListener('load', ok);
|
|
427
473
|
});
|
|
428
474
|
|
|
475
|
+
// now make sure the iframe is ready,
|
|
476
|
+
// timeout timer starts here
|
|
429
477
|
const r = await ready.wait();
|
|
430
478
|
|
|
479
|
+
// FIXME wait until the iframe is ready to accept requests,
|
|
480
|
+
// maybe it should send us some message?
|
|
481
|
+
|
|
431
482
|
console.log('nostr-login iframe ready', iframeUrl, r);
|
|
432
483
|
|
|
433
484
|
return { iframe, port: r[1] as MessagePort };
|
|
434
485
|
}
|
|
435
486
|
|
|
487
|
+
// private async getIframeUrl(domain?: string) {
|
|
488
|
+
// if (!domain) return '';
|
|
489
|
+
// try {
|
|
490
|
+
// const r = await fetch(`https://${domain}/.well-known/nostr.json`);
|
|
491
|
+
// const data = await r.json();
|
|
492
|
+
// return data.nip46?.iframe_url || '';
|
|
493
|
+
// } catch (e) {
|
|
494
|
+
// console.log('failed to fetch iframe url', e, domain);
|
|
495
|
+
// return '';
|
|
496
|
+
// }
|
|
497
|
+
// }
|
|
498
|
+
|
|
436
499
|
public async sendNeedAuth() {
|
|
437
500
|
const [nostrconnect] = await this.getNostrConnectServices();
|
|
438
501
|
const event = new CustomEvent('nlNeedAuth', { detail: { nostrconnect } });
|
|
@@ -445,19 +508,22 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
445
508
|
}
|
|
446
509
|
|
|
447
510
|
public async startAuth() {
|
|
448
|
-
console.log(
|
|
511
|
+
console.log("startAuth");
|
|
449
512
|
if (this.readyCallback) throw new Error('Already started');
|
|
450
513
|
|
|
514
|
+
// start the new promise
|
|
451
515
|
this.readyPromise = new Promise<void>(ok => (this.readyCallback = ok));
|
|
452
516
|
}
|
|
453
517
|
|
|
454
518
|
public async endAuth() {
|
|
455
519
|
console.log('endAuth', this.params.userInfo);
|
|
456
520
|
if (this.params.userInfo && this.params.userInfo.iframeUrl) {
|
|
521
|
+
// create iframe
|
|
457
522
|
const { iframe, port } = (await this.createIframe(this.params.userInfo.iframeUrl)) || {};
|
|
458
523
|
this.iframe = iframe;
|
|
459
524
|
if (!this.iframe || !port) return;
|
|
460
525
|
|
|
526
|
+
// assign iframe to RPC object
|
|
461
527
|
(this.signer!.rpc as IframeNostrRpc).setWorkerIframePort(port);
|
|
462
528
|
}
|
|
463
529
|
|
|
@@ -482,58 +548,85 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
482
548
|
}
|
|
483
549
|
|
|
484
550
|
public async initSigner(info: Info, { listen = false, connect = false, eventToAddAccount = false } = {}) {
|
|
551
|
+
// mutex
|
|
485
552
|
if (this.signerPromise) {
|
|
486
553
|
try {
|
|
487
554
|
await this.signerPromise;
|
|
488
|
-
} catch {}
|
|
555
|
+
} catch { }
|
|
489
556
|
}
|
|
490
557
|
|
|
558
|
+
// we remove support for iframe from nip05 and bunker-url methods,
|
|
559
|
+
// only nostrconnect flow will use it.
|
|
560
|
+
// info.iframeUrl = info.iframeUrl || (await this.getIframeUrl(info.domain));
|
|
491
561
|
console.log('initSigner info', info);
|
|
492
562
|
|
|
563
|
+
// start listening for the ready signal
|
|
493
564
|
const iframeOrigin = info.iframeUrl ? new URL(info.iframeUrl!).origin : undefined;
|
|
494
565
|
if (iframeOrigin) this.starterReady = new ReadyListener(['starterDone', 'starterError'], iframeOrigin);
|
|
495
566
|
|
|
567
|
+
// notify modals so they could show the starter iframe,
|
|
568
|
+
// FIXME shouldn't this come from nostrconnect service list?
|
|
496
569
|
this.emit('onIframeUrl', info.iframeUrl);
|
|
497
570
|
|
|
498
571
|
this.signerPromise = new Promise<void>(async (ok, err) => {
|
|
499
572
|
this.signerErrCallback = err;
|
|
500
573
|
try {
|
|
574
|
+
// pre-connect if we're creating the connection (listen|connect) or
|
|
575
|
+
// not iframe mode
|
|
501
576
|
if (info.relays && !info.iframeUrl) {
|
|
502
577
|
for (const r of info.relays) {
|
|
503
578
|
this.ndk.addExplicitRelay(r, undefined);
|
|
504
579
|
}
|
|
505
580
|
}
|
|
506
581
|
|
|
507
|
-
|
|
582
|
+
// wait until we connect, otherwise
|
|
583
|
+
// signer won't start properly
|
|
584
|
+
await this.ndk.connect(CONNECT_TIMEOUT);
|
|
508
585
|
|
|
586
|
+
// create and prepare the signer
|
|
509
587
|
const localSigner = new PrivateKeySigner(info.sk!);
|
|
510
588
|
this.signer = new Nip46Signer(this.ndk, localSigner, info.signerPubkey!, iframeOrigin);
|
|
511
589
|
|
|
590
|
+
// we should notify the banner the same way as
|
|
591
|
+
// the onAuthUrl does
|
|
512
592
|
this.signer.on(`iframeRestart`, async () => {
|
|
513
593
|
const iframeUrl = info.iframeUrl + (info.iframeUrl!.includes('?') ? '&' : '?') + 'pubkey=' + info.pubkey + '&rebind=' + localSigner.pubkey;
|
|
514
594
|
this.emit('iframeRestart', { pubkey: info.pubkey, iframeUrl });
|
|
515
595
|
});
|
|
516
596
|
|
|
597
|
+
// OAuth flow
|
|
598
|
+
// if (!listen) {
|
|
517
599
|
this.signer.on('authUrl', (url: string) => {
|
|
518
600
|
console.log('nostr login auth url', url);
|
|
519
601
|
|
|
602
|
+
// notify our UI
|
|
520
603
|
this.emit('onAuthUrl', { url, iframeUrl: info.iframeUrl, eventToAddAccount });
|
|
521
604
|
});
|
|
605
|
+
// }
|
|
522
606
|
|
|
523
607
|
if (listen) {
|
|
608
|
+
// nostrconnect: flow
|
|
609
|
+
// wait for the incoming message from signer
|
|
524
610
|
await this.listen(info);
|
|
525
611
|
} else if (connect) {
|
|
612
|
+
// bunker: flow
|
|
613
|
+
// send 'connect' message to signer
|
|
526
614
|
await this.connect(info, this.params.optionsModal.perms);
|
|
527
615
|
} else {
|
|
616
|
+
// provide saved pubkey as a hint
|
|
528
617
|
await this.signer!.initUserPubkey(info.pubkey);
|
|
529
618
|
}
|
|
530
619
|
|
|
620
|
+
// ensure, we're using it in callbacks above
|
|
621
|
+
// and expect info to be valid after this call
|
|
531
622
|
info.pubkey = this.signer!.userPubkey;
|
|
623
|
+
// learned after nostrconnect flow
|
|
532
624
|
info.signerPubkey = this.signer!.remotePubkey;
|
|
533
625
|
|
|
534
626
|
ok();
|
|
535
627
|
} catch (e) {
|
|
536
628
|
console.log('initSigner failure', e);
|
|
629
|
+
// make sure signer isn't set
|
|
537
630
|
this.signer = null;
|
|
538
631
|
err(e);
|
|
539
632
|
}
|
|
@@ -556,35 +649,50 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
556
649
|
if (domain) info.domain = domain;
|
|
557
650
|
if (iframeUrl) info.iframeUrl = iframeUrl;
|
|
558
651
|
|
|
559
|
-
|
|
652
|
+
// console.log('nostr login auth info', info);
|
|
653
|
+
if (!info.signerPubkey || !info.sk || !info.relays || info.relays.length === 0) {
|
|
560
654
|
throw new Error(`Bad bunker url ${bunkerUrl}`);
|
|
561
655
|
}
|
|
562
656
|
|
|
563
657
|
const eventToAddAccount = Boolean(this.params.userInfo);
|
|
564
658
|
console.log('authNip46', type, info);
|
|
565
659
|
|
|
660
|
+
// updates the info
|
|
566
661
|
await this.initSigner(info, { connect: true, eventToAddAccount });
|
|
567
662
|
|
|
663
|
+
// callback
|
|
568
664
|
this.onAuth(type, info);
|
|
569
665
|
} catch (e) {
|
|
570
666
|
console.log('nostr login auth failed', e);
|
|
667
|
+
// make ure it's closed
|
|
668
|
+
// this.popupManager.closePopup();
|
|
571
669
|
throw e;
|
|
572
670
|
}
|
|
573
671
|
}
|
|
574
672
|
|
|
575
|
-
public async signEvent(
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
673
|
+
public async signEvent(event: any) {
|
|
674
|
+
const timeoutMs = 20000;
|
|
675
|
+
|
|
676
|
+
const signPromise = (async () => {
|
|
677
|
+
if (this.localSigner) {
|
|
678
|
+
event.pubkey = getPublicKey(this.localSigner.privateKey!);
|
|
679
|
+
event.id = getEventHash(event);
|
|
680
|
+
event.sig = await this.localSigner.sign(event);
|
|
681
|
+
} else {
|
|
682
|
+
event.pubkey = this.signer?.remotePubkey;
|
|
683
|
+
event.id = getEventHash(event);
|
|
684
|
+
event.sig = await this.signer?.sign(event);
|
|
685
|
+
}
|
|
686
|
+
return event;
|
|
687
|
+
})();
|
|
688
|
+
|
|
689
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
690
|
+
setTimeout(() => reject(new Error('Sign timeout')), timeoutMs);
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
const result = await Promise.race([signPromise, timeoutPromise]);
|
|
694
|
+
console.log('signed', { event: result });
|
|
695
|
+
return result;
|
|
588
696
|
}
|
|
589
697
|
|
|
590
698
|
private async codec_call(method: string, pubkey: string, param: string) {
|
|
@@ -611,6 +719,10 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
611
719
|
if (this.localSigner) {
|
|
612
720
|
return this.localSigner.decrypt(new NDKUser({ pubkey }), ciphertext);
|
|
613
721
|
} else {
|
|
722
|
+
// decrypt is broken in ndk v2.3.1, and latest
|
|
723
|
+
// ndk v2.8.1 doesn't allow to override connect easily,
|
|
724
|
+
// so we reimplement and fix decrypt here as a temporary fix
|
|
725
|
+
|
|
614
726
|
return this.codec_call('nip04_decrypt', pubkey, ciphertext);
|
|
615
727
|
}
|
|
616
728
|
}
|
|
@@ -619,6 +731,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
619
731
|
if (this.localSigner) {
|
|
620
732
|
return this.nip44Codec.encrypt(this.localSigner.privateKey!, pubkey, plaintext);
|
|
621
733
|
} else {
|
|
734
|
+
// no support of nip44 in ndk yet
|
|
622
735
|
return this.codec_call('nip44_encrypt', pubkey, plaintext);
|
|
623
736
|
}
|
|
624
737
|
}
|
|
@@ -627,6 +740,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
627
740
|
if (this.localSigner) {
|
|
628
741
|
return this.nip44Codec.decrypt(this.localSigner.privateKey!, pubkey, ciphertext);
|
|
629
742
|
} else {
|
|
743
|
+
// no support of nip44 in ndk yet
|
|
630
744
|
return this.codec_call('nip44_decrypt', pubkey, ciphertext);
|
|
631
745
|
}
|
|
632
746
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NostrLoginOptions, TypeBanner } from '../types';
|
|
2
2
|
import { NostrParams } from '.';
|
|
3
|
-
import { Info } from 'nostr-login-components';
|
|
3
|
+
import { Info } from 'nostr-login-components/dist/types/types';
|
|
4
4
|
import { EventEmitter } from 'tseep';
|
|
5
5
|
import { getDarkMode } from '../utils';
|
|
6
6
|
import { ReadyListener } from './Nip46';
|