@konemono/nostr-login 1.9.13 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/const/index.d.ts +3 -0
- package/dist/index.esm.js +12 -10
- package/dist/index.esm.js.map +1 -1
- package/dist/modules/AuthNostrService.d.ts +13 -8
- package/dist/modules/ModalManager.d.ts +1 -0
- package/dist/modules/Nip46.d.ts +44 -47
- package/dist/modules/NostrExtensionService.d.ts +0 -5
- package/dist/modules/Signer.d.ts +4 -11
- package/dist/modules/nip46/Nip46Adapter.d.ts +2 -10
- package/dist/modules/nip46/Nip46Client.d.ts +1 -16
- package/dist/modules/nip46/types.d.ts +0 -8
- package/dist/types.d.ts +0 -5
- package/dist/unpkg.js +12 -10
- package/dist/utils/index.d.ts +3 -2
- package/package.json +4 -7
- package/src/const/index.ts +7 -0
- package/src/index.ts +2 -8
- package/src/modules/AuthNostrService.ts +168 -165
- package/src/modules/ModalManager.ts +14 -10
- package/src/modules/Nip46.ts +400 -208
- package/src/modules/NostrExtensionService.ts +0 -6
- package/src/modules/Signer.ts +8 -39
- package/src/types.ts +0 -7
- package/src/utils/index.ts +31 -79
- package/tsconfig.json +2 -2
- package/src/modules/AmberDirectSigner.ts +0 -242
- package/src/modules/Nip46.iframe.test.ts +0 -124
- package/src/modules/Nip46.test.ts +0 -31
- package/src/modules/nip46/Nip46Adapter.ts +0 -180
- package/src/modules/nip46/Nip46Client.ts +0 -363
- package/src/modules/nip46/types.ts +0 -34
- package/vitest.config.ts +0 -9
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Info, RecentType } from 'nostr-login-components/dist/types/types';
|
|
2
|
+
import NDK, { NDKSigner } from '@nostr-dev-kit/ndk';
|
|
2
3
|
import { NostrLoginOptions } from '../types';
|
|
3
4
|
export declare const localStorageSetItem: (key: string, value: string) => void;
|
|
4
5
|
export declare const localStorageGetItem: (key: string) => any;
|
|
5
6
|
export declare const localStorageRemoveItem: (key: string) => void;
|
|
6
|
-
export declare const fetchProfile: (info: Info,
|
|
7
|
+
export declare const fetchProfile: (info: Info, profileNdk: NDK) => Promise<import("@nostr-dev-kit/ndk").NDKUserProfile | null>;
|
|
7
8
|
export declare const prepareSignupRelays: (signupRelays?: string) => string[];
|
|
8
|
-
export declare const createProfile: (info: Info, signer:
|
|
9
|
+
export declare const createProfile: (info: Info, profileNdk: NDK, signer: NDKSigner, signupRelays?: string, outboxRelays?: string[]) => Promise<void>;
|
|
9
10
|
export declare const bunkerUrlToInfo: (bunkerUrl: string, sk?: string) => Info;
|
|
10
11
|
export declare const isBunkerUrl: (value: string) => boolean;
|
|
11
12
|
export declare const getBunkerUrl: (value: string, optionsModal: NostrLoginOptions) => Promise<string>;
|
package/package.json
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@konemono/nostr-login",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./dist/index.esm.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "rollup -c",
|
|
10
|
-
"format": "npx prettier --write src"
|
|
11
|
-
"test": "vitest run",
|
|
12
|
-
"test:watch": "vitest"
|
|
10
|
+
"format": "npx prettier --write src"
|
|
13
11
|
},
|
|
14
12
|
"author": "a-fralou",
|
|
15
13
|
"dependencies": {
|
|
14
|
+
"@nostr-dev-kit/ndk": "^2.3.1",
|
|
16
15
|
"nostr-tools": "^1.17.0",
|
|
17
16
|
"tseep": "^1.2.1"
|
|
18
17
|
},
|
|
@@ -23,9 +22,7 @@
|
|
|
23
22
|
"nostr-login-components": "^1.0.3",
|
|
24
23
|
"prettier": "^3.2.2",
|
|
25
24
|
"rollup": "^4.9.6",
|
|
26
|
-
"rollup-plugin-typescript2": "^0.36.0"
|
|
27
|
-
"vitest": "^1.0.0",
|
|
28
|
-
"@types/node": "^18.0.0"
|
|
25
|
+
"rollup-plugin-typescript2": "^0.36.0"
|
|
29
26
|
},
|
|
30
27
|
"license": "MIT"
|
|
31
28
|
}
|
package/src/const/index.ts
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
1
|
export const CALL_TIMEOUT = 5000;
|
|
2
|
+
|
|
3
|
+
// デフォルトのNip46リレー
|
|
4
|
+
export const DEFAULT_NIP46_RELAYS = ['wss://relay.nsec.app/', 'wss://ephemeral.snowflare.cc/'];
|
|
5
|
+
|
|
6
|
+
// Nip46タイムアウト設定
|
|
7
|
+
export const NIP46_REQUEST_TIMEOUT = 30000; // 30秒
|
|
8
|
+
export const NIP46_CONNECT_TIMEOUT = 60000; // 60秒
|
package/src/index.ts
CHANGED
|
@@ -148,11 +148,7 @@ export class NostrLoginInitializer {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
private openPopup(url: string) {
|
|
151
|
-
|
|
152
|
-
window.open(url, '_blank', 'width=400,height=600');
|
|
153
|
-
} else {
|
|
154
|
-
this.popupManager.openPopup(url);
|
|
155
|
-
}
|
|
151
|
+
this.popupManager.openPopup(url);
|
|
156
152
|
}
|
|
157
153
|
|
|
158
154
|
private async switchAccount(info: Info, signup = false) {
|
|
@@ -173,8 +169,6 @@ export class NostrLoginInitializer {
|
|
|
173
169
|
await this.extensionService.trySetExtensionForPubkey(info.pubkey);
|
|
174
170
|
} else if (info.authMethod === 'connect' && info.sk && info.relays && info.relays[0]) {
|
|
175
171
|
this.authNostrService.setConnect(info);
|
|
176
|
-
} else if (info.authMethod === ('amber' as any)) {
|
|
177
|
-
this.authNostrService.setAmber(info);
|
|
178
172
|
} else {
|
|
179
173
|
throw new Error('Bad auth info');
|
|
180
174
|
}
|
|
@@ -309,7 +303,7 @@ export class NostrLoginInitializer {
|
|
|
309
303
|
public setAuth = async (o: NostrLoginAuthOptions) => {
|
|
310
304
|
if (!o.type) throw new Error('Invalid auth event');
|
|
311
305
|
if (o.type !== 'login' && o.type !== 'logout' && o.type !== 'signup') throw new Error('Invalid auth event');
|
|
312
|
-
if (o.method && o.method !== 'connect' && o.method !== 'extension' && o.method !== 'local' && o.method !== 'otp' && o.method !== 'readOnly'
|
|
306
|
+
if (o.method && o.method !== 'connect' && o.method !== 'extension' && o.method !== 'local' && o.method !== 'otp' && o.method !== 'readOnly')
|
|
313
307
|
throw new Error('Invalid auth event');
|
|
314
308
|
|
|
315
309
|
if (o.type === 'logout') return this.logout();
|
|
@@ -2,19 +2,16 @@ import { localStorageAddAccount, bunkerUrlToInfo, isBunkerUrl, fetchProfile, get
|
|
|
2
2
|
import { ConnectionString, Info } from 'nostr-login-components/dist/types/types';
|
|
3
3
|
import { generatePrivateKey, getEventHash, getPublicKey, nip19 } from 'nostr-tools';
|
|
4
4
|
import { NostrLoginAuthOptions, Response } from '../types';
|
|
5
|
-
|
|
5
|
+
import NDK, { NDKEvent, NDKNip46Signer, NDKRpcResponse, NDKUser, NostrEvent } from '@nostr-dev-kit/ndk';
|
|
6
6
|
import { NostrParams } from './';
|
|
7
7
|
import { EventEmitter } from 'tseep';
|
|
8
8
|
import { Signer } from './Nostr';
|
|
9
9
|
import { Nip44 } from '../utils/nip44';
|
|
10
|
-
import { IframeNostrRpc, Nip46Signer, ReadyListener
|
|
11
|
-
import { Nip46Client } from './nip46/Nip46Client';
|
|
12
|
-
import { Nip46Adapter } from './nip46/Nip46Adapter';
|
|
10
|
+
import { IframeNostrRpc, Nip46Signer, ReadyListener } from './Nip46';
|
|
13
11
|
import { PrivateKeySigner } from './Signer';
|
|
14
12
|
|
|
15
13
|
const OUTBOX_RELAYS = ['wss://user.kindpag.es', 'wss://purplepag.es', 'wss://relay.nos.social'];
|
|
16
|
-
const
|
|
17
|
-
const CONNECT_TIMEOUT = 5000;
|
|
14
|
+
const DEFAULT_NOSTRCONNECT_RELAY = 'wss://relay.nsec.app/';
|
|
18
15
|
const NOSTRCONNECT_APPS: ConnectionString[] = [
|
|
19
16
|
{
|
|
20
17
|
name: 'Nsec.app',
|
|
@@ -22,28 +19,31 @@ const NOSTRCONNECT_APPS: ConnectionString[] = [
|
|
|
22
19
|
canImport: true,
|
|
23
20
|
img: 'https://nsec.app/assets/favicon.ico',
|
|
24
21
|
link: 'https://use.nsec.app/<nostrconnect>',
|
|
25
|
-
|
|
22
|
+
relay: 'wss://relay.nsec.app/',
|
|
26
23
|
},
|
|
27
24
|
{
|
|
28
25
|
name: 'Amber',
|
|
29
26
|
img: 'https://raw.githubusercontent.com/greenart7c3/Amber/refs/heads/master/assets/android-icon.svg',
|
|
30
27
|
link: '<nostrconnect>',
|
|
31
|
-
|
|
28
|
+
relay: 'wss://relay.nsec.app/',
|
|
32
29
|
},
|
|
33
30
|
{
|
|
34
31
|
name: 'Other key stores',
|
|
35
32
|
img: '',
|
|
36
33
|
link: '<nostrconnect>',
|
|
37
|
-
|
|
34
|
+
relay: 'wss://relay.nsec.app/',
|
|
38
35
|
},
|
|
39
36
|
];
|
|
40
37
|
|
|
41
38
|
class AuthNostrService extends EventEmitter implements Signer {
|
|
42
|
-
private
|
|
39
|
+
private ndk: NDK;
|
|
40
|
+
private profileNdk: NDK;
|
|
41
|
+
private signer: Nip46Signer | null = null;
|
|
43
42
|
private localSigner: PrivateKeySigner | null = null;
|
|
44
43
|
private params: NostrParams;
|
|
45
44
|
private signerPromise?: Promise<void>;
|
|
46
45
|
private signerErrCallback?: (err: string) => void;
|
|
46
|
+
private signerAbortController?: AbortController;
|
|
47
47
|
private readyPromise?: Promise<void>;
|
|
48
48
|
private readyCallback?: () => void;
|
|
49
49
|
private nip44Codec = new Nip44();
|
|
@@ -64,6 +64,15 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
64
64
|
constructor(params: NostrParams) {
|
|
65
65
|
super();
|
|
66
66
|
this.params = params;
|
|
67
|
+
this.ndk = new NDK({
|
|
68
|
+
enableOutboxModel: false,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
this.profileNdk = new NDK({
|
|
72
|
+
enableOutboxModel: true,
|
|
73
|
+
explicitRelayUrls: OUTBOX_RELAYS,
|
|
74
|
+
});
|
|
75
|
+
this.profileNdk.connect();
|
|
67
76
|
|
|
68
77
|
this.nip04 = {
|
|
69
78
|
encrypt: this.encrypt04.bind(this),
|
|
@@ -98,8 +107,20 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
98
107
|
this.resetAuth();
|
|
99
108
|
}
|
|
100
109
|
|
|
110
|
+
public cancelSignerInit() {
|
|
111
|
+
if (this.signerAbortController) {
|
|
112
|
+
this.signerAbortController.abort();
|
|
113
|
+
this.signerAbortController = undefined;
|
|
114
|
+
}
|
|
115
|
+
if (this.signerErrCallback) {
|
|
116
|
+
this.signerErrCallback('Cancelled by user');
|
|
117
|
+
this.signerErrCallback = undefined;
|
|
118
|
+
}
|
|
119
|
+
this.emit('signerCancelled');
|
|
120
|
+
}
|
|
121
|
+
|
|
101
122
|
public async nostrConnect(
|
|
102
|
-
|
|
123
|
+
relay?: string,
|
|
103
124
|
{
|
|
104
125
|
domain = '',
|
|
105
126
|
link = '',
|
|
@@ -111,8 +132,8 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
111
132
|
importConnect?: boolean;
|
|
112
133
|
iframeUrl?: string;
|
|
113
134
|
} = {},
|
|
114
|
-
)
|
|
115
|
-
|
|
135
|
+
) {
|
|
136
|
+
relay = relay || DEFAULT_NOSTRCONNECT_RELAY;
|
|
116
137
|
|
|
117
138
|
const info: Info = {
|
|
118
139
|
authMethod: 'connect',
|
|
@@ -120,16 +141,14 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
120
141
|
signerPubkey: '', // unknown too!
|
|
121
142
|
sk: this.nostrConnectKey,
|
|
122
143
|
domain: domain,
|
|
123
|
-
relays:
|
|
144
|
+
relays: [relay],
|
|
124
145
|
iframeUrl,
|
|
125
146
|
};
|
|
126
147
|
|
|
127
148
|
console.log('nostrconnect info', info, link);
|
|
128
149
|
|
|
129
150
|
// non-iframe flow
|
|
130
|
-
if (link && !iframeUrl)
|
|
131
|
-
window.open(link, '_blank', 'width=400,height=700');
|
|
132
|
-
}
|
|
151
|
+
if (link && !iframeUrl) window.open(link, '_blank', 'width=400,height=700');
|
|
133
152
|
|
|
134
153
|
// init nip46 signer
|
|
135
154
|
await this.initSigner(info, { listen: true });
|
|
@@ -137,7 +156,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
137
156
|
// signer learns the remote pubkey
|
|
138
157
|
if (!info.pubkey || !info.signerPubkey) throw new Error('Bad remote pubkey');
|
|
139
158
|
|
|
140
|
-
info.bunkerUrl = `bunker://${info.signerPubkey}
|
|
159
|
+
info.bunkerUrl = `bunker://${info.signerPubkey}?relay=${relay}`;
|
|
141
160
|
|
|
142
161
|
// callback
|
|
143
162
|
if (!importConnect) this.onAuth('login', info);
|
|
@@ -145,14 +164,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
145
164
|
return info;
|
|
146
165
|
}
|
|
147
166
|
|
|
148
|
-
public async createNostrConnect(
|
|
149
|
-
/*const relayList = relays
|
|
150
|
-
? relays
|
|
151
|
-
.split(",")
|
|
152
|
-
.map(r => r.trim().replace(/['"]/g, ""))
|
|
153
|
-
.filter(r => r.length > 0)
|
|
154
|
-
: [];*/
|
|
155
|
-
|
|
167
|
+
public async createNostrConnect(relay?: string) {
|
|
156
168
|
this.nostrConnectKey = generatePrivateKey();
|
|
157
169
|
this.nostrConnectSecret = Math.random().toString(36).substring(7);
|
|
158
170
|
|
|
@@ -164,7 +176,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
164
176
|
perms: encodeURIComponent(this.params.optionsModal.perms || ''),
|
|
165
177
|
};
|
|
166
178
|
|
|
167
|
-
return `nostrconnect://${pubkey}?image=${meta.icon}&url=${meta.url}&name=${meta.name}&perms=${meta.perms}&secret=${this.nostrConnectSecret}${
|
|
179
|
+
return `nostrconnect://${pubkey}?image=${meta.icon}&url=${meta.url}&name=${meta.name}&perms=${meta.perms}&secret=${this.nostrConnectSecret}${relay ? `&relay=${relay}` : ''}`;
|
|
168
180
|
}
|
|
169
181
|
|
|
170
182
|
public async getNostrConnectServices(): Promise<[string, ConnectionString[]]> {
|
|
@@ -184,35 +196,25 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
184
196
|
// }
|
|
185
197
|
|
|
186
198
|
for (const a of apps) {
|
|
187
|
-
let
|
|
188
|
-
|
|
199
|
+
let relay = DEFAULT_NOSTRCONNECT_RELAY;
|
|
189
200
|
if (a.link.startsWith('https://')) {
|
|
190
|
-
|
|
201
|
+
let domain = a.domain || new URL(a.link).hostname;
|
|
191
202
|
try {
|
|
192
203
|
const info = await (await fetch(`https://${domain}/.well-known/nostr.json`)).json();
|
|
193
204
|
const pubkey = info.names['_'];
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
relays = appRelays;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
a.iframeUrl = info.nip46?.iframe_url || '';
|
|
205
|
+
const relays = info.nip46[pubkey] as string[];
|
|
206
|
+
if (relays && relays.length) relay = relays[0];
|
|
207
|
+
a.iframeUrl = info.nip46.iframe_url || '';
|
|
201
208
|
} catch (e) {
|
|
202
209
|
console.log('Bad app info', e, a);
|
|
203
210
|
}
|
|
204
211
|
}
|
|
205
|
-
|
|
206
|
-
const relayParams = relays
|
|
207
|
-
.map(r => r.replace(/['"]/g, ''))
|
|
208
|
-
.map(r => `&relay=${encodeURIComponent(r)}`)
|
|
209
|
-
.join('');
|
|
210
|
-
|
|
211
|
-
const nc = nostrconnect + relayParams;
|
|
212
|
-
|
|
212
|
+
const nc = nostrconnect + '&relay=' + relay;
|
|
213
213
|
if (a.iframeUrl) {
|
|
214
|
+
// pass plain nc url for iframe-based flow
|
|
214
215
|
a.link = nc;
|
|
215
216
|
} else {
|
|
217
|
+
// we will open popup ourselves
|
|
216
218
|
a.link = a.link.replace('<nostrconnect>', nc);
|
|
217
219
|
}
|
|
218
220
|
}
|
|
@@ -238,7 +240,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
238
240
|
this.releaseSigner();
|
|
239
241
|
this.localSigner = new PrivateKeySigner(info.sk!);
|
|
240
242
|
|
|
241
|
-
if (signup) await createProfile(info, this.localSigner, this.params.optionsModal.signupRelays, this.params.optionsModal.outboxRelays);
|
|
243
|
+
if (signup) await createProfile(info, this.profileNdk, this.localSigner, this.params.optionsModal.signupRelays, this.params.optionsModal.outboxRelays);
|
|
242
244
|
|
|
243
245
|
this.onAuth(signup ? 'signup' : 'login', info);
|
|
244
246
|
}
|
|
@@ -253,18 +255,19 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
253
255
|
}
|
|
254
256
|
|
|
255
257
|
public async importAndConnect(cs: ConnectionString) {
|
|
256
|
-
const {
|
|
258
|
+
const { relay, domain, link, iframeUrl } = cs;
|
|
257
259
|
if (!domain) throw new Error('Domain required');
|
|
258
260
|
|
|
259
|
-
const info = await this.nostrConnect(
|
|
260
|
-
domain,
|
|
261
|
-
link,
|
|
262
|
-
importConnect: true,
|
|
263
|
-
iframeUrl,
|
|
264
|
-
});
|
|
261
|
+
const info = await this.nostrConnect(relay, { domain, link, importConnect: true, iframeUrl });
|
|
265
262
|
|
|
266
|
-
|
|
263
|
+
// logout to remove local keys from storage
|
|
264
|
+
// but keep the connect signer
|
|
265
|
+
await this.logout(/*keepSigner*/ true);
|
|
266
|
+
|
|
267
|
+
// release local one
|
|
267
268
|
this.localSigner = null;
|
|
269
|
+
|
|
270
|
+
// notify app that we've switched to 'connect' keys
|
|
268
271
|
this.onAuth('login', info);
|
|
269
272
|
}
|
|
270
273
|
|
|
@@ -291,15 +294,6 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
291
294
|
await this.endAuth();
|
|
292
295
|
}
|
|
293
296
|
|
|
294
|
-
public async setAmber(info: Info) {
|
|
295
|
-
// Amber now uses NIP-46 (nostrconnect) like other signers
|
|
296
|
-
this.releaseSigner();
|
|
297
|
-
await this.startAuth();
|
|
298
|
-
await this.initSigner(info);
|
|
299
|
-
this.onAuth('login', info);
|
|
300
|
-
await this.endAuth();
|
|
301
|
-
}
|
|
302
|
-
|
|
303
297
|
public async createAccount(nip05: string) {
|
|
304
298
|
const [name, domain] = nip05.split('@');
|
|
305
299
|
|
|
@@ -319,8 +313,8 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
319
313
|
const userPubkey = await this.signer!.createAccount2({ bunkerPubkey: info.signerPubkey!, name, domain, perms: this.params.optionsModal.perms });
|
|
320
314
|
|
|
321
315
|
return {
|
|
322
|
-
bunkerUrl: `bunker://${userPubkey}
|
|
323
|
-
sk: info.sk,
|
|
316
|
+
bunkerUrl: `bunker://${userPubkey}?relay=${info.relays?.[0]}`,
|
|
317
|
+
sk: info.sk, // reuse the same local key
|
|
324
318
|
};
|
|
325
319
|
}
|
|
326
320
|
|
|
@@ -328,6 +322,11 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
328
322
|
this.signer = null;
|
|
329
323
|
this.signerErrCallback?.('cancelled');
|
|
330
324
|
this.localSigner = null;
|
|
325
|
+
|
|
326
|
+
// disconnect from signer relays
|
|
327
|
+
for (const r of this.ndk.pool.relays.keys()) {
|
|
328
|
+
this.ndk.pool.removeRelay(r);
|
|
329
|
+
}
|
|
331
330
|
}
|
|
332
331
|
|
|
333
332
|
public async logout(keepSigner = false) {
|
|
@@ -372,7 +371,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
372
371
|
|
|
373
372
|
if (info) {
|
|
374
373
|
// async profile fetch
|
|
375
|
-
fetchProfile(info,
|
|
374
|
+
fetchProfile(info, this.profileNdk).then(p => {
|
|
376
375
|
if (this.params.userInfo !== info) return;
|
|
377
376
|
|
|
378
377
|
const userInfo = {
|
|
@@ -501,7 +500,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
501
500
|
return !!this.readyCallback;
|
|
502
501
|
}
|
|
503
502
|
|
|
504
|
-
public startAuth() {
|
|
503
|
+
public async startAuth() {
|
|
505
504
|
console.log('startAuth');
|
|
506
505
|
if (this.readyCallback) throw new Error('Already started');
|
|
507
506
|
|
|
@@ -562,71 +561,24 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
562
561
|
// FIXME shouldn't this come from nostrconnect service list?
|
|
563
562
|
this.emit('onIframeUrl', info.iframeUrl);
|
|
564
563
|
|
|
564
|
+
// AbortControllerでキャンセル可能にする
|
|
565
|
+
this.signerAbortController = new AbortController();
|
|
566
|
+
const abortPromise = new Promise<never>((_, reject) => {
|
|
567
|
+
this.signerAbortController!.signal.addEventListener('abort', () => {
|
|
568
|
+
reject(new Error('Cancelled by user'));
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
|
|
565
572
|
this.signerPromise = new Promise<void>(async (ok, err) => {
|
|
566
573
|
this.signerErrCallback = err;
|
|
567
574
|
try {
|
|
568
|
-
//
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
if (info.iframeUrl) {
|
|
572
|
-
// use NDK-free iframe signer implementation (MessagePort + SimplePool)
|
|
573
|
-
this.signer = new Nip46Signer(localSigner, info.signerPubkey!, iframeOrigin, info.relays || []);
|
|
574
|
-
|
|
575
|
-
// we should notify the banner the same way as the onAuthUrl does
|
|
576
|
-
this.signer.on(`iframeRestart`, async () => {
|
|
577
|
-
const iframeUrl = info.iframeUrl + (info.iframeUrl!.includes('?') ? '&' : '?') + 'pubkey=' + info.pubkey + '&rebind=' + localSigner.pubkey;
|
|
578
|
-
this.emit('iframeRestart', { pubkey: info.pubkey, iframeUrl });
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
// OAuth flow
|
|
582
|
-
this.signer.on('authUrl', (url: string) => {
|
|
583
|
-
console.log('nostr login auth url', url);
|
|
584
|
-
|
|
585
|
-
// notify our UI
|
|
586
|
-
this.emit('onAuthUrl', { url, iframeUrl: info.iframeUrl, eventToAddAccount });
|
|
587
|
-
});
|
|
588
|
-
} else {
|
|
589
|
-
// New SimplePool-based NIP-46 flow
|
|
590
|
-
const client = new Nip46Client({
|
|
591
|
-
localPrivateKey: info.sk!,
|
|
592
|
-
remotePubkey: info.signerPubkey!,
|
|
593
|
-
relays: info.relays || [],
|
|
594
|
-
timeoutMs: 30000,
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
const adapter = new Nip46Adapter(client, localSigner);
|
|
598
|
-
this.signer = adapter;
|
|
599
|
-
|
|
600
|
-
// OAuth flow: forward authUrl events
|
|
601
|
-
this.signer.on('authUrl', (url: string) => {
|
|
602
|
-
console.log('nostr login auth url', url);
|
|
603
|
-
this.emit('onAuthUrl', { url, iframeUrl: info.iframeUrl, eventToAddAccount });
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
if (listen) {
|
|
607
|
-
// nostrconnect: flow
|
|
608
|
-
// wait for the incoming message from signer
|
|
609
|
-
await this.listen(info);
|
|
610
|
-
} else if (connect) {
|
|
611
|
-
// bunker: flow
|
|
612
|
-
// send 'connect' message to signer
|
|
613
|
-
await this.connect(info, this.params.optionsModal.perms);
|
|
614
|
-
} else {
|
|
615
|
-
// provide saved pubkey as a hint
|
|
616
|
-
await this.signer!.initUserPubkey(info.pubkey);
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// ensure, we're using it in callbacks above
|
|
620
|
-
// and expect info to be valid after this call
|
|
621
|
-
info.pubkey = this.signer!.userPubkey;
|
|
622
|
-
// learned after nostrconnect flow
|
|
623
|
-
info.signerPubkey = this.signer!.remotePubkey;
|
|
624
|
-
|
|
625
|
-
ok();
|
|
575
|
+
// タイムアウトとキャンセルの両方に対応
|
|
576
|
+
await Promise.race([this.initSignerInternal(info, listen, connect, eventToAddAccount, ok), abortPromise]);
|
|
626
577
|
} catch (e) {
|
|
627
578
|
console.log('initSigner failure', e);
|
|
628
579
|
// make sure signer isn't set
|
|
629
580
|
this.signer = null;
|
|
581
|
+
this.signerAbortController = undefined;
|
|
630
582
|
err(e);
|
|
631
583
|
}
|
|
632
584
|
});
|
|
@@ -634,9 +586,73 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
634
586
|
return this.signerPromise;
|
|
635
587
|
}
|
|
636
588
|
|
|
589
|
+
private async initSignerInternal(info: Info, listen: boolean, connect: boolean, eventToAddAccount: boolean, resolve: () => void) {
|
|
590
|
+
// pre-connect if we're creating the connection (listen|connect) or
|
|
591
|
+
// not iframe mode
|
|
592
|
+
if (info.relays && !info.iframeUrl) {
|
|
593
|
+
for (const r of info.relays) {
|
|
594
|
+
this.ndk.addExplicitRelay(r, undefined);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// wait until we connect, otherwise
|
|
599
|
+
// signer won't start properly
|
|
600
|
+
await this.ndk.connect();
|
|
601
|
+
|
|
602
|
+
// create and prepare the signer
|
|
603
|
+
const localSigner = new PrivateKeySigner(info.sk!);
|
|
604
|
+
this.signer = new Nip46Signer(this.ndk, localSigner, info.signerPubkey!, info.iframeUrl ? new URL(info.iframeUrl!).origin : undefined);
|
|
605
|
+
|
|
606
|
+
// we should notify the banner the same way as
|
|
607
|
+
// the onAuthUrl does
|
|
608
|
+
this.signer.on(`iframeRestart`, async () => {
|
|
609
|
+
const iframeUrl = info.iframeUrl + (info.iframeUrl!.includes('?') ? '&' : '?') + 'pubkey=' + info.pubkey + '&rebind=' + localSigner.pubkey;
|
|
610
|
+
this.emit('iframeRestart', { pubkey: info.pubkey, iframeUrl });
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// OAuth flow
|
|
614
|
+
// if (!listen) {
|
|
615
|
+
this.signer.on('authUrl', (url: string) => {
|
|
616
|
+
console.log('nostr login auth url', url);
|
|
617
|
+
|
|
618
|
+
// notify our UI
|
|
619
|
+
this.emit('onAuthUrl', { url, iframeUrl: info.iframeUrl, eventToAddAccount });
|
|
620
|
+
});
|
|
621
|
+
// }
|
|
622
|
+
|
|
623
|
+
if (listen) {
|
|
624
|
+
// nostrconnect: flow
|
|
625
|
+
// wait for the incoming message from signer
|
|
626
|
+
await this.listen(info);
|
|
627
|
+
} else if (connect) {
|
|
628
|
+
// bunker: flow
|
|
629
|
+
// send 'connect' message to signer
|
|
630
|
+
await this.connect(info, this.params.optionsModal.perms);
|
|
631
|
+
} else {
|
|
632
|
+
// provide saved pubkey as a hint
|
|
633
|
+
await this.signer!.initUserPubkey(info.pubkey);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// ensure, we're using it in callbacks above
|
|
637
|
+
// and expect info to be valid after this call
|
|
638
|
+
info.pubkey = this.signer!.userPubkey;
|
|
639
|
+
// learned after nostrconnect flow
|
|
640
|
+
info.signerPubkey = this.signer!.remotePubkey;
|
|
641
|
+
|
|
642
|
+
this.signerAbortController = undefined;
|
|
643
|
+
resolve();
|
|
644
|
+
}
|
|
645
|
+
|
|
637
646
|
public async authNip46(
|
|
638
647
|
type: 'login' | 'signup',
|
|
639
|
-
{
|
|
648
|
+
{
|
|
649
|
+
name,
|
|
650
|
+
bunkerUrl,
|
|
651
|
+
sk = '',
|
|
652
|
+
domain = '',
|
|
653
|
+
iframeUrl = '',
|
|
654
|
+
customRelays,
|
|
655
|
+
}: { name: string; bunkerUrl: string; sk?: string; domain?: string; iframeUrl?: string; customRelays?: string[] },
|
|
640
656
|
) {
|
|
641
657
|
try {
|
|
642
658
|
const info = bunkerUrlToInfo(bunkerUrl, sk);
|
|
@@ -648,8 +664,13 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
648
664
|
if (domain) info.domain = domain;
|
|
649
665
|
if (iframeUrl) info.iframeUrl = iframeUrl;
|
|
650
666
|
|
|
667
|
+
// カスタムリレーが指定されていれば使用する
|
|
668
|
+
if (customRelays && customRelays.length > 0) {
|
|
669
|
+
info.relays = customRelays;
|
|
670
|
+
}
|
|
671
|
+
|
|
651
672
|
// console.log('nostr login auth info', info);
|
|
652
|
-
if (!info.signerPubkey || !info.sk || !info.relays
|
|
673
|
+
if (!info.signerPubkey || !info.sk || !info.relays?.[0]) {
|
|
653
674
|
throw new Error(`Bad bunker url ${bunkerUrl}`);
|
|
654
675
|
}
|
|
655
676
|
|
|
@@ -670,33 +691,22 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
670
691
|
}
|
|
671
692
|
|
|
672
693
|
public async signEvent(event: any) {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
}
|
|
685
|
-
return event;
|
|
686
|
-
})();
|
|
687
|
-
|
|
688
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
689
|
-
setTimeout(() => reject(new Error('Sign timeout')), timeoutMs);
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
const result = await Promise.race([signPromise, timeoutPromise]);
|
|
693
|
-
console.log('signed', { event: result });
|
|
694
|
-
return result;
|
|
694
|
+
if (this.localSigner) {
|
|
695
|
+
event.pubkey = getPublicKey(this.localSigner.privateKey!);
|
|
696
|
+
event.id = getEventHash(event);
|
|
697
|
+
event.sig = await this.localSigner.sign(event);
|
|
698
|
+
} else {
|
|
699
|
+
event.pubkey = this.signer?.remotePubkey;
|
|
700
|
+
event.id = getEventHash(event);
|
|
701
|
+
event.sig = await this.signer?.sign(event);
|
|
702
|
+
}
|
|
703
|
+
console.log('signed', { event });
|
|
704
|
+
return event;
|
|
695
705
|
}
|
|
696
706
|
|
|
697
707
|
private async codec_call(method: string, pubkey: string, param: string) {
|
|
698
708
|
return new Promise<string>((resolve, reject) => {
|
|
699
|
-
this.signer!.rpc.sendRequest(this.signer!.remotePubkey!, method, [pubkey, param], 24133, (response:
|
|
709
|
+
this.signer!.rpc.sendRequest(this.signer!.remotePubkey!, method, [pubkey, param], 24133, (response: NDKRpcResponse) => {
|
|
700
710
|
if (!response.error) {
|
|
701
711
|
resolve(response.result);
|
|
702
712
|
} else {
|
|
@@ -708,27 +718,20 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
708
718
|
|
|
709
719
|
public async encrypt04(pubkey: string, plaintext: string) {
|
|
710
720
|
if (this.localSigner) {
|
|
711
|
-
return this.localSigner.encrypt(pubkey, plaintext);
|
|
721
|
+
return this.localSigner.encrypt(new NDKUser({ pubkey }), plaintext);
|
|
712
722
|
} else {
|
|
713
|
-
|
|
714
|
-
if (this.signer && typeof this.signer.encrypt === 'function') {
|
|
715
|
-
return this.signer.encrypt(pubkey, plaintext);
|
|
716
|
-
}
|
|
717
|
-
// fallback to remote codec via signer RPC
|
|
718
|
-
return this.codec_call('nip04_encrypt', pubkey, plaintext);
|
|
723
|
+
return this.signer!.encrypt(new NDKUser({ pubkey }), plaintext);
|
|
719
724
|
}
|
|
720
725
|
}
|
|
721
726
|
|
|
722
727
|
public async decrypt04(pubkey: string, ciphertext: string) {
|
|
723
728
|
if (this.localSigner) {
|
|
724
|
-
return this.localSigner.decrypt(pubkey, ciphertext);
|
|
729
|
+
return this.localSigner.decrypt(new NDKUser({ pubkey }), ciphertext);
|
|
725
730
|
} else {
|
|
726
|
-
//
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
}
|
|
731
|
+
// decrypt is broken in ndk v2.3.1, and latest
|
|
732
|
+
// ndk v2.8.1 doesn't allow to override connect easily,
|
|
733
|
+
// so we reimplement and fix decrypt here as a temporary fix
|
|
730
734
|
|
|
731
|
-
// fallback to remote codec via signer RPC
|
|
732
735
|
return this.codec_call('nip04_decrypt', pubkey, ciphertext);
|
|
733
736
|
}
|
|
734
737
|
}
|
|
@@ -737,7 +740,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
737
740
|
if (this.localSigner) {
|
|
738
741
|
return this.nip44Codec.encrypt(this.localSigner.privateKey!, pubkey, plaintext);
|
|
739
742
|
} else {
|
|
740
|
-
// no support of nip44 in
|
|
743
|
+
// no support of nip44 in ndk yet
|
|
741
744
|
return this.codec_call('nip44_encrypt', pubkey, plaintext);
|
|
742
745
|
}
|
|
743
746
|
}
|
|
@@ -746,7 +749,7 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
746
749
|
if (this.localSigner) {
|
|
747
750
|
return this.nip44Codec.decrypt(this.localSigner.privateKey!, pubkey, ciphertext);
|
|
748
751
|
} else {
|
|
749
|
-
// no support of nip44 in
|
|
752
|
+
// no support of nip44 in ndk yet
|
|
750
753
|
return this.codec_call('nip44_decrypt', pubkey, ciphertext);
|
|
751
754
|
}
|
|
752
755
|
}
|