@konemono/nostr-login 1.7.11
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/.prettierrc.json +13 -0
- package/README.md +167 -0
- package/dist/const/index.d.ts +1 -0
- package/dist/iife-module.d.ts +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.esm.js +18 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/modules/AuthNostrService.d.ts +84 -0
- package/dist/modules/BannerManager.d.ts +20 -0
- package/dist/modules/ModalManager.d.ts +25 -0
- package/dist/modules/Nip46.d.ts +56 -0
- package/dist/modules/Nostr.d.ts +34 -0
- package/dist/modules/NostrExtensionService.d.ts +17 -0
- package/dist/modules/NostrParams.d.ts +8 -0
- package/dist/modules/Popup.d.ts +7 -0
- package/dist/modules/ProcessManager.d.ts +10 -0
- package/dist/modules/Signer.d.ts +9 -0
- package/dist/modules/index.d.ts +8 -0
- package/dist/types.d.ts +72 -0
- package/dist/unpkg.js +17 -0
- package/dist/utils/index.d.ts +27 -0
- package/dist/utils/nip44.d.ts +9 -0
- package/index.html +30 -0
- package/package.json +28 -0
- package/rollup.config.js +55 -0
- package/src/const/index.ts +1 -0
- package/src/iife-module.ts +81 -0
- package/src/index.ts +347 -0
- package/src/modules/AuthNostrService.ts +756 -0
- package/src/modules/BannerManager.ts +146 -0
- package/src/modules/ModalManager.ts +635 -0
- package/src/modules/Nip46.ts +441 -0
- package/src/modules/Nostr.ts +107 -0
- package/src/modules/NostrExtensionService.ts +99 -0
- package/src/modules/NostrParams.ts +18 -0
- package/src/modules/Popup.ts +27 -0
- package/src/modules/ProcessManager.ts +67 -0
- package/src/modules/Signer.ts +25 -0
- package/src/modules/index.ts +8 -0
- package/src/types.ts +124 -0
- package/src/utils/index.ts +326 -0
- package/src/utils/nip44.ts +185 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
import { NostrLoginOptions, StartScreens, TypeModal } from '../types';
|
|
2
|
+
import { checkNip05, getBunkerUrl, getDarkMode, localStorageRemoveRecent, localStorageSetItem, prepareSignupRelays } from '../utils';
|
|
3
|
+
import { AuthNostrService, NostrExtensionService, NostrParams } from '.';
|
|
4
|
+
import { EventEmitter } from 'tseep';
|
|
5
|
+
import { ConnectionString, Info, RecentType } from 'nostr-login-components';
|
|
6
|
+
import { nip19 } from 'nostr-tools';
|
|
7
|
+
import { setDarkMode } from '..';
|
|
8
|
+
|
|
9
|
+
class ModalManager extends EventEmitter {
|
|
10
|
+
private modal: TypeModal | null = null;
|
|
11
|
+
private params: NostrParams;
|
|
12
|
+
private extensionService: NostrExtensionService;
|
|
13
|
+
private authNostrService: AuthNostrService;
|
|
14
|
+
private launcherPromise?: Promise<void>;
|
|
15
|
+
private accounts: Info[] = [];
|
|
16
|
+
private recents: RecentType[] = [];
|
|
17
|
+
private opt?: NostrLoginOptions;
|
|
18
|
+
|
|
19
|
+
constructor(params: NostrParams, authNostrService: AuthNostrService, extensionManager: NostrExtensionService) {
|
|
20
|
+
super();
|
|
21
|
+
this.params = params;
|
|
22
|
+
this.extensionService = extensionManager;
|
|
23
|
+
this.authNostrService = authNostrService;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public async waitReady() {
|
|
27
|
+
if (this.launcherPromise) {
|
|
28
|
+
try {
|
|
29
|
+
await this.launcherPromise;
|
|
30
|
+
} catch {}
|
|
31
|
+
this.launcherPromise = undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public async launch(opt: NostrLoginOptions) {
|
|
36
|
+
console.log('nostr-login launch', opt);
|
|
37
|
+
// mutex
|
|
38
|
+
if (this.launcherPromise) await this.waitReady();
|
|
39
|
+
|
|
40
|
+
// hmm?!
|
|
41
|
+
if (this.authNostrService.isAuthing()) this.authNostrService.resetAuth();
|
|
42
|
+
|
|
43
|
+
this.opt = opt;
|
|
44
|
+
|
|
45
|
+
const dialog = document.createElement('dialog');
|
|
46
|
+
this.modal = document.createElement('nl-auth');
|
|
47
|
+
this.modal.accounts = this.accounts;
|
|
48
|
+
this.modal.recents = this.recents;
|
|
49
|
+
|
|
50
|
+
this.modal.setAttribute('dark-mode', String(getDarkMode(opt)));
|
|
51
|
+
|
|
52
|
+
if (opt.theme) {
|
|
53
|
+
this.modal.setAttribute('theme', opt.theme);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (opt.startScreen) {
|
|
57
|
+
this.modal.setAttribute('start-screen', opt.startScreen);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (opt.bunkers) {
|
|
61
|
+
this.modal.setAttribute('bunkers', opt.bunkers);
|
|
62
|
+
} else {
|
|
63
|
+
let bunkers = 'nsec.app,highlighter.com';
|
|
64
|
+
// if (opt.dev) bunkers += ',new.nsec.app';
|
|
65
|
+
this.modal.setAttribute('bunkers', bunkers);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (opt.methods !== undefined) {
|
|
69
|
+
this.modal.authMethods = opt.methods;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (opt.localSignup !== undefined) {
|
|
73
|
+
this.modal.localSignup = opt.localSignup;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (opt.signupNstart !== undefined) {
|
|
77
|
+
this.modal.signupNjump = opt.signupNstart;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (opt.title) {
|
|
81
|
+
this.modal.welcomeTitle = opt.title;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (opt.description) {
|
|
85
|
+
this.modal.welcomeDescription = opt.description;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.modal.hasExtension = this.extensionService.hasExtension();
|
|
89
|
+
this.modal.hasOTP = !!opt.otpRequestUrl && !!opt.otpReplyUrl;
|
|
90
|
+
|
|
91
|
+
this.modal.isLoadingExtension = false;
|
|
92
|
+
this.modal.isLoading = false;
|
|
93
|
+
|
|
94
|
+
[this.modal.connectionString, this.modal.connectionStringServices] = await this.authNostrService.getNostrConnectServices();
|
|
95
|
+
|
|
96
|
+
dialog.appendChild(this.modal);
|
|
97
|
+
document.body.appendChild(dialog);
|
|
98
|
+
|
|
99
|
+
let otpPubkey = '';
|
|
100
|
+
|
|
101
|
+
this.launcherPromise = new Promise<void>((ok, err) => {
|
|
102
|
+
dialog.addEventListener('close', () => {
|
|
103
|
+
// noop if already resolved
|
|
104
|
+
err(new Error('Closed'));
|
|
105
|
+
|
|
106
|
+
this.authNostrService.resetAuth();
|
|
107
|
+
|
|
108
|
+
if (this.modal) {
|
|
109
|
+
// it's reset on modal creation
|
|
110
|
+
// // reset state
|
|
111
|
+
// this.modal.isLoading = false;
|
|
112
|
+
// this.modal.authUrl = '';
|
|
113
|
+
// this.modal.iframeUrl = '';
|
|
114
|
+
// this.modal.error = '';
|
|
115
|
+
// this.modal.isLoadingExtension = false;
|
|
116
|
+
|
|
117
|
+
// drop it
|
|
118
|
+
// @ts-ignore
|
|
119
|
+
document.body.removeChild(this.modal.parentNode);
|
|
120
|
+
this.modal = null;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const done = async (ok: () => void) => {
|
|
125
|
+
if (this.modal) this.modal.isLoading = false;
|
|
126
|
+
await this.authNostrService.endAuth();
|
|
127
|
+
dialog.close();
|
|
128
|
+
this.modal = null;
|
|
129
|
+
ok();
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const exec = async (
|
|
133
|
+
body: () => Promise<void>,
|
|
134
|
+
options?: {
|
|
135
|
+
start?: boolean;
|
|
136
|
+
end?: boolean;
|
|
137
|
+
},
|
|
138
|
+
) => {
|
|
139
|
+
if (this.modal) {
|
|
140
|
+
this.modal.isLoading = true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
if (!options || options.start) await this.authNostrService.startAuth();
|
|
145
|
+
await body();
|
|
146
|
+
if (!options || options.end) await done(ok);
|
|
147
|
+
} catch (e: any) {
|
|
148
|
+
console.log('error', e);
|
|
149
|
+
if (this.modal) {
|
|
150
|
+
this.modal.isLoading = false;
|
|
151
|
+
this.modal.authUrl = '';
|
|
152
|
+
this.modal.iframeUrl = '';
|
|
153
|
+
if (e !== 'cancelled') this.modal.error = e.toString();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const login = async (name: string, domain?: string) => {
|
|
159
|
+
await exec(async () => {
|
|
160
|
+
// convert name to bunker url
|
|
161
|
+
const bunkerUrl = await getBunkerUrl(name, this.params.optionsModal);
|
|
162
|
+
|
|
163
|
+
// connect to bunker by url
|
|
164
|
+
await this.authNostrService.authNip46('login', { name, bunkerUrl, domain });
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const signup = async (name: string) => {
|
|
169
|
+
await exec(async () => {
|
|
170
|
+
// create acc on service and get bunker url
|
|
171
|
+
const { bunkerUrl, sk } = await this.authNostrService.createAccount(name);
|
|
172
|
+
|
|
173
|
+
// connect to bunker by url
|
|
174
|
+
await this.authNostrService.authNip46('signup', { name, bunkerUrl, sk });
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const exportKeys = async () => {
|
|
179
|
+
try {
|
|
180
|
+
await navigator.clipboard.writeText(this.authNostrService.exportKeys());
|
|
181
|
+
localStorageSetItem('backupKey', 'true');
|
|
182
|
+
} catch (err) {
|
|
183
|
+
console.error('Failed to copy to clipboard: ', err);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const importKeys = async (cs: ConnectionString) => {
|
|
188
|
+
await exec(async () => {
|
|
189
|
+
const { iframeUrl } = cs;
|
|
190
|
+
cs.link = this.authNostrService.prepareImportUrl(cs.link);
|
|
191
|
+
|
|
192
|
+
if (this.modal && iframeUrl) {
|
|
193
|
+
// we pass the link down to iframe so it could open it
|
|
194
|
+
this.modal.authUrl = cs.link;
|
|
195
|
+
this.modal.iframeUrl = iframeUrl;
|
|
196
|
+
this.modal.isLoading = false;
|
|
197
|
+
console.log('nostrconnect authUrl', this.modal.authUrl, this.modal.iframeUrl);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
await this.authNostrService.importAndConnect(cs);
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const nostrConnect = async (cs?: ConnectionString) => {
|
|
205
|
+
await exec(async () => {
|
|
206
|
+
const { relay, domain, link, iframeUrl } = cs || {};
|
|
207
|
+
console.log('nostrConnect', cs, relay, domain, link, iframeUrl);
|
|
208
|
+
|
|
209
|
+
if (this.modal) {
|
|
210
|
+
if (iframeUrl) {
|
|
211
|
+
// we pass the link down to iframe so it could open it
|
|
212
|
+
this.modal.authUrl = link;
|
|
213
|
+
this.modal.iframeUrl = iframeUrl;
|
|
214
|
+
this.modal.isLoading = false;
|
|
215
|
+
console.log('nostrconnect authUrl', this.modal.authUrl, this.modal.iframeUrl);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!cs) this.modal.isLoading = false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
await this.authNostrService.nostrConnect(relay, { domain, link, iframeUrl });
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const localSignup = async (name?: string) => {
|
|
226
|
+
await exec(async () => {
|
|
227
|
+
if (!name) throw new Error('Please enter some nickname');
|
|
228
|
+
await this.authNostrService.localSignup(name);
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const signupNjump = async () => {
|
|
233
|
+
await exec(async () => {
|
|
234
|
+
const self = new URL(window.location.href);
|
|
235
|
+
const name =
|
|
236
|
+
self.hostname
|
|
237
|
+
.toLocaleLowerCase()
|
|
238
|
+
.replace(/^www\./i, '')
|
|
239
|
+
.charAt(0)
|
|
240
|
+
.toUpperCase() + self.hostname.slice(1);
|
|
241
|
+
const relays = prepareSignupRelays(this.params.optionsModal.signupRelays);
|
|
242
|
+
// const url = `https://start.njump.me/?an=${name}&at=popup&ac=${window.location.href}&s=${this.opt!.followNpubs || ''}&arr=${relays}&awr=${relays}`;
|
|
243
|
+
// console.log('njump url', url);
|
|
244
|
+
|
|
245
|
+
this.modal!.njumpIframe = `
|
|
246
|
+
<html><body>
|
|
247
|
+
<script src='https://start.njump.me/modal.js'></script>
|
|
248
|
+
<script>
|
|
249
|
+
new NstartModal({
|
|
250
|
+
baseUrl: 'https://start.njump.me',
|
|
251
|
+
// Required parameters
|
|
252
|
+
an: '${name}',
|
|
253
|
+
// Optional parameters
|
|
254
|
+
s: [${this.opt!.followNpubs ? `'${this.opt!.followNpubs}'` : ''}],
|
|
255
|
+
afb: false, // forceBunker
|
|
256
|
+
asb: false, // skipBunker
|
|
257
|
+
aan: false, // avoidNsec
|
|
258
|
+
aac: true, // avoidNcryptsec
|
|
259
|
+
ahc: true, // hide close button
|
|
260
|
+
arr: ${JSON.stringify(relays)}, //readRelays
|
|
261
|
+
awr: ${JSON.stringify(relays)}, //writeRelays
|
|
262
|
+
// Callbacks
|
|
263
|
+
onComplete: (result) => {
|
|
264
|
+
console.log('Login token:', result.nostrLogin);
|
|
265
|
+
window.parent.location.href='${window.location.href}#nostr-login='+result.nostrLogin;
|
|
266
|
+
},
|
|
267
|
+
onCancel: () => {
|
|
268
|
+
window.parent.location.href='${window.location.href}#nostr-login=null';
|
|
269
|
+
},
|
|
270
|
+
}).open();
|
|
271
|
+
</script>
|
|
272
|
+
</body></html>
|
|
273
|
+
`.replaceAll('&', '&'); // needed?
|
|
274
|
+
|
|
275
|
+
return new Promise((ok, err) => {
|
|
276
|
+
const process = async (nsecOrBunker: string) => {
|
|
277
|
+
// process the returned value
|
|
278
|
+
console.log('nsecOrBunker', nsecOrBunker);
|
|
279
|
+
if (nsecOrBunker.startsWith('nsec1')) {
|
|
280
|
+
let decoded;
|
|
281
|
+
try {
|
|
282
|
+
decoded = nip19.decode(nsecOrBunker);
|
|
283
|
+
} catch (e) {
|
|
284
|
+
throw new Error('Bad nsec value');
|
|
285
|
+
}
|
|
286
|
+
if (decoded.type !== 'nsec') throw new Error('Bad bech32 type');
|
|
287
|
+
await this.authNostrService.localSignup('', decoded.data);
|
|
288
|
+
ok();
|
|
289
|
+
} else if (nsecOrBunker.startsWith('bunker:')) {
|
|
290
|
+
await this.authNostrService.authNip46('login', { name: '', bunkerUrl: nsecOrBunker });
|
|
291
|
+
ok();
|
|
292
|
+
} else if (nsecOrBunker === 'null') {
|
|
293
|
+
err('Cancelled');
|
|
294
|
+
} else {
|
|
295
|
+
err('Unknown return value');
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const onOpen = async () => {
|
|
300
|
+
if (window.location.hash.startsWith('#nostr-login=')) {
|
|
301
|
+
const nsecOrBunker = window.location.hash.split('#nostr-login=')[1];
|
|
302
|
+
|
|
303
|
+
// clear hash from history
|
|
304
|
+
const url = new URL(window.location.toString());
|
|
305
|
+
url.hash = '';
|
|
306
|
+
window.history.replaceState({}, '', url.toString());
|
|
307
|
+
|
|
308
|
+
process(nsecOrBunker);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// // use random 'target' to make sure window.opener is
|
|
313
|
+
// // accessible to the popup
|
|
314
|
+
// window.open(url, '' + Date.now(), 'popup=true,width=600,height=950');
|
|
315
|
+
window.addEventListener('hashchange', onOpen);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
if (!this.modal) throw new Error('WTH?');
|
|
321
|
+
|
|
322
|
+
this.modal.addEventListener('handleContinue', () => {
|
|
323
|
+
if (this.modal) {
|
|
324
|
+
this.modal.isLoading = true;
|
|
325
|
+
this.emit('onAuthUrlClick', this.modal.authUrl);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
this.modal.addEventListener('nlLogin', (event: any) => {
|
|
330
|
+
login(event.detail);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
this.modal.addEventListener('nlSignup', (event: any) => {
|
|
334
|
+
signup(event.detail);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
this.modal.addEventListener('nlLocalSignup', (event: any) => {
|
|
338
|
+
localSignup(event.detail);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
this.modal.addEventListener('nlSignupNjump', (event: any) => {
|
|
342
|
+
signupNjump();
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
this.modal.addEventListener('nlImportAccount', (event: any) => {
|
|
346
|
+
importKeys(event.detail);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
this.modal.addEventListener('nlExportKeys', (event: any) => {
|
|
350
|
+
exportKeys();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
this.modal.addEventListener('handleLogoutBanner', () => {
|
|
354
|
+
this.emit('onLogoutBanner');
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
this.modal.addEventListener('nlNostrConnect', (event: any) => {
|
|
358
|
+
nostrConnect(event.detail);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
this.modal.addEventListener('nlNostrConnectDefault', () => {
|
|
362
|
+
// dedup the calls
|
|
363
|
+
if (!this.authNostrService.isAuthing()) nostrConnect();
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
this.modal.addEventListener('nlNostrConnectDefaultCancel', () => {
|
|
367
|
+
console.log('nlNostrConnectDefaultCancel');
|
|
368
|
+
this.authNostrService.cancelNostrConnect();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
this.modal.addEventListener('nlSwitchAccount', (event: any) => {
|
|
372
|
+
const eventInfo: Info = event.detail as Info;
|
|
373
|
+
|
|
374
|
+
this.emit('onSwitchAccount', eventInfo);
|
|
375
|
+
|
|
376
|
+
// wait a bit, if dialog closes before
|
|
377
|
+
// switching finishes then launched promise rejects
|
|
378
|
+
|
|
379
|
+
// FIXME this calls resetAuth which then prevents
|
|
380
|
+
// endAuth from getting properly called. 300 is not
|
|
381
|
+
// enough to init iframe, so there should be a
|
|
382
|
+
// feedback from switchAccount here
|
|
383
|
+
setTimeout(() => dialog.close(), 300);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
this.modal.addEventListener('nlLoginRecentAccount', async (event: any) => {
|
|
387
|
+
const userInfo: Info = event.detail as Info;
|
|
388
|
+
|
|
389
|
+
if (userInfo.authMethod === 'readOnly') {
|
|
390
|
+
this.authNostrService.setReadOnly(userInfo.pubkey);
|
|
391
|
+
dialog.close();
|
|
392
|
+
} else if (userInfo.authMethod === 'otp') {
|
|
393
|
+
try {
|
|
394
|
+
this.modal!.dispatchEvent(
|
|
395
|
+
new CustomEvent('nlLoginOTPUser', {
|
|
396
|
+
detail: userInfo.nip05 || userInfo.pubkey,
|
|
397
|
+
}),
|
|
398
|
+
);
|
|
399
|
+
} catch (e) {
|
|
400
|
+
console.error(e);
|
|
401
|
+
}
|
|
402
|
+
} else if (userInfo.authMethod === 'extension') {
|
|
403
|
+
await this.extensionService.trySetExtensionForPubkey(userInfo.pubkey);
|
|
404
|
+
dialog.close();
|
|
405
|
+
} else {
|
|
406
|
+
const input = userInfo.bunkerUrl || userInfo.nip05;
|
|
407
|
+
if (!input) throw new Error('Bad connect info');
|
|
408
|
+
login(input, userInfo.domain);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
this.modal.addEventListener('nlRemoveRecent', (event: any) => {
|
|
413
|
+
localStorageRemoveRecent(event.detail as RecentType);
|
|
414
|
+
this.emit('updateAccounts');
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const nameToPubkey = async (nameNpub: string) => {
|
|
418
|
+
let pubkey = '';
|
|
419
|
+
if (nameNpub.includes('@')) {
|
|
420
|
+
const { error, pubkey: nip05pubkey } = await checkNip05(nameNpub);
|
|
421
|
+
if (nip05pubkey) pubkey = nip05pubkey;
|
|
422
|
+
else throw new Error(error);
|
|
423
|
+
} else if (nameNpub.startsWith('npub')) {
|
|
424
|
+
const { type, data } = nip19.decode(nameNpub);
|
|
425
|
+
if (type === 'npub') pubkey = data as string;
|
|
426
|
+
else throw new Error('Bad npub');
|
|
427
|
+
} else if (nameNpub.trim().length === 64) {
|
|
428
|
+
pubkey = nameNpub.trim();
|
|
429
|
+
nip19.npubEncode(pubkey); // check
|
|
430
|
+
}
|
|
431
|
+
return pubkey;
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
this.modal.addEventListener('nlLoginReadOnly', async (event: any) => {
|
|
435
|
+
await exec(async () => {
|
|
436
|
+
const nameNpub = event.detail;
|
|
437
|
+
const pubkey = await nameToPubkey(nameNpub);
|
|
438
|
+
this.authNostrService.setReadOnly(pubkey);
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
this.modal.addEventListener('nlLoginExtension', async () => {
|
|
443
|
+
if (!this.extensionService.hasExtension()) {
|
|
444
|
+
throw new Error('No extension');
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
await exec(async () => {
|
|
448
|
+
if (!this.modal) return;
|
|
449
|
+
this.modal.isLoadingExtension = true;
|
|
450
|
+
await this.extensionService.setExtension();
|
|
451
|
+
this.modal.isLoadingExtension = false;
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
this.modal.addEventListener('nlLoginOTPUser', async (event: any) => {
|
|
456
|
+
await exec(
|
|
457
|
+
async () => {
|
|
458
|
+
if (!this.modal) return;
|
|
459
|
+
|
|
460
|
+
const nameNpub = event.detail;
|
|
461
|
+
const pubkey = await nameToPubkey(nameNpub);
|
|
462
|
+
const url = this.opt!.otpRequestUrl! + (this.opt!.otpRequestUrl!.includes('?') ? '&' : '?') + 'pubkey=' + pubkey;
|
|
463
|
+
const r = await fetch(url);
|
|
464
|
+
if (r.status !== 200) {
|
|
465
|
+
console.warn('nostr-login: bad otp reply', r);
|
|
466
|
+
throw new Error('Failed to send DM');
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// switch to 'enter code' mode
|
|
470
|
+
this.modal.isOTP = true;
|
|
471
|
+
|
|
472
|
+
// remember for code handler below
|
|
473
|
+
otpPubkey = pubkey;
|
|
474
|
+
|
|
475
|
+
// spinner off
|
|
476
|
+
this.modal.isLoading = false;
|
|
477
|
+
},
|
|
478
|
+
{ start: true },
|
|
479
|
+
);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
this.modal.addEventListener('nlLoginOTPCode', async (event: any) => {
|
|
483
|
+
await exec(
|
|
484
|
+
async () => {
|
|
485
|
+
if (!this.modal) return;
|
|
486
|
+
const code = event.detail;
|
|
487
|
+
const url = this.opt!.otpReplyUrl! + (this.opt!.otpRequestUrl!.includes('?') ? '&' : '?') + 'pubkey=' + otpPubkey + '&code=' + code;
|
|
488
|
+
const r = await fetch(url);
|
|
489
|
+
if (r.status !== 200) {
|
|
490
|
+
console.warn('nostr-login: bad otp reply', r);
|
|
491
|
+
throw new Error('Invalid code');
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const data = await r.text();
|
|
495
|
+
this.authNostrService.setOTP(otpPubkey, data);
|
|
496
|
+
|
|
497
|
+
this.modal.isOTP = false;
|
|
498
|
+
},
|
|
499
|
+
{ end: true },
|
|
500
|
+
);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
this.modal.addEventListener('nlCheckSignup', async (event: any) => {
|
|
504
|
+
const { available, taken, error } = await checkNip05(event.detail);
|
|
505
|
+
if (this.modal) {
|
|
506
|
+
this.modal.error = String(error);
|
|
507
|
+
|
|
508
|
+
if (!error && taken) {
|
|
509
|
+
this.modal.error = 'Already taken';
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
this.modal.signupNameIsAvailable = available;
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
this.modal.addEventListener('nlCheckLogin', async (event: any) => {
|
|
517
|
+
const { available, taken, error } = await checkNip05(event.detail);
|
|
518
|
+
if (this.modal) {
|
|
519
|
+
this.modal.error = String(error);
|
|
520
|
+
if (available) {
|
|
521
|
+
this.modal.error = 'Name not found';
|
|
522
|
+
}
|
|
523
|
+
this.modal.loginIsGood = taken;
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
const cancel = () => {
|
|
528
|
+
if (this.modal) {
|
|
529
|
+
this.modal.isLoading = false;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// this.authNostrService.cancelListenNostrConnect();
|
|
533
|
+
|
|
534
|
+
dialog.close();
|
|
535
|
+
err(new Error('Cancelled'));
|
|
536
|
+
};
|
|
537
|
+
this.modal.addEventListener('stopFetchHandler', cancel);
|
|
538
|
+
this.modal.addEventListener('nlCloseModal', cancel);
|
|
539
|
+
|
|
540
|
+
this.modal.addEventListener('nlChangeDarkMode', (event: any) => {
|
|
541
|
+
setDarkMode(event.detail);
|
|
542
|
+
document.dispatchEvent(new CustomEvent('nlDarkMode', { detail: event.detail }));
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
this.on('onIframeAuthUrlCallEnd', () => {
|
|
546
|
+
dialog.close();
|
|
547
|
+
this.modal = null;
|
|
548
|
+
ok();
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
dialog.showModal();
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
return this.launcherPromise;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
public async showIframeUrl(url: string) {
|
|
558
|
+
// make sure we consume the previous promise,
|
|
559
|
+
// otherwise launch will start await-ing
|
|
560
|
+
// before modal is created and setting iframeUrl will fail
|
|
561
|
+
await this.waitReady();
|
|
562
|
+
|
|
563
|
+
this.launch({
|
|
564
|
+
startScreen: 'iframe' as StartScreens,
|
|
565
|
+
}).catch(() => console.log('closed auth iframe'));
|
|
566
|
+
|
|
567
|
+
this.modal!.authUrl = url;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
public connectModals(defaultOpt: NostrLoginOptions) {
|
|
571
|
+
const initialModals = async (opt: NostrLoginOptions) => {
|
|
572
|
+
await this.launch(opt);
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const nlElements = document.getElementsByTagName('nl-button');
|
|
576
|
+
|
|
577
|
+
for (let i = 0; i < nlElements.length; i++) {
|
|
578
|
+
const theme = nlElements[i].getAttribute('nl-theme');
|
|
579
|
+
const startScreen = nlElements[i].getAttribute('start-screen');
|
|
580
|
+
|
|
581
|
+
const elementOpt = {
|
|
582
|
+
...defaultOpt,
|
|
583
|
+
};
|
|
584
|
+
if (theme) elementOpt.theme = theme;
|
|
585
|
+
|
|
586
|
+
switch (startScreen as StartScreens) {
|
|
587
|
+
case 'login':
|
|
588
|
+
case 'login-bunker-url':
|
|
589
|
+
case 'login-read-only':
|
|
590
|
+
case 'signup':
|
|
591
|
+
case 'switch-account':
|
|
592
|
+
case 'welcome':
|
|
593
|
+
elementOpt.startScreen = startScreen as StartScreens;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
nlElements[i].addEventListener('click', function () {
|
|
597
|
+
initialModals(elementOpt);
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
public onAuthUrl(url: string) {
|
|
603
|
+
if (this.modal) {
|
|
604
|
+
this.modal.authUrl = url;
|
|
605
|
+
this.modal.isLoading = false;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
public onIframeUrl(url: string) {
|
|
610
|
+
if (this.modal) {
|
|
611
|
+
console.log('modal iframe url', url);
|
|
612
|
+
this.modal.iframeUrl = url;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
public onCallEnd() {
|
|
617
|
+
if (this.modal && this.modal.authUrl && this.params.userInfo?.iframeUrl) {
|
|
618
|
+
this.emit('onIframeAuthUrlCallEnd');
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
public onUpdateAccounts(accounts: Info[], recents: RecentType[]) {
|
|
623
|
+
this.accounts = accounts;
|
|
624
|
+
this.recents = recents;
|
|
625
|
+
if (!this.modal) return;
|
|
626
|
+
this.modal.accounts = accounts;
|
|
627
|
+
this.modal.recents = recents;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
public onDarkMode(dark: boolean) {
|
|
631
|
+
if (this.modal) this.modal.darkMode = dark;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
export default ModalManager;
|