@konemono/nostr-login 1.7.27 → 1.7.29
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/modules/Nip46.ts +69 -475
- package/src/modules/ProcessManager.ts +28 -20
package/package.json
CHANGED
package/src/modules/Nip46.ts
CHANGED
|
@@ -1,579 +1,173 @@
|
|
|
1
|
-
|
|
1
|
+
// Nip46.ts
|
|
2
|
+
import NDK, { NDKEvent, NDKFilter, NDKNip46Signer, NDKNostrRpc, NDKRpcRequest, NDKRpcResponse, NDKSubscription, NostrEvent } from '@nostr-dev-kit/ndk';
|
|
2
3
|
import { validateEvent, verifySignature } from 'nostr-tools';
|
|
3
4
|
import { PrivateKeySigner } from './Signer';
|
|
4
5
|
import { NIP46_TIMEOUT } from '../const';
|
|
5
6
|
import ProcessManager from './ProcessManager';
|
|
6
7
|
|
|
8
|
+
/* =========================
|
|
9
|
+
* NostrRpc
|
|
10
|
+
* ========================= */
|
|
7
11
|
class NostrRpc extends NDKNostrRpc {
|
|
8
|
-
protected processManager
|
|
12
|
+
protected processManager: ProcessManager;
|
|
9
13
|
protected _ndk: NDK;
|
|
10
14
|
protected _signer: PrivateKeySigner;
|
|
11
15
|
protected requests: Set<string> = new Set();
|
|
12
16
|
protected requestTimeouts: Map<string, NodeJS.Timeout> = new Map();
|
|
13
17
|
private sub?: NDKSubscription;
|
|
14
|
-
protected _useNip44
|
|
18
|
+
protected _useNip44 = false;
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
constructor(ndk: NDK, signer: PrivateKeySigner, processManager: ProcessManager) {
|
|
17
21
|
super(ndk, signer, ndk.debug.extend('nip46:signer:rpc'));
|
|
18
22
|
this._ndk = ndk;
|
|
19
23
|
this._signer = signer;
|
|
20
24
|
this.processManager = processManager;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
async subscribe(filter: NDKFilter): Promise<NDKSubscription> {
|
|
24
28
|
filter.kinds = filter.kinds?.filter(k => k === 24133);
|
|
25
29
|
this.sub = await super.subscribe(filter);
|
|
26
30
|
return this.sub;
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
this.
|
|
33
|
+
stop() {
|
|
34
|
+
for (const t of this.requestTimeouts.values()) clearTimeout(t);
|
|
35
|
+
this.requestTimeouts.clear();
|
|
31
36
|
if (this.sub) {
|
|
32
37
|
this.sub.stop();
|
|
33
38
|
this.sub = undefined;
|
|
34
39
|
}
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
private clearAllTimeouts() {
|
|
38
|
-
for (const timeout of this.requestTimeouts.values()) {
|
|
39
|
-
clearTimeout(timeout);
|
|
40
|
-
}
|
|
41
|
-
this.requestTimeouts.clear();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
42
|
protected clearTimeout(id: string) {
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
this.requestTimeouts.delete(id);
|
|
49
|
-
}
|
|
43
|
+
const t = this.requestTimeouts.get(id);
|
|
44
|
+
if (t) clearTimeout(t);
|
|
45
|
+
this.requestTimeouts.delete(id);
|
|
50
46
|
}
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
this._useNip44 =
|
|
48
|
+
setUseNip44(v: boolean) {
|
|
49
|
+
this._useNip44 = v;
|
|
54
50
|
}
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
const l = ciphertext.length;
|
|
58
|
-
if (l < 28) return false;
|
|
59
|
-
return ciphertext[l - 28] === '?' && ciphertext[l - 27] === 'i' && ciphertext[l - 26] === 'v' && ciphertext[l - 25] === '=';
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
public async parseEvent(event: NDKEvent): Promise<NDKRpcRequest | NDKRpcResponse> {
|
|
52
|
+
async parseEvent(event: NDKEvent): Promise<NDKRpcRequest | NDKRpcResponse> {
|
|
63
53
|
const remoteUser = this._ndk.getUser({ pubkey: event.pubkey });
|
|
64
54
|
remoteUser.ndk = this._ndk;
|
|
65
|
-
const decrypt = this.isNip04(event.content) ? this._signer.decrypt : this._signer.decryptNip44;
|
|
66
|
-
const decryptedContent = await decrypt.call(this._signer, remoteUser, event.content);
|
|
67
|
-
const parsedContent = JSON.parse(decryptedContent);
|
|
68
|
-
const { id, method, params, result, error } = parsedContent;
|
|
69
|
-
|
|
70
|
-
if (method) {
|
|
71
|
-
return { id, pubkey: event.pubkey, method, params, event };
|
|
72
|
-
} else {
|
|
73
|
-
return { id, result, error, event };
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
55
|
|
|
77
|
-
|
|
78
|
-
const event = new NDKEvent(this._ndk, reply);
|
|
79
|
-
const parsedEvent = await this.parseEvent(event);
|
|
80
|
-
console.log('nostr connect parsedEvent', parsedEvent);
|
|
81
|
-
if (!(parsedEvent as NDKRpcRequest).method) {
|
|
82
|
-
const response = parsedEvent as NDKRpcResponse;
|
|
83
|
-
if (response.result !== secret) throw new Error(response.error);
|
|
84
|
-
return event.pubkey;
|
|
85
|
-
} else {
|
|
86
|
-
throw new Error('Bad nostr connect reply');
|
|
87
|
-
}
|
|
88
|
-
}
|
|
56
|
+
const decrypted = await this._signer.decrypt.call(this._signer, remoteUser, event.content);
|
|
89
57
|
|
|
90
|
-
|
|
91
|
-
const pubkey = this._signer.pubkey;
|
|
92
|
-
console.log('nostr-login listening for conn to', pubkey);
|
|
93
|
-
const sub = await this.subscribe({
|
|
94
|
-
'kinds': [24133],
|
|
95
|
-
'#p': [pubkey],
|
|
96
|
-
});
|
|
97
|
-
return new Promise<string>((ok, err) => {
|
|
98
|
-
const timeoutId = setTimeout(() => {
|
|
99
|
-
this.stop();
|
|
100
|
-
err(new Error('NIP46 listen timeout'));
|
|
101
|
-
}, NIP46_TIMEOUT);
|
|
102
|
-
|
|
103
|
-
sub.on('event', async (event: NDKEvent) => {
|
|
104
|
-
try {
|
|
105
|
-
const parsedEvent = await this.parseEvent(event);
|
|
106
|
-
if (!(parsedEvent as NDKRpcRequest).method) {
|
|
107
|
-
const response = parsedEvent as NDKRpcResponse;
|
|
108
|
-
|
|
109
|
-
if (response.result === 'auth_url') return;
|
|
110
|
-
|
|
111
|
-
if (response.result === 'ack' || response.result === nostrConnectSecret) {
|
|
112
|
-
clearTimeout(timeoutId);
|
|
113
|
-
ok(event.pubkey);
|
|
114
|
-
} else {
|
|
115
|
-
clearTimeout(timeoutId);
|
|
116
|
-
err(response.error);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
} catch (e) {
|
|
120
|
-
console.log('error parsing event', e, event.rawEvent());
|
|
121
|
-
}
|
|
122
|
-
this.stop();
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
}
|
|
58
|
+
const { id, method, params, result, error } = JSON.parse(decrypted);
|
|
126
59
|
|
|
127
|
-
|
|
128
|
-
return new Promise<void>((ok, err) => {
|
|
129
|
-
const connectParams = [pubkey!, token || '', perms || ''];
|
|
130
|
-
|
|
131
|
-
const timeoutId = setTimeout(() => {
|
|
132
|
-
err(new Error('NIP46 connect timeout'));
|
|
133
|
-
}, NIP46_TIMEOUT);
|
|
134
|
-
|
|
135
|
-
this.sendRequest(pubkey!, 'connect', connectParams, 24133, (response: NDKRpcResponse) => {
|
|
136
|
-
clearTimeout(timeoutId);
|
|
137
|
-
if (response.result === 'ack') {
|
|
138
|
-
ok();
|
|
139
|
-
} else {
|
|
140
|
-
err(response.error);
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
});
|
|
60
|
+
return method ? { id, pubkey: event.pubkey, method, params, event } : { id, result, error, event };
|
|
144
61
|
}
|
|
145
62
|
|
|
146
|
-
protected getId()
|
|
147
|
-
return Math.random().toString(36).
|
|
63
|
+
protected getId() {
|
|
64
|
+
return Math.random().toString(36).slice(2);
|
|
148
65
|
}
|
|
149
66
|
|
|
150
|
-
protected async ensureConnected()
|
|
67
|
+
protected async ensureConnected() {
|
|
151
68
|
const relays = Array.from(this._ndk.pool.relays.values());
|
|
152
|
-
|
|
153
|
-
'Checking relay connections:',
|
|
154
|
-
relays.map(r => ({ url: r.url, status: r.status })),
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
const connectedRelays = relays.filter(r => r.status === 1);
|
|
158
|
-
|
|
159
|
-
if (connectedRelays.length === 0) {
|
|
160
|
-
console.log('No connected relays, forcing reconnection...');
|
|
161
|
-
|
|
162
|
-
// 既存の接続を全てクリーンアップ
|
|
163
|
-
for (const relay of relays) {
|
|
164
|
-
try {
|
|
165
|
-
await relay.disconnect();
|
|
166
|
-
} catch (e) {
|
|
167
|
-
console.log('Error disconnecting relay:', relay.url, e);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
69
|
+
if (relays.some(r => r.status === 1)) return;
|
|
170
70
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
await new Promise<void>((resolve, reject) => {
|
|
176
|
-
const timeout = setTimeout(() => {
|
|
177
|
-
const status = Array.from(this._ndk.pool.relays.values()).map(r => ({ url: r.url, status: r.status }));
|
|
178
|
-
console.error('Failed to reconnect to relays within 10s. Status:', status);
|
|
179
|
-
reject(new Error('Failed to reconnect to relays'));
|
|
180
|
-
}, 10000);
|
|
181
|
-
|
|
182
|
-
const checkConnection = () => {
|
|
183
|
-
const connected = Array.from(this._ndk.pool.relays.values()).filter(r => r.status === 1);
|
|
184
|
-
if (connected.length > 0) {
|
|
185
|
-
clearTimeout(timeout);
|
|
186
|
-
console.log(
|
|
187
|
-
'Successfully reconnected to',
|
|
188
|
-
connected.length,
|
|
189
|
-
'relays:',
|
|
190
|
-
connected.map(r => r.url),
|
|
191
|
-
);
|
|
192
|
-
resolve();
|
|
193
|
-
} else {
|
|
194
|
-
setTimeout(checkConnection, 200);
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
checkConnection();
|
|
198
|
-
});
|
|
199
|
-
} else {
|
|
200
|
-
console.log(
|
|
201
|
-
'Already connected to',
|
|
202
|
-
connectedRelays.length,
|
|
203
|
-
'relays:',
|
|
204
|
-
connectedRelays.map(r => r.url),
|
|
205
|
-
);
|
|
71
|
+
for (const r of relays) {
|
|
72
|
+
try {
|
|
73
|
+
await r.disconnect();
|
|
74
|
+
} catch {}
|
|
206
75
|
}
|
|
207
|
-
}
|
|
208
76
|
|
|
209
|
-
|
|
210
|
-
|
|
77
|
+
await this._ndk.connect();
|
|
78
|
+
}
|
|
211
79
|
|
|
80
|
+
async sendRequest(remotePubkey: string, method: string, params: string[] = [], kind = 24133, cb?: (r: NDKRpcResponse) => void): Promise<NDKRpcResponse> {
|
|
81
|
+
this.processManager.pause();
|
|
212
82
|
try {
|
|
213
|
-
this.processManager?.pause();
|
|
214
83
|
await this.ensureConnected();
|
|
215
|
-
} catch (e) {
|
|
216
|
-
console.error('Failed to ensure connection:', e);
|
|
217
|
-
if (cb) {
|
|
218
|
-
cb({
|
|
219
|
-
id: '',
|
|
220
|
-
result: '',
|
|
221
|
-
error: 'Failed to connect to relays: ' + (e as Error).message,
|
|
222
|
-
event: undefined as any,
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
throw e;
|
|
226
84
|
} finally {
|
|
227
|
-
this.processManager
|
|
85
|
+
this.processManager.resume();
|
|
228
86
|
}
|
|
229
87
|
|
|
230
88
|
const id = this.getId();
|
|
231
|
-
|
|
232
89
|
this.setResponseHandler(id, cb);
|
|
233
90
|
|
|
234
91
|
const event = await this.createRequestEvent(id, remotePubkey, method, params, kind);
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
try {
|
|
238
|
-
await event.publish();
|
|
239
|
-
console.log('sendRequest event published successfully');
|
|
240
|
-
} catch (e) {
|
|
241
|
-
console.error('Failed to publish event:', e);
|
|
242
|
-
this.clearTimeout(id);
|
|
243
|
-
this.requests.delete(id);
|
|
244
|
-
if (cb) {
|
|
245
|
-
cb({
|
|
246
|
-
id,
|
|
247
|
-
result: '',
|
|
248
|
-
error: 'Failed to publish event: ' + (e as Error).message,
|
|
249
|
-
event: undefined as any,
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
throw e;
|
|
253
|
-
}
|
|
92
|
+
await event.publish();
|
|
254
93
|
|
|
255
94
|
// @ts-ignore
|
|
256
|
-
return undefined
|
|
95
|
+
return undefined;
|
|
257
96
|
}
|
|
258
97
|
|
|
259
|
-
protected setResponseHandler(id: string, cb?: (
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const timeoutId = setTimeout(() => {
|
|
264
|
-
if (this.requests.has(id)) {
|
|
265
|
-
this.requests.delete(id);
|
|
266
|
-
this.requestTimeouts.delete(id);
|
|
267
|
-
console.log('NIP46 request timeout for', id);
|
|
268
|
-
if (cb) {
|
|
269
|
-
cb({
|
|
270
|
-
id,
|
|
271
|
-
result: '',
|
|
272
|
-
error: 'NIP46 request timeout',
|
|
273
|
-
event: undefined as any,
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
}
|
|
98
|
+
protected setResponseHandler(id: string, cb?: (r: NDKRpcResponse) => void) {
|
|
99
|
+
const to = setTimeout(() => {
|
|
100
|
+
this.requests.delete(id);
|
|
101
|
+
cb?.({ id, result: '', error: 'timeout', event: undefined as any });
|
|
277
102
|
}, NIP46_TIMEOUT);
|
|
278
103
|
|
|
279
|
-
this.requestTimeouts.set(id,
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (!authUrlSent) {
|
|
286
|
-
authUrlSent = true;
|
|
287
|
-
this.emit('authUrl', response.error);
|
|
288
|
-
}
|
|
289
|
-
} else if (cb) {
|
|
290
|
-
if (this.requests.has(id)) {
|
|
291
|
-
this.clearTimeout(id);
|
|
292
|
-
this.requests.delete(id);
|
|
293
|
-
console.log('nostr-login processed nip46 request in', Date.now() - now, 'ms');
|
|
294
|
-
cb(response);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
this.once(`response-${id}`, responseHandler);
|
|
104
|
+
this.requestTimeouts.set(id, to);
|
|
105
|
+
|
|
106
|
+
this.once(`response-${id}`, r => {
|
|
107
|
+
this.clearTimeout(id);
|
|
108
|
+
this.requests.delete(id);
|
|
109
|
+
cb?.(r);
|
|
300
110
|
});
|
|
301
111
|
}
|
|
302
112
|
|
|
303
|
-
protected async createRequestEvent(id: string, remotePubkey: string, method: string, params: string[]
|
|
113
|
+
protected async createRequestEvent(id: string, remotePubkey: string, method: string, params: string[], kind: number) {
|
|
304
114
|
this.requests.add(id);
|
|
305
115
|
const localUser = await this._signer.user();
|
|
306
116
|
const remoteUser = this._ndk.getUser({ pubkey: remotePubkey });
|
|
307
|
-
const request = { id, method, params };
|
|
308
117
|
|
|
309
118
|
const event = new NDKEvent(this._ndk, {
|
|
310
119
|
kind,
|
|
311
|
-
content: JSON.stringify(
|
|
120
|
+
content: JSON.stringify({ id, method, params }),
|
|
312
121
|
tags: [['p', remotePubkey]],
|
|
313
122
|
pubkey: localUser.pubkey,
|
|
314
123
|
} as NostrEvent);
|
|
315
124
|
|
|
316
|
-
|
|
317
|
-
const encrypt = useNip44 ? this._signer.encryptNip44 : this._signer.encrypt;
|
|
318
|
-
event.content = await encrypt.call(this._signer, remoteUser, event.content);
|
|
319
|
-
await event.sign(this._signer);
|
|
125
|
+
event.content = await this._signer.encrypt.call(this._signer, remoteUser, event.content);
|
|
320
126
|
|
|
127
|
+
await event.sign(this._signer);
|
|
321
128
|
return event;
|
|
322
129
|
}
|
|
323
130
|
}
|
|
324
131
|
|
|
132
|
+
/* =========================
|
|
133
|
+
* IframeNostrRpc
|
|
134
|
+
* ========================= */
|
|
325
135
|
export class IframeNostrRpc extends NostrRpc {
|
|
326
|
-
private peerOrigin?: string;
|
|
327
136
|
private iframePort?: MessagePort;
|
|
328
|
-
private iframeRequests = new Map<string, { id: string; pubkey: string }>();
|
|
329
|
-
|
|
330
|
-
public constructor(ndk: NDK, localSigner: PrivateKeySigner, iframePeerOrigin?: string) {
|
|
331
|
-
super(ndk, localSigner);
|
|
332
|
-
this._ndk = ndk;
|
|
333
|
-
this.peerOrigin = iframePeerOrigin;
|
|
334
|
-
}
|
|
335
137
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
return new NDKSubscription(
|
|
339
|
-
this._ndk,
|
|
340
|
-
{},
|
|
341
|
-
{
|
|
342
|
-
closeOnEose: true,
|
|
343
|
-
cacheUsage: NDKSubscriptionCacheUsage.ONLY_CACHE,
|
|
344
|
-
},
|
|
345
|
-
);
|
|
138
|
+
constructor(ndk: NDK, signer: PrivateKeySigner, processManager: ProcessManager) {
|
|
139
|
+
super(ndk, signer, processManager);
|
|
346
140
|
}
|
|
347
141
|
|
|
348
|
-
|
|
349
|
-
if (!this.peerOrigin) throw new Error('Unexpected iframe port');
|
|
350
|
-
|
|
142
|
+
setWorkerIframePort(port: MessagePort) {
|
|
351
143
|
this.iframePort = port;
|
|
352
144
|
|
|
353
|
-
setInterval(() => {
|
|
354
|
-
console.log('iframe-nip46 ping');
|
|
355
|
-
this.iframePort!.postMessage('ping');
|
|
356
|
-
}, 5000);
|
|
357
|
-
|
|
358
145
|
port.onmessage = async ev => {
|
|
359
|
-
|
|
360
|
-
if (
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
try {
|
|
368
|
-
const event = ev.data;
|
|
369
|
-
|
|
370
|
-
if (!validateEvent(event)) throw new Error('Invalid event from iframe');
|
|
371
|
-
if (!verifySignature(event)) throw new Error('Invalid event signature from iframe');
|
|
372
|
-
const nevent = new NDKEvent(this._ndk, event);
|
|
373
|
-
const parsedEvent = await this.parseEvent(nevent);
|
|
374
|
-
if (!(parsedEvent as NDKRpcRequest).method) {
|
|
375
|
-
console.log('parsed response', parsedEvent);
|
|
376
|
-
this.emit(`response-${parsedEvent.id}`, parsedEvent);
|
|
377
|
-
}
|
|
378
|
-
} catch (e) {
|
|
379
|
-
console.log('error parsing event', e, ev.data);
|
|
146
|
+
const event = ev.data;
|
|
147
|
+
if (!validateEvent(event) || !verifySignature(event)) return;
|
|
148
|
+
const parsed = await this.parseEvent(new NDKEvent(this._ndk, event));
|
|
149
|
+
if (!('method' in parsed)) {
|
|
150
|
+
this.emit(`response-${parsed.id}`, parsed);
|
|
380
151
|
}
|
|
381
152
|
};
|
|
382
153
|
}
|
|
383
|
-
|
|
384
|
-
public async sendRequest(remotePubkey: string, method: string, params: string[] = [], kind = 24133, cb?: (res: NDKRpcResponse) => void): Promise<NDKRpcResponse> {
|
|
385
|
-
console.log('IframeNostrRpc.sendRequest called:', method, 'iframePort:', !!this.iframePort);
|
|
386
|
-
|
|
387
|
-
if (!this.iframePort) {
|
|
388
|
-
try {
|
|
389
|
-
this.processManager?.pause();
|
|
390
|
-
await this.ensureConnected();
|
|
391
|
-
} catch (e) {
|
|
392
|
-
console.error('Failed to ensure connection:', e);
|
|
393
|
-
if (cb) {
|
|
394
|
-
cb({
|
|
395
|
-
id: '',
|
|
396
|
-
result: '',
|
|
397
|
-
error: 'Failed to connect to relays: ' + (e as Error).message,
|
|
398
|
-
event: undefined as any,
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
throw e;
|
|
402
|
-
} finally {
|
|
403
|
-
this.processManager?.resume();
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
const id = this.getId();
|
|
408
|
-
|
|
409
|
-
const event = await this.createRequestEvent(id, remotePubkey, method, params, kind);
|
|
410
|
-
|
|
411
|
-
this.setResponseHandler(id, cb);
|
|
412
|
-
|
|
413
|
-
if (this.iframePort) {
|
|
414
|
-
this.iframeRequests.set(event.id, { id, pubkey: remotePubkey });
|
|
415
|
-
|
|
416
|
-
console.log('iframe-nip46 sending request to', this.peerOrigin, event.rawEvent());
|
|
417
|
-
this.iframePort.postMessage(event.rawEvent());
|
|
418
|
-
} else {
|
|
419
|
-
try {
|
|
420
|
-
await event.publish();
|
|
421
|
-
console.log('Request published to relays successfully');
|
|
422
|
-
} catch (e) {
|
|
423
|
-
console.error('Failed to publish event:', e);
|
|
424
|
-
this.clearTimeout(id);
|
|
425
|
-
this.requests.delete(id);
|
|
426
|
-
if (cb) {
|
|
427
|
-
cb({
|
|
428
|
-
id,
|
|
429
|
-
result: '',
|
|
430
|
-
error: 'Failed to publish event: ' + (e as Error).message,
|
|
431
|
-
event: undefined as any,
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
throw e;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// @ts-ignore
|
|
439
|
-
return undefined as NDKRpcResponse;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
export class ReadyListener {
|
|
444
|
-
origin: string;
|
|
445
|
-
messages: string[];
|
|
446
|
-
promise: Promise<any>;
|
|
447
|
-
|
|
448
|
-
constructor(messages: string[], origin: string) {
|
|
449
|
-
this.origin = origin;
|
|
450
|
-
this.messages = messages;
|
|
451
|
-
this.promise = new Promise<any>(ok => {
|
|
452
|
-
console.log(new Date(), 'started listener for', this.messages);
|
|
453
|
-
|
|
454
|
-
const onReady = async (e: MessageEvent) => {
|
|
455
|
-
const originHostname = new URL(origin!).hostname;
|
|
456
|
-
const messageHostname = new URL(e.origin).hostname;
|
|
457
|
-
const validHost = messageHostname === originHostname || messageHostname.endsWith('.' + originHostname);
|
|
458
|
-
if (!validHost || !Array.isArray(e.data) || !e.data.length || !this.messages.includes(e.data[0])) {
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
console.log(new Date(), 'got ready message from', e.origin, e.data);
|
|
463
|
-
window.removeEventListener('message', onReady);
|
|
464
|
-
ok(e.data);
|
|
465
|
-
};
|
|
466
|
-
window.addEventListener('message', onReady);
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
async wait(): Promise<any> {
|
|
471
|
-
console.log(new Date(), 'waiting for', this.messages);
|
|
472
|
-
const r = await this.promise;
|
|
473
|
-
console.log(new Date(), 'finished waiting for', this.messages, r);
|
|
474
|
-
return r;
|
|
475
|
-
}
|
|
476
154
|
}
|
|
477
155
|
|
|
156
|
+
/* =========================
|
|
157
|
+
* Nip46Signer
|
|
158
|
+
* ========================= */
|
|
478
159
|
export class Nip46Signer extends NDKNip46Signer {
|
|
479
|
-
private _userPubkey: string = '';
|
|
480
160
|
private _rpc: IframeNostrRpc;
|
|
481
161
|
|
|
482
|
-
constructor(ndk: NDK, localSigner: PrivateKeySigner, signerPubkey: string
|
|
162
|
+
constructor(ndk: NDK, localSigner: PrivateKeySigner, signerPubkey: string) {
|
|
163
|
+
const pm = new ProcessManager();
|
|
483
164
|
super(ndk, signerPubkey, localSigner);
|
|
484
165
|
|
|
485
|
-
this._rpc = new IframeNostrRpc(ndk, localSigner,
|
|
486
|
-
this._rpc.setUseNip44(true);
|
|
487
|
-
this._rpc.on('authUrl', (url: string) => {
|
|
488
|
-
this.emit('authUrl', url);
|
|
489
|
-
});
|
|
490
|
-
|
|
166
|
+
this._rpc = new IframeNostrRpc(ndk, localSigner, pm);
|
|
491
167
|
this.rpc = this._rpc;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
get userPubkey() {
|
|
495
|
-
return this._userPubkey;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
private async setSignerPubkey(signerPubkey: string, sameAsUser: boolean = false) {
|
|
499
|
-
console.log('setSignerPubkey', signerPubkey);
|
|
500
168
|
|
|
501
|
-
this.
|
|
502
|
-
|
|
503
|
-
this._rpc.on(`iframeRestart-${signerPubkey}`, () => {
|
|
504
|
-
this.emit('iframeRestart');
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
await this.initUserPubkey(sameAsUser ? signerPubkey : '');
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
public async initUserPubkey(hintPubkey?: string) {
|
|
511
|
-
if (this._userPubkey) throw new Error('Already called initUserPubkey');
|
|
512
|
-
|
|
513
|
-
if (hintPubkey) {
|
|
514
|
-
this._userPubkey = hintPubkey;
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
this._userPubkey = await new Promise<string>((ok, err) => {
|
|
519
|
-
if (!this.remotePubkey) throw new Error('Signer pubkey not set');
|
|
520
|
-
|
|
521
|
-
console.log('get_public_key', this.remotePubkey);
|
|
522
|
-
|
|
523
|
-
const timeoutId = setTimeout(() => {
|
|
524
|
-
err(new Error('NIP46 get_public_key timeout'));
|
|
525
|
-
}, NIP46_TIMEOUT);
|
|
526
|
-
|
|
527
|
-
this._rpc.sendRequest(this.remotePubkey, 'get_public_key', [], 24133, (response: NDKRpcResponse) => {
|
|
528
|
-
clearTimeout(timeoutId);
|
|
529
|
-
if (response.error) {
|
|
530
|
-
err(new Error(response.error));
|
|
531
|
-
} else {
|
|
532
|
-
ok(response.result);
|
|
533
|
-
}
|
|
534
|
-
});
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
public async listen(nostrConnectSecret: string) {
|
|
539
|
-
const signerPubkey = await (this.rpc as IframeNostrRpc).listen(nostrConnectSecret);
|
|
540
|
-
await this.setSignerPubkey(signerPubkey);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
public async connect(token?: string, perms?: string) {
|
|
544
|
-
if (!this.remotePubkey) throw new Error('No signer pubkey');
|
|
545
|
-
await this._rpc.connect(this.remotePubkey, token, perms);
|
|
546
|
-
await this.setSignerPubkey(this.remotePubkey);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
public async setListenReply(reply: any, nostrConnectSecret: string) {
|
|
550
|
-
const signerPubkey = await this._rpc.parseNostrConnectReply(reply, nostrConnectSecret);
|
|
551
|
-
await this.setSignerPubkey(signerPubkey, true);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
public async createAccount2({ bunkerPubkey, name, domain, perms = '' }: { bunkerPubkey: string; name: string; domain: string; perms?: string }) {
|
|
555
|
-
const params = [name, domain, '', perms];
|
|
556
|
-
|
|
557
|
-
const r = await new Promise<NDKRpcResponse>((ok, err) => {
|
|
558
|
-
const timeoutId = setTimeout(() => {
|
|
559
|
-
err(new Error('NIP46 create_account timeout'));
|
|
560
|
-
}, NIP46_TIMEOUT);
|
|
561
|
-
|
|
562
|
-
this.rpc.sendRequest(bunkerPubkey, 'create_account', params, undefined, response => {
|
|
563
|
-
clearTimeout(timeoutId);
|
|
564
|
-
if (response.error) {
|
|
565
|
-
err(new Error(response.error));
|
|
566
|
-
} else {
|
|
567
|
-
ok(response);
|
|
568
|
-
}
|
|
569
|
-
});
|
|
169
|
+
this._rpc.on('authUrl', url => {
|
|
170
|
+
this.emit('authUrl', url);
|
|
570
171
|
});
|
|
571
|
-
|
|
572
|
-
console.log('create_account pubkey', r);
|
|
573
|
-
if (r.result === 'error') {
|
|
574
|
-
throw new Error(r.error);
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
return r.result;
|
|
578
172
|
}
|
|
579
173
|
}
|
|
@@ -2,34 +2,45 @@ import { EventEmitter } from 'tseep';
|
|
|
2
2
|
import { CALL_TIMEOUT } from '../const';
|
|
3
3
|
|
|
4
4
|
class ProcessManager extends EventEmitter {
|
|
5
|
-
private paused = false;
|
|
6
5
|
private callCount = 0;
|
|
7
6
|
private callTimer?: NodeJS.Timeout;
|
|
7
|
+
private paused = false;
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
super();
|
|
11
|
-
}
|
|
12
|
-
|
|
9
|
+
/* 呼び出し元互換 */
|
|
13
10
|
public onAuthUrl() {
|
|
14
11
|
this.resetTimer();
|
|
15
12
|
}
|
|
16
13
|
|
|
14
|
+
/* 呼び出し元互換 */
|
|
17
15
|
public onIframeUrl() {
|
|
18
16
|
this.resetTimer();
|
|
19
17
|
}
|
|
20
18
|
|
|
21
|
-
private
|
|
19
|
+
private resetTimer() {
|
|
22
20
|
if (this.paused) return;
|
|
21
|
+
|
|
23
22
|
if (this.callTimer) clearTimeout(this.callTimer);
|
|
24
23
|
|
|
24
|
+
if (this.callCount > 0) {
|
|
25
|
+
this.callTimer = setTimeout(() => {
|
|
26
|
+
this.emit('onCallTimeout');
|
|
27
|
+
}, CALL_TIMEOUT);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private startTimer() {
|
|
32
|
+
if (this.paused) return;
|
|
33
|
+
if (this.callTimer) return;
|
|
34
|
+
|
|
25
35
|
this.callTimer = setTimeout(() => {
|
|
26
36
|
this.emit('onCallTimeout');
|
|
27
37
|
}, CALL_TIMEOUT);
|
|
28
38
|
}
|
|
29
39
|
|
|
30
|
-
private
|
|
31
|
-
if (this.callCount
|
|
32
|
-
this.
|
|
40
|
+
private stopTimerIfIdle() {
|
|
41
|
+
if (this.callCount === 0 && this.callTimer) {
|
|
42
|
+
clearTimeout(this.callTimer);
|
|
43
|
+
this.callTimer = undefined;
|
|
33
44
|
}
|
|
34
45
|
}
|
|
35
46
|
|
|
@@ -39,23 +50,19 @@ class ProcessManager extends EventEmitter {
|
|
|
39
50
|
}
|
|
40
51
|
|
|
41
52
|
this.callCount++;
|
|
42
|
-
this.startTimer();
|
|
43
53
|
|
|
44
54
|
try {
|
|
45
|
-
|
|
55
|
+
const p = Promise.resolve().then(cb);
|
|
56
|
+
this.startTimer();
|
|
57
|
+
return await p;
|
|
46
58
|
} finally {
|
|
47
59
|
this.callCount--;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (this.callTimer) {
|
|
51
|
-
clearTimeout(this.callTimer);
|
|
52
|
-
this.callTimer = undefined;
|
|
53
|
-
}
|
|
54
|
-
this.emit('onCallEnd');
|
|
55
|
-
}
|
|
60
|
+
this.emit('onCallEnd');
|
|
61
|
+
this.stopTimerIfIdle();
|
|
56
62
|
}
|
|
57
63
|
}
|
|
58
64
|
|
|
65
|
+
/* 再接続などで timeout を止めるため */
|
|
59
66
|
public pause() {
|
|
60
67
|
this.paused = true;
|
|
61
68
|
if (this.callTimer) {
|
|
@@ -64,9 +71,10 @@ class ProcessManager extends EventEmitter {
|
|
|
64
71
|
}
|
|
65
72
|
}
|
|
66
73
|
|
|
74
|
+
/* 再接続完了後に再開 */
|
|
67
75
|
public resume() {
|
|
68
76
|
this.paused = false;
|
|
69
|
-
if (this.callCount > 0) {
|
|
77
|
+
if (this.callCount > 0 && !this.callTimer) {
|
|
70
78
|
this.startTimer();
|
|
71
79
|
}
|
|
72
80
|
}
|