@konemono/nostr-login 1.7.22 → 1.7.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/const/index.ts +1 -0
- package/src/iife-module.ts +7 -2
- package/src/index.ts +0 -7
- package/src/modules/AuthNostrService.ts +47 -204
- package/src/modules/BannerManager.ts +1 -9
- package/src/modules/ModalManager.ts +49 -56
- package/src/modules/Nip46.ts +82 -110
- package/src/modules/ProcessManager.ts +5 -47
- package/src/types.ts +2 -32
- package/src/utils/index.ts +15 -29
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { NostrLoginOptions, StartScreens, TypeModal } from '../types';
|
|
2
|
-
import { checkNip05, getBunkerUrl, getDarkMode, localStorageRemoveRecent, localStorageSetItem, prepareSignupRelays } from '../utils';
|
|
2
|
+
import { checkNip05, getBunkerUrl, getDarkMode, localStorageRemoveRecent, localStorageSetItem, prepareSignupRelays, localStorageGetItem } from '../utils';
|
|
3
3
|
import { AuthNostrService, NostrExtensionService, NostrParams } from '.';
|
|
4
4
|
import { EventEmitter } from 'tseep';
|
|
5
|
-
import { ConnectionString, Info, RecentType } from 'nostr-login-components';
|
|
5
|
+
import { ConnectionString, Info, RecentType } from 'nostr-login-components/dist/types/types';
|
|
6
6
|
import { nip19 } from 'nostr-tools';
|
|
7
7
|
import { setDarkMode } from '..';
|
|
8
8
|
|
|
9
|
+
const RELAY_STORAGE_KEY = '__nostrlogin_user_relays';
|
|
10
|
+
|
|
9
11
|
class ModalManager extends EventEmitter {
|
|
10
12
|
private modal: TypeModal | null = null;
|
|
11
13
|
private params: NostrParams;
|
|
@@ -23,6 +25,18 @@ class ModalManager extends EventEmitter {
|
|
|
23
25
|
this.authNostrService = authNostrService;
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
private getUserRelays(): string[] {
|
|
29
|
+
const stored = localStorageGetItem(RELAY_STORAGE_KEY);
|
|
30
|
+
if (stored && Array.isArray(stored)) {
|
|
31
|
+
return stored;
|
|
32
|
+
}
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private saveUserRelays(relays: string[]) {
|
|
37
|
+
localStorageSetItem(RELAY_STORAGE_KEY, JSON.stringify(relays));
|
|
38
|
+
}
|
|
39
|
+
|
|
26
40
|
public async waitReady() {
|
|
27
41
|
if (this.launcherPromise) {
|
|
28
42
|
try {
|
|
@@ -34,10 +48,8 @@ class ModalManager extends EventEmitter {
|
|
|
34
48
|
|
|
35
49
|
public async launch(opt: NostrLoginOptions) {
|
|
36
50
|
console.log('nostr-login launch', opt);
|
|
37
|
-
// mutex
|
|
38
51
|
if (this.launcherPromise) await this.waitReady();
|
|
39
52
|
|
|
40
|
-
// hmm?!
|
|
41
53
|
if (this.authNostrService.isAuthing()) this.authNostrService.resetAuth();
|
|
42
54
|
|
|
43
55
|
this.opt = opt;
|
|
@@ -61,7 +73,6 @@ class ModalManager extends EventEmitter {
|
|
|
61
73
|
this.modal.setAttribute('bunkers', opt.bunkers);
|
|
62
74
|
} else {
|
|
63
75
|
let bunkers = 'nsec.app,highlighter.com';
|
|
64
|
-
// if (opt.dev) bunkers += ',new.nsec.app';
|
|
65
76
|
this.modal.setAttribute('bunkers', bunkers);
|
|
66
77
|
}
|
|
67
78
|
|
|
@@ -91,6 +102,11 @@ class ModalManager extends EventEmitter {
|
|
|
91
102
|
this.modal.isLoadingExtension = false;
|
|
92
103
|
this.modal.isLoading = false;
|
|
93
104
|
|
|
105
|
+
const userRelays = this.getUserRelays();
|
|
106
|
+
if (userRelays.length > 0) {
|
|
107
|
+
this.params.optionsModal.connectRelays = userRelays;
|
|
108
|
+
}
|
|
109
|
+
|
|
94
110
|
[this.modal.connectionString, this.modal.connectionStringServices] = await this.authNostrService.getNostrConnectServices();
|
|
95
111
|
|
|
96
112
|
dialog.appendChild(this.modal);
|
|
@@ -100,21 +116,11 @@ class ModalManager extends EventEmitter {
|
|
|
100
116
|
|
|
101
117
|
this.launcherPromise = new Promise<void>((ok, err) => {
|
|
102
118
|
dialog.addEventListener('close', () => {
|
|
103
|
-
// noop if already resolved
|
|
104
119
|
err(new Error('Closed'));
|
|
105
120
|
|
|
106
121
|
this.authNostrService.resetAuth();
|
|
107
122
|
|
|
108
123
|
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
124
|
// @ts-ignore
|
|
119
125
|
document.body.removeChild(this.modal.parentNode);
|
|
120
126
|
this.modal = null;
|
|
@@ -157,20 +163,16 @@ class ModalManager extends EventEmitter {
|
|
|
157
163
|
|
|
158
164
|
const login = async (name: string, domain?: string) => {
|
|
159
165
|
await exec(async () => {
|
|
160
|
-
// convert name to bunker url
|
|
161
166
|
const bunkerUrl = await getBunkerUrl(name, this.params.optionsModal);
|
|
162
167
|
|
|
163
|
-
// connect to bunker by url
|
|
164
168
|
await this.authNostrService.authNip46('login', { name, bunkerUrl, domain });
|
|
165
169
|
});
|
|
166
170
|
};
|
|
167
171
|
|
|
168
172
|
const signup = async (name: string) => {
|
|
169
173
|
await exec(async () => {
|
|
170
|
-
// create acc on service and get bunker url
|
|
171
174
|
const { bunkerUrl, sk } = await this.authNostrService.createAccount(name);
|
|
172
175
|
|
|
173
|
-
// connect to bunker by url
|
|
174
176
|
await this.authNostrService.authNip46('signup', { name, bunkerUrl, sk });
|
|
175
177
|
});
|
|
176
178
|
};
|
|
@@ -190,7 +192,6 @@ class ModalManager extends EventEmitter {
|
|
|
190
192
|
cs.link = this.authNostrService.prepareImportUrl(cs.link);
|
|
191
193
|
|
|
192
194
|
if (this.modal && iframeUrl) {
|
|
193
|
-
// we pass the link down to iframe so it could open it
|
|
194
195
|
this.modal.authUrl = cs.link;
|
|
195
196
|
this.modal.iframeUrl = iframeUrl;
|
|
196
197
|
this.modal.isLoading = false;
|
|
@@ -208,7 +209,6 @@ class ModalManager extends EventEmitter {
|
|
|
208
209
|
|
|
209
210
|
if (this.modal) {
|
|
210
211
|
if (iframeUrl) {
|
|
211
|
-
// we pass the link down to iframe so it could open it
|
|
212
212
|
this.modal.authUrl = link;
|
|
213
213
|
this.modal.iframeUrl = iframeUrl;
|
|
214
214
|
this.modal.isLoading = false;
|
|
@@ -239,8 +239,6 @@ class ModalManager extends EventEmitter {
|
|
|
239
239
|
.charAt(0)
|
|
240
240
|
.toUpperCase() + self.hostname.slice(1);
|
|
241
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
242
|
|
|
245
243
|
this.modal!.njumpIframe = `
|
|
246
244
|
<html><body>
|
|
@@ -248,18 +246,15 @@ class ModalManager extends EventEmitter {
|
|
|
248
246
|
<script>
|
|
249
247
|
new NstartModal({
|
|
250
248
|
baseUrl: 'https://start.njump.me',
|
|
251
|
-
// Required parameters
|
|
252
249
|
an: '${name}',
|
|
253
|
-
// Optional parameters
|
|
254
250
|
s: [${this.opt!.followNpubs ? `'${this.opt!.followNpubs}'` : ''}],
|
|
255
|
-
afb: false,
|
|
256
|
-
asb: false,
|
|
257
|
-
aan: false,
|
|
258
|
-
aac: true,
|
|
259
|
-
ahc: true,
|
|
260
|
-
arr: ${JSON.stringify(relays)},
|
|
261
|
-
awr: ${JSON.stringify(relays)},
|
|
262
|
-
// Callbacks
|
|
251
|
+
afb: false,
|
|
252
|
+
asb: false,
|
|
253
|
+
aan: false,
|
|
254
|
+
aac: true,
|
|
255
|
+
ahc: true,
|
|
256
|
+
arr: ${JSON.stringify(relays)},
|
|
257
|
+
awr: ${JSON.stringify(relays)},
|
|
263
258
|
onComplete: (result) => {
|
|
264
259
|
console.log('Login token:', result.nostrLogin);
|
|
265
260
|
window.parent.location.href='${window.location.href}#nostr-login='+result.nostrLogin;
|
|
@@ -270,11 +265,10 @@ class ModalManager extends EventEmitter {
|
|
|
270
265
|
}).open();
|
|
271
266
|
</script>
|
|
272
267
|
</body></html>
|
|
273
|
-
`.replaceAll('&', '&');
|
|
268
|
+
`.replaceAll('&', '&');
|
|
274
269
|
|
|
275
270
|
return new Promise((ok, err) => {
|
|
276
271
|
const process = async (nsecOrBunker: string) => {
|
|
277
|
-
// process the returned value
|
|
278
272
|
console.log('nsecOrBunker', nsecOrBunker);
|
|
279
273
|
if (nsecOrBunker.startsWith('nsec1')) {
|
|
280
274
|
let decoded;
|
|
@@ -300,7 +294,6 @@ class ModalManager extends EventEmitter {
|
|
|
300
294
|
if (window.location.hash.startsWith('#nostr-login=')) {
|
|
301
295
|
const nsecOrBunker = window.location.hash.split('#nostr-login=')[1];
|
|
302
296
|
|
|
303
|
-
// clear hash from history
|
|
304
297
|
const url = new URL(window.location.toString());
|
|
305
298
|
url.hash = '';
|
|
306
299
|
window.history.replaceState({}, '', url.toString());
|
|
@@ -309,9 +302,6 @@ class ModalManager extends EventEmitter {
|
|
|
309
302
|
}
|
|
310
303
|
};
|
|
311
304
|
|
|
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
305
|
window.addEventListener('hashchange', onOpen);
|
|
316
306
|
});
|
|
317
307
|
});
|
|
@@ -359,7 +349,6 @@ class ModalManager extends EventEmitter {
|
|
|
359
349
|
});
|
|
360
350
|
|
|
361
351
|
this.modal.addEventListener('nlNostrConnectDefault', () => {
|
|
362
|
-
// dedup the calls
|
|
363
352
|
if (!this.authNostrService.isAuthing()) nostrConnect();
|
|
364
353
|
});
|
|
365
354
|
|
|
@@ -368,18 +357,30 @@ class ModalManager extends EventEmitter {
|
|
|
368
357
|
this.authNostrService.cancelNostrConnect();
|
|
369
358
|
});
|
|
370
359
|
|
|
360
|
+
this.modal.addEventListener('nlSaveUserRelays', (event: any) => {
|
|
361
|
+
const relays = event.detail as string[];
|
|
362
|
+
console.log('nlSaveUserRelays', relays);
|
|
363
|
+
this.saveUserRelays(relays);
|
|
364
|
+
this.params.optionsModal.connectRelays = relays;
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
this.modal.addEventListener('nlGetUserRelays', () => {
|
|
368
|
+
const relays = this.getUserRelays();
|
|
369
|
+
console.log('nlGetUserRelays', relays);
|
|
370
|
+
if (this.modal) {
|
|
371
|
+
this.modal.dispatchEvent(
|
|
372
|
+
new CustomEvent('nlUserRelaysReply', {
|
|
373
|
+
detail: relays,
|
|
374
|
+
}),
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
371
379
|
this.modal.addEventListener('nlSwitchAccount', (event: any) => {
|
|
372
380
|
const eventInfo: Info = event.detail as Info;
|
|
373
381
|
|
|
374
382
|
this.emit('onSwitchAccount', eventInfo);
|
|
375
383
|
|
|
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
384
|
setTimeout(() => dialog.close(), 300);
|
|
384
385
|
});
|
|
385
386
|
|
|
@@ -426,7 +427,7 @@ class ModalManager extends EventEmitter {
|
|
|
426
427
|
else throw new Error('Bad npub');
|
|
427
428
|
} else if (nameNpub.trim().length === 64) {
|
|
428
429
|
pubkey = nameNpub.trim();
|
|
429
|
-
nip19.npubEncode(pubkey);
|
|
430
|
+
nip19.npubEncode(pubkey);
|
|
430
431
|
}
|
|
431
432
|
return pubkey;
|
|
432
433
|
};
|
|
@@ -466,13 +467,10 @@ class ModalManager extends EventEmitter {
|
|
|
466
467
|
throw new Error('Failed to send DM');
|
|
467
468
|
}
|
|
468
469
|
|
|
469
|
-
// switch to 'enter code' mode
|
|
470
470
|
this.modal.isOTP = true;
|
|
471
471
|
|
|
472
|
-
// remember for code handler below
|
|
473
472
|
otpPubkey = pubkey;
|
|
474
473
|
|
|
475
|
-
// spinner off
|
|
476
474
|
this.modal.isLoading = false;
|
|
477
475
|
},
|
|
478
476
|
{ start: true },
|
|
@@ -529,8 +527,6 @@ class ModalManager extends EventEmitter {
|
|
|
529
527
|
this.modal.isLoading = false;
|
|
530
528
|
}
|
|
531
529
|
|
|
532
|
-
// this.authNostrService.cancelListenNostrConnect();
|
|
533
|
-
|
|
534
530
|
dialog.close();
|
|
535
531
|
err(new Error('Cancelled'));
|
|
536
532
|
};
|
|
@@ -555,9 +551,6 @@ class ModalManager extends EventEmitter {
|
|
|
555
551
|
}
|
|
556
552
|
|
|
557
553
|
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
554
|
await this.waitReady();
|
|
562
555
|
|
|
563
556
|
this.launch({
|
package/src/modules/Nip46.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import NDK, { NDKEvent, NDKFilter, NDKNip46Signer, NDKNostrRpc, NDKRpcRequest, NDKRpcResponse, NDKSubscription, NDKSubscriptionCacheUsage, NostrEvent } from '@nostr-dev-kit/ndk';
|
|
2
2
|
import { validateEvent, verifySignature } from 'nostr-tools';
|
|
3
3
|
import { PrivateKeySigner } from './Signer';
|
|
4
|
+
import { NIP46_TIMEOUT } from '../const';
|
|
4
5
|
|
|
5
6
|
class NostrRpc extends NDKNostrRpc {
|
|
6
7
|
protected _ndk: NDK;
|
|
7
8
|
protected _signer: PrivateKeySigner;
|
|
8
9
|
protected requests: Set<string> = new Set();
|
|
10
|
+
protected requestTimeouts: Map<string, NodeJS.Timeout> = new Map();
|
|
9
11
|
private sub?: NDKSubscription;
|
|
10
12
|
protected _useNip44: boolean = false;
|
|
11
13
|
|
|
@@ -16,19 +18,34 @@ class NostrRpc extends NDKNostrRpc {
|
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
public async subscribe(filter: NDKFilter): Promise<NDKSubscription> {
|
|
19
|
-
// NOTE: fixing ndk
|
|
20
21
|
filter.kinds = filter.kinds?.filter(k => k === 24133);
|
|
21
22
|
this.sub = await super.subscribe(filter);
|
|
22
23
|
return this.sub;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
public stop() {
|
|
27
|
+
this.clearAllTimeouts();
|
|
26
28
|
if (this.sub) {
|
|
27
29
|
this.sub.stop();
|
|
28
30
|
this.sub = undefined;
|
|
29
31
|
}
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
private clearAllTimeouts() {
|
|
35
|
+
for (const timeout of this.requestTimeouts.values()) {
|
|
36
|
+
clearTimeout(timeout);
|
|
37
|
+
}
|
|
38
|
+
this.requestTimeouts.clear();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private clearTimeout(id: string) {
|
|
42
|
+
const timeout = this.requestTimeouts.get(id);
|
|
43
|
+
if (timeout) {
|
|
44
|
+
clearTimeout(timeout);
|
|
45
|
+
this.requestTimeouts.delete(id);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
32
49
|
public setUseNip44(useNip44: boolean) {
|
|
33
50
|
this._useNip44 = useNip44;
|
|
34
51
|
}
|
|
@@ -39,7 +56,6 @@ class NostrRpc extends NDKNostrRpc {
|
|
|
39
56
|
return ciphertext[l - 28] === '?' && ciphertext[l - 27] === 'i' && ciphertext[l - 26] === 'v' && ciphertext[l - 25] === '=';
|
|
40
57
|
}
|
|
41
58
|
|
|
42
|
-
// override to auto-decrypt nip04/nip44
|
|
43
59
|
public async parseEvent(event: NDKEvent): Promise<NDKRpcRequest | NDKRpcResponse> {
|
|
44
60
|
const remoteUser = this._ndk.getUser({ pubkey: event.pubkey });
|
|
45
61
|
remoteUser.ndk = this._ndk;
|
|
@@ -68,9 +84,6 @@ class NostrRpc extends NDKNostrRpc {
|
|
|
68
84
|
}
|
|
69
85
|
}
|
|
70
86
|
|
|
71
|
-
// ndk doesn't support nostrconnect:
|
|
72
|
-
// we just listed to an unsolicited reply to
|
|
73
|
-
// our pubkey and if it's ack/secret - we're fine
|
|
74
87
|
public async listen(nostrConnectSecret: string): Promise<string> {
|
|
75
88
|
const pubkey = this._signer.pubkey;
|
|
76
89
|
console.log('nostr-login listening for conn to', pubkey);
|
|
@@ -78,61 +91,46 @@ class NostrRpc extends NDKNostrRpc {
|
|
|
78
91
|
'kinds': [24133],
|
|
79
92
|
'#p': [pubkey],
|
|
80
93
|
});
|
|
81
|
-
|
|
82
94
|
return new Promise<string>((ok, err) => {
|
|
83
|
-
let finished = false;
|
|
84
|
-
|
|
85
|
-
// 30 sec timeout
|
|
86
95
|
const timeoutId = setTimeout(() => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
this.emit('timeout');
|
|
92
|
-
err('Listen timed out');
|
|
93
|
-
}
|
|
94
|
-
}, 30000);
|
|
95
|
-
|
|
96
|
+
this.stop();
|
|
97
|
+
err(new Error('NIP46 listen timeout'));
|
|
98
|
+
}, NIP46_TIMEOUT);
|
|
99
|
+
|
|
96
100
|
sub.on('event', async (event: NDKEvent) => {
|
|
97
|
-
if (finished) return;
|
|
98
|
-
|
|
99
101
|
try {
|
|
100
102
|
const parsedEvent = await this.parseEvent(event);
|
|
101
|
-
// console.log('ack parsedEvent', parsedEvent);
|
|
102
103
|
if (!(parsedEvent as NDKRpcRequest).method) {
|
|
103
104
|
const response = parsedEvent as NDKRpcResponse;
|
|
104
105
|
|
|
105
|
-
// ignore
|
|
106
106
|
if (response.result === 'auth_url') return;
|
|
107
107
|
|
|
108
|
-
// FIXME for now accept 'ack' replies, later on only
|
|
109
|
-
// accept secrets
|
|
110
108
|
if (response.result === 'ack' || response.result === nostrConnectSecret) {
|
|
111
|
-
finished = true;
|
|
112
109
|
clearTimeout(timeoutId);
|
|
113
|
-
this.stop();
|
|
114
110
|
ok(event.pubkey);
|
|
115
111
|
} else {
|
|
116
|
-
finished = true;
|
|
117
112
|
clearTimeout(timeoutId);
|
|
118
|
-
this.stop();
|
|
119
113
|
err(response.error);
|
|
120
114
|
}
|
|
121
115
|
}
|
|
122
116
|
} catch (e) {
|
|
123
117
|
console.log('error parsing event', e, event.rawEvent());
|
|
124
118
|
}
|
|
119
|
+
this.stop();
|
|
125
120
|
});
|
|
126
121
|
});
|
|
127
122
|
}
|
|
128
123
|
|
|
129
|
-
// since ndk doesn't yet support perms param
|
|
130
|
-
// we reimplement the 'connect' call here
|
|
131
|
-
// instead of await signer.blockUntilReady();
|
|
132
124
|
public async connect(pubkey: string, token?: string, perms?: string) {
|
|
133
125
|
return new Promise<void>((ok, err) => {
|
|
134
126
|
const connectParams = [pubkey!, token || '', perms || ''];
|
|
127
|
+
|
|
128
|
+
const timeoutId = setTimeout(() => {
|
|
129
|
+
err(new Error('NIP46 connect timeout'));
|
|
130
|
+
}, NIP46_TIMEOUT);
|
|
131
|
+
|
|
135
132
|
this.sendRequest(pubkey!, 'connect', connectParams, 24133, (response: NDKRpcResponse) => {
|
|
133
|
+
clearTimeout(timeoutId);
|
|
136
134
|
if (response.result === 'ack') {
|
|
137
135
|
ok();
|
|
138
136
|
} else {
|
|
@@ -149,22 +147,13 @@ class NostrRpc extends NDKNostrRpc {
|
|
|
149
147
|
public async sendRequest(remotePubkey: string, method: string, params: string[] = [], kind = 24133, cb?: (res: NDKRpcResponse) => void): Promise<NDKRpcResponse> {
|
|
150
148
|
const id = this.getId();
|
|
151
149
|
|
|
152
|
-
// response handler will deduplicate auth urls and responses
|
|
153
150
|
this.setResponseHandler(id, cb);
|
|
154
151
|
|
|
155
|
-
// create and sign request
|
|
156
152
|
const event = await this.createRequestEvent(id, remotePubkey, method, params, kind);
|
|
157
|
-
console.log(
|
|
153
|
+
console.log('sendRequest', { event, method, remotePubkey, params });
|
|
158
154
|
|
|
159
|
-
|
|
160
|
-
const relays = await event.publish();
|
|
161
|
-
if (relays.size === 0) throw new Error('Failed to publish to relays');
|
|
155
|
+
await event.publish();
|
|
162
156
|
|
|
163
|
-
// NOTE: ndk returns a promise that never resolves and
|
|
164
|
-
// in fact REQUIRES cb to be provided (otherwise no way
|
|
165
|
-
// to consume the result), we've already stepped on the bug
|
|
166
|
-
// of waiting for this unresolvable result, so now we return
|
|
167
|
-
// undefined to make sure waiters fail, not hang.
|
|
168
157
|
// @ts-ignore
|
|
169
158
|
return undefined as NDKRpcResponse;
|
|
170
159
|
}
|
|
@@ -172,7 +161,25 @@ class NostrRpc extends NDKNostrRpc {
|
|
|
172
161
|
protected setResponseHandler(id: string, cb?: (res: NDKRpcResponse) => void) {
|
|
173
162
|
let authUrlSent = false;
|
|
174
163
|
const now = Date.now();
|
|
175
|
-
|
|
164
|
+
|
|
165
|
+
const timeoutId = setTimeout(() => {
|
|
166
|
+
if (this.requests.has(id)) {
|
|
167
|
+
this.requests.delete(id);
|
|
168
|
+
this.requestTimeouts.delete(id);
|
|
169
|
+
if (cb) {
|
|
170
|
+
cb({
|
|
171
|
+
id,
|
|
172
|
+
result: '',
|
|
173
|
+
error: 'NIP46 request timeout',
|
|
174
|
+
event: undefined as any,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}, NIP46_TIMEOUT);
|
|
179
|
+
|
|
180
|
+
this.requestTimeouts.set(id, timeoutId);
|
|
181
|
+
|
|
182
|
+
return new Promise<NDKRpcResponse>(() => {
|
|
176
183
|
const responseHandler = (response: NDKRpcResponse) => {
|
|
177
184
|
if (response.result === 'auth_url') {
|
|
178
185
|
this.once(`response-${id}`, responseHandler);
|
|
@@ -182,30 +189,18 @@ class NostrRpc extends NDKNostrRpc {
|
|
|
182
189
|
}
|
|
183
190
|
} else if (cb) {
|
|
184
191
|
if (this.requests.has(id)) {
|
|
192
|
+
this.clearTimeout(id);
|
|
185
193
|
this.requests.delete(id);
|
|
186
194
|
console.log('nostr-login processed nip46 request in', Date.now() - now, 'ms');
|
|
187
195
|
cb(response);
|
|
188
|
-
resolve(response);
|
|
189
196
|
}
|
|
190
197
|
}
|
|
191
198
|
};
|
|
192
199
|
|
|
193
200
|
this.once(`response-${id}`, responseHandler);
|
|
194
|
-
|
|
195
|
-
// timeout
|
|
196
|
-
setTimeout(() => {
|
|
197
|
-
if (this.requests.has(id)) {
|
|
198
|
-
this.requests.delete(id);
|
|
199
|
-
this.removeListener(`response-${id}`, responseHandler);
|
|
200
|
-
console.log('nostr-login: NIP-46 request timeout, emitting timeout event');
|
|
201
|
-
this.emit('timeout');
|
|
202
|
-
reject('[Nip46] Request timed out');
|
|
203
|
-
}
|
|
204
|
-
}, 30000);
|
|
205
201
|
});
|
|
206
202
|
}
|
|
207
203
|
|
|
208
|
-
|
|
209
204
|
protected async createRequestEvent(id: string, remotePubkey: string, method: string, params: string[] = [], kind = 24133) {
|
|
210
205
|
this.requests.add(id);
|
|
211
206
|
const localUser = await this._signer.user();
|
|
@@ -232,7 +227,6 @@ export class IframeNostrRpc extends NostrRpc {
|
|
|
232
227
|
private peerOrigin?: string;
|
|
233
228
|
private iframePort?: MessagePort;
|
|
234
229
|
private iframeRequests = new Map<string, { id: string; pubkey: string }>();
|
|
235
|
-
private iframePingInterval?: NodeJS.Timeout;
|
|
236
230
|
|
|
237
231
|
public constructor(ndk: NDK, localSigner: PrivateKeySigner, iframePeerOrigin?: string) {
|
|
238
232
|
super(ndk, localSigner);
|
|
@@ -246,7 +240,6 @@ export class IframeNostrRpc extends NostrRpc {
|
|
|
246
240
|
this._ndk,
|
|
247
241
|
{},
|
|
248
242
|
{
|
|
249
|
-
// don't send to relay
|
|
250
243
|
closeOnEose: true,
|
|
251
244
|
cacheUsage: NDKSubscriptionCacheUsage.ONLY_CACHE,
|
|
252
245
|
},
|
|
@@ -258,8 +251,7 @@ export class IframeNostrRpc extends NostrRpc {
|
|
|
258
251
|
|
|
259
252
|
this.iframePort = port;
|
|
260
253
|
|
|
261
|
-
|
|
262
|
-
this.iframePingInterval = setInterval(() => {
|
|
254
|
+
setInterval(() => {
|
|
263
255
|
console.log('iframe-nip46 ping');
|
|
264
256
|
this.iframePort!.postMessage('ping');
|
|
265
257
|
}, 5000);
|
|
@@ -273,7 +265,6 @@ export class IframeNostrRpc extends NostrRpc {
|
|
|
273
265
|
return;
|
|
274
266
|
}
|
|
275
267
|
|
|
276
|
-
// a copy-paste from rpc.subscribe
|
|
277
268
|
try {
|
|
278
269
|
const event = ev.data;
|
|
279
270
|
|
|
@@ -281,7 +272,6 @@ export class IframeNostrRpc extends NostrRpc {
|
|
|
281
272
|
if (!verifySignature(event)) throw new Error('Invalid event signature from iframe');
|
|
282
273
|
const nevent = new NDKEvent(this._ndk, event);
|
|
283
274
|
const parsedEvent = await this.parseEvent(nevent);
|
|
284
|
-
// we're only implementing client-side rpc
|
|
285
275
|
if (!(parsedEvent as NDKRpcRequest).method) {
|
|
286
276
|
console.log('parsed response', parsedEvent);
|
|
287
277
|
this.emit(`response-${parsedEvent.id}`, parsedEvent);
|
|
@@ -292,39 +282,22 @@ export class IframeNostrRpc extends NostrRpc {
|
|
|
292
282
|
};
|
|
293
283
|
}
|
|
294
284
|
|
|
295
|
-
public cleanup() {
|
|
296
|
-
if (this.iframePingInterval) {
|
|
297
|
-
clearInterval(this.iframePingInterval);
|
|
298
|
-
this.iframePingInterval = undefined;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
285
|
public async sendRequest(remotePubkey: string, method: string, params: string[] = [], kind = 24133, cb?: (res: NDKRpcResponse) => void): Promise<NDKRpcResponse> {
|
|
303
286
|
const id = this.getId();
|
|
304
287
|
|
|
305
|
-
// create and sign request event
|
|
306
288
|
const event = await this.createRequestEvent(id, remotePubkey, method, params, kind);
|
|
307
289
|
|
|
308
|
-
// set response handler, it will dedup auth urls,
|
|
309
|
-
// and also dedup response handlers - we're sending
|
|
310
|
-
// to relays and to iframe
|
|
311
290
|
this.setResponseHandler(id, cb);
|
|
312
291
|
|
|
313
292
|
if (this.iframePort) {
|
|
314
|
-
// map request event id to request id, if iframe
|
|
315
|
-
// has no key it will reply with error:event_id (it can't
|
|
316
|
-
// decrypt the request id without keys)
|
|
317
293
|
this.iframeRequests.set(event.id, { id, pubkey: remotePubkey });
|
|
318
294
|
|
|
319
|
-
// send to iframe
|
|
320
295
|
console.log('iframe-nip46 sending request to', this.peerOrigin, event.rawEvent());
|
|
321
296
|
this.iframePort.postMessage(event.rawEvent());
|
|
322
297
|
} else {
|
|
323
|
-
// send to relays
|
|
324
298
|
await event.publish();
|
|
325
299
|
}
|
|
326
300
|
|
|
327
|
-
// see notes in 'super'
|
|
328
301
|
// @ts-ignore
|
|
329
302
|
return undefined as NDKRpcResponse;
|
|
330
303
|
}
|
|
@@ -341,14 +314,11 @@ export class ReadyListener {
|
|
|
341
314
|
this.promise = new Promise<any>(ok => {
|
|
342
315
|
console.log(new Date(), 'started listener for', this.messages);
|
|
343
316
|
|
|
344
|
-
// ready message handler
|
|
345
317
|
const onReady = async (e: MessageEvent) => {
|
|
346
318
|
const originHostname = new URL(origin!).hostname;
|
|
347
319
|
const messageHostname = new URL(e.origin).hostname;
|
|
348
|
-
// same host or subdomain
|
|
349
320
|
const validHost = messageHostname === originHostname || messageHostname.endsWith('.' + originHostname);
|
|
350
321
|
if (!validHost || !Array.isArray(e.data) || !e.data.length || !this.messages.includes(e.data[0])) {
|
|
351
|
-
// console.log(new Date(), 'got invalid ready message', e.origin, e.data);
|
|
352
322
|
return;
|
|
353
323
|
}
|
|
354
324
|
|
|
@@ -362,14 +332,7 @@ export class ReadyListener {
|
|
|
362
332
|
|
|
363
333
|
async wait(): Promise<any> {
|
|
364
334
|
console.log(new Date(), 'waiting for', this.messages);
|
|
365
|
-
|
|
366
|
-
const r = await new Promise<any>((ok, err) => {
|
|
367
|
-
// 30 sec timeout for iframe ready
|
|
368
|
-
setTimeout(() => err(new Date() + ' timeout for ' + this.messages), 30000);
|
|
369
|
-
|
|
370
|
-
this.promise.then(ok).catch(err);
|
|
371
|
-
});
|
|
372
|
-
|
|
335
|
+
const r = await this.promise;
|
|
373
336
|
console.log(new Date(), 'finished waiting for', this.messages, r);
|
|
374
337
|
return r;
|
|
375
338
|
}
|
|
@@ -382,15 +345,11 @@ export class Nip46Signer extends NDKNip46Signer {
|
|
|
382
345
|
constructor(ndk: NDK, localSigner: PrivateKeySigner, signerPubkey: string, iframeOrigin?: string) {
|
|
383
346
|
super(ndk, signerPubkey, localSigner);
|
|
384
347
|
|
|
385
|
-
// override with our own rpc implementation
|
|
386
348
|
this._rpc = new IframeNostrRpc(ndk, localSigner, iframeOrigin);
|
|
387
|
-
this._rpc.setUseNip44(true);
|
|
349
|
+
this._rpc.setUseNip44(true);
|
|
388
350
|
this._rpc.on('authUrl', (url: string) => {
|
|
389
351
|
this.emit('authUrl', url);
|
|
390
352
|
});
|
|
391
|
-
this._rpc.on('timeout', () => {
|
|
392
|
-
this.emit('timeout');
|
|
393
|
-
});
|
|
394
353
|
|
|
395
354
|
this.rpc = this._rpc;
|
|
396
355
|
}
|
|
@@ -400,17 +359,14 @@ export class Nip46Signer extends NDKNip46Signer {
|
|
|
400
359
|
}
|
|
401
360
|
|
|
402
361
|
private async setSignerPubkey(signerPubkey: string, sameAsUser: boolean = false) {
|
|
403
|
-
console.log(
|
|
362
|
+
console.log('setSignerPubkey', signerPubkey);
|
|
404
363
|
|
|
405
|
-
// ensure it's set
|
|
406
364
|
this.remotePubkey = signerPubkey;
|
|
407
365
|
|
|
408
|
-
// when we're sure it's known
|
|
409
366
|
this._rpc.on(`iframeRestart-${signerPubkey}`, () => {
|
|
410
367
|
this.emit('iframeRestart');
|
|
411
368
|
});
|
|
412
369
|
|
|
413
|
-
// now call getPublicKey and swap remotePubkey w/ that
|
|
414
370
|
await this.initUserPubkey(sameAsUser ? signerPubkey : '');
|
|
415
371
|
}
|
|
416
372
|
|
|
@@ -425,9 +381,19 @@ export class Nip46Signer extends NDKNip46Signer {
|
|
|
425
381
|
this._userPubkey = await new Promise<string>((ok, err) => {
|
|
426
382
|
if (!this.remotePubkey) throw new Error('Signer pubkey not set');
|
|
427
383
|
|
|
428
|
-
console.log(
|
|
384
|
+
console.log('get_public_key', this.remotePubkey);
|
|
385
|
+
|
|
386
|
+
const timeoutId = setTimeout(() => {
|
|
387
|
+
err(new Error('NIP46 get_public_key timeout'));
|
|
388
|
+
}, NIP46_TIMEOUT);
|
|
389
|
+
|
|
429
390
|
this._rpc.sendRequest(this.remotePubkey, 'get_public_key', [], 24133, (response: NDKRpcResponse) => {
|
|
430
|
-
|
|
391
|
+
clearTimeout(timeoutId);
|
|
392
|
+
if (response.error) {
|
|
393
|
+
err(new Error(response.error));
|
|
394
|
+
} else {
|
|
395
|
+
ok(response.result);
|
|
396
|
+
}
|
|
431
397
|
});
|
|
432
398
|
});
|
|
433
399
|
}
|
|
@@ -449,15 +415,21 @@ export class Nip46Signer extends NDKNip46Signer {
|
|
|
449
415
|
}
|
|
450
416
|
|
|
451
417
|
public async createAccount2({ bunkerPubkey, name, domain, perms = '' }: { bunkerPubkey: string; name: string; domain: string; perms?: string }) {
|
|
452
|
-
const params = [
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
418
|
+
const params = [name, domain, '', perms];
|
|
419
|
+
|
|
420
|
+
const r = await new Promise<NDKRpcResponse>((ok, err) => {
|
|
421
|
+
const timeoutId = setTimeout(() => {
|
|
422
|
+
err(new Error('NIP46 create_account timeout'));
|
|
423
|
+
}, NIP46_TIMEOUT);
|
|
424
|
+
|
|
425
|
+
this.rpc.sendRequest(bunkerPubkey, 'create_account', params, undefined, response => {
|
|
426
|
+
clearTimeout(timeoutId);
|
|
427
|
+
if (response.error) {
|
|
428
|
+
err(new Error(response.error));
|
|
429
|
+
} else {
|
|
430
|
+
ok(response);
|
|
431
|
+
}
|
|
432
|
+
});
|
|
461
433
|
});
|
|
462
434
|
|
|
463
435
|
console.log('create_account pubkey', r);
|