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