@konemono/nostr-login 1.7.29 → 1.7.31
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/AuthNostrService.ts +2 -1
- package/src/modules/Nip46.ts +475 -69
- package/src/modules/ProcessManager.ts +51 -37
package/package.json
CHANGED
|
@@ -572,7 +572,8 @@ class AuthNostrService extends EventEmitter implements Signer {
|
|
|
572
572
|
}
|
|
573
573
|
}
|
|
574
574
|
|
|
575
|
-
public async signEvent(
|
|
575
|
+
public async signEvent(ev: any) {
|
|
576
|
+
const event = { ...ev };
|
|
576
577
|
if (this.localSigner) {
|
|
577
578
|
event.pubkey = getPublicKey(this.localSigner.privateKey!);
|
|
578
579
|
event.id = getEventHash(event);
|
package/src/modules/Nip46.ts
CHANGED
|
@@ -1,173 +1,579 @@
|
|
|
1
|
-
|
|
2
|
-
import NDK, { NDKEvent, NDKFilter, NDKNip46Signer, NDKNostrRpc, NDKRpcRequest, NDKRpcResponse, NDKSubscription, NostrEvent } from '@nostr-dev-kit/ndk';
|
|
1
|
+
import NDK, { NDKEvent, NDKFilter, NDKNip46Signer, NDKNostrRpc, NDKRpcRequest, NDKRpcResponse, NDKSubscription, NDKSubscriptionCacheUsage, NostrEvent } from '@nostr-dev-kit/ndk';
|
|
3
2
|
import { validateEvent, verifySignature } from 'nostr-tools';
|
|
4
3
|
import { PrivateKeySigner } from './Signer';
|
|
5
4
|
import { NIP46_TIMEOUT } from '../const';
|
|
6
5
|
import ProcessManager from './ProcessManager';
|
|
7
6
|
|
|
8
|
-
/* =========================
|
|
9
|
-
* NostrRpc
|
|
10
|
-
* ========================= */
|
|
11
7
|
class NostrRpc extends NDKNostrRpc {
|
|
12
|
-
protected processManager
|
|
8
|
+
protected processManager?: ProcessManager;
|
|
13
9
|
protected _ndk: NDK;
|
|
14
10
|
protected _signer: PrivateKeySigner;
|
|
15
11
|
protected requests: Set<string> = new Set();
|
|
16
12
|
protected requestTimeouts: Map<string, NodeJS.Timeout> = new Map();
|
|
17
13
|
private sub?: NDKSubscription;
|
|
18
|
-
protected _useNip44 = false;
|
|
14
|
+
protected _useNip44: boolean = false;
|
|
19
15
|
|
|
20
|
-
constructor(ndk: NDK, signer: PrivateKeySigner, processManager
|
|
16
|
+
public constructor(ndk: NDK, signer: PrivateKeySigner, processManager?: ProcessManager) {
|
|
21
17
|
super(ndk, signer, ndk.debug.extend('nip46:signer:rpc'));
|
|
22
18
|
this._ndk = ndk;
|
|
23
19
|
this._signer = signer;
|
|
24
20
|
this.processManager = processManager;
|
|
25
21
|
}
|
|
26
22
|
|
|
27
|
-
async subscribe(filter: NDKFilter): Promise<NDKSubscription> {
|
|
23
|
+
public async subscribe(filter: NDKFilter): Promise<NDKSubscription> {
|
|
28
24
|
filter.kinds = filter.kinds?.filter(k => k === 24133);
|
|
29
25
|
this.sub = await super.subscribe(filter);
|
|
30
26
|
return this.sub;
|
|
31
27
|
}
|
|
32
28
|
|
|
33
|
-
stop() {
|
|
34
|
-
|
|
35
|
-
this.requestTimeouts.clear();
|
|
29
|
+
public stop() {
|
|
30
|
+
this.clearAllTimeouts();
|
|
36
31
|
if (this.sub) {
|
|
37
32
|
this.sub.stop();
|
|
38
33
|
this.sub = undefined;
|
|
39
34
|
}
|
|
40
35
|
}
|
|
41
36
|
|
|
37
|
+
private clearAllTimeouts() {
|
|
38
|
+
for (const timeout of this.requestTimeouts.values()) {
|
|
39
|
+
clearTimeout(timeout);
|
|
40
|
+
}
|
|
41
|
+
this.requestTimeouts.clear();
|
|
42
|
+
}
|
|
43
|
+
|
|
42
44
|
protected clearTimeout(id: string) {
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
|
|
45
|
+
const timeout = this.requestTimeouts.get(id);
|
|
46
|
+
if (timeout) {
|
|
47
|
+
clearTimeout(timeout);
|
|
48
|
+
this.requestTimeouts.delete(id);
|
|
49
|
+
}
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
setUseNip44(
|
|
49
|
-
this._useNip44 =
|
|
52
|
+
public setUseNip44(useNip44: boolean) {
|
|
53
|
+
this._useNip44 = useNip44;
|
|
50
54
|
}
|
|
51
55
|
|
|
52
|
-
|
|
56
|
+
private isNip04(ciphertext: string) {
|
|
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> {
|
|
53
63
|
const remoteUser = this._ndk.getUser({ pubkey: event.pubkey });
|
|
54
64
|
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
|
+
}
|
|
55
76
|
|
|
56
|
-
|
|
77
|
+
public async parseNostrConnectReply(reply: any, secret: string) {
|
|
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
|
+
}
|
|
57
89
|
|
|
58
|
-
|
|
90
|
+
public async listen(nostrConnectSecret: string): Promise<string> {
|
|
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
|
+
}
|
|
59
126
|
|
|
60
|
-
|
|
127
|
+
public async connect(pubkey: string, token?: string, perms?: string) {
|
|
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
|
+
});
|
|
61
144
|
}
|
|
62
145
|
|
|
63
|
-
protected getId() {
|
|
64
|
-
return Math.random().toString(36).
|
|
146
|
+
protected getId(): string {
|
|
147
|
+
return Math.random().toString(36).substring(7);
|
|
65
148
|
}
|
|
66
149
|
|
|
67
|
-
protected async ensureConnected() {
|
|
150
|
+
protected async ensureConnected(): Promise<void> {
|
|
68
151
|
const relays = Array.from(this._ndk.pool.relays.values());
|
|
69
|
-
|
|
152
|
+
console.log(
|
|
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
|
+
}
|
|
70
170
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
171
|
+
// 再接続
|
|
172
|
+
await this._ndk.connect();
|
|
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
|
+
);
|
|
75
206
|
}
|
|
76
|
-
|
|
77
|
-
await this._ndk.connect();
|
|
78
207
|
}
|
|
79
208
|
|
|
80
|
-
async sendRequest(remotePubkey: string, method: string, params: string[] = [], kind = 24133, cb?: (
|
|
81
|
-
|
|
209
|
+
public async sendRequest(remotePubkey: string, method: string, params: string[] = [], kind = 24133, cb?: (res: NDKRpcResponse) => void): Promise<NDKRpcResponse> {
|
|
210
|
+
console.log('sendRequest called:', method, 'to', remotePubkey);
|
|
211
|
+
|
|
82
212
|
try {
|
|
213
|
+
this.processManager?.pause();
|
|
83
214
|
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;
|
|
84
226
|
} finally {
|
|
85
|
-
this.processManager
|
|
227
|
+
this.processManager?.resume();
|
|
86
228
|
}
|
|
87
229
|
|
|
88
230
|
const id = this.getId();
|
|
231
|
+
|
|
89
232
|
this.setResponseHandler(id, cb);
|
|
90
233
|
|
|
91
234
|
const event = await this.createRequestEvent(id, remotePubkey, method, params, kind);
|
|
92
|
-
|
|
235
|
+
console.log('sendRequest event created', { event, method, remotePubkey, params });
|
|
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
|
+
}
|
|
93
254
|
|
|
94
255
|
// @ts-ignore
|
|
95
|
-
return undefined;
|
|
256
|
+
return undefined as NDKRpcResponse;
|
|
96
257
|
}
|
|
97
258
|
|
|
98
|
-
protected setResponseHandler(id: string, cb?: (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
259
|
+
protected setResponseHandler(id: string, cb?: (res: NDKRpcResponse) => void) {
|
|
260
|
+
let authUrlSent = false;
|
|
261
|
+
const now = Date.now();
|
|
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
|
+
}
|
|
102
277
|
}, NIP46_TIMEOUT);
|
|
103
278
|
|
|
104
|
-
this.requestTimeouts.set(id,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
279
|
+
this.requestTimeouts.set(id, timeoutId);
|
|
280
|
+
|
|
281
|
+
return new Promise<NDKRpcResponse>(() => {
|
|
282
|
+
const responseHandler = (response: NDKRpcResponse) => {
|
|
283
|
+
if (response.result === 'auth_url') {
|
|
284
|
+
this.once(`response-${id}`, responseHandler);
|
|
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);
|
|
110
300
|
});
|
|
111
301
|
}
|
|
112
302
|
|
|
113
|
-
protected async createRequestEvent(id: string, remotePubkey: string, method: string, params: string[], kind
|
|
303
|
+
protected async createRequestEvent(id: string, remotePubkey: string, method: string, params: string[] = [], kind = 24133) {
|
|
114
304
|
this.requests.add(id);
|
|
115
305
|
const localUser = await this._signer.user();
|
|
116
306
|
const remoteUser = this._ndk.getUser({ pubkey: remotePubkey });
|
|
307
|
+
const request = { id, method, params };
|
|
117
308
|
|
|
118
309
|
const event = new NDKEvent(this._ndk, {
|
|
119
310
|
kind,
|
|
120
|
-
content: JSON.stringify(
|
|
311
|
+
content: JSON.stringify(request),
|
|
121
312
|
tags: [['p', remotePubkey]],
|
|
122
313
|
pubkey: localUser.pubkey,
|
|
123
314
|
} as NostrEvent);
|
|
124
315
|
|
|
125
|
-
|
|
126
|
-
|
|
316
|
+
const useNip44 = this._useNip44 && method !== 'create_account';
|
|
317
|
+
const encrypt = useNip44 ? this._signer.encryptNip44 : this._signer.encrypt;
|
|
318
|
+
event.content = await encrypt.call(this._signer, remoteUser, event.content);
|
|
127
319
|
await event.sign(this._signer);
|
|
320
|
+
|
|
128
321
|
return event;
|
|
129
322
|
}
|
|
130
323
|
}
|
|
131
324
|
|
|
132
|
-
/* =========================
|
|
133
|
-
* IframeNostrRpc
|
|
134
|
-
* ========================= */
|
|
135
325
|
export class IframeNostrRpc extends NostrRpc {
|
|
326
|
+
private peerOrigin?: string;
|
|
136
327
|
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
|
+
}
|
|
137
335
|
|
|
138
|
-
|
|
139
|
-
|
|
336
|
+
public async subscribe(filter: NDKFilter): Promise<NDKSubscription> {
|
|
337
|
+
if (!this.peerOrigin) return super.subscribe(filter);
|
|
338
|
+
return new NDKSubscription(
|
|
339
|
+
this._ndk,
|
|
340
|
+
{},
|
|
341
|
+
{
|
|
342
|
+
closeOnEose: true,
|
|
343
|
+
cacheUsage: NDKSubscriptionCacheUsage.ONLY_CACHE,
|
|
344
|
+
},
|
|
345
|
+
);
|
|
140
346
|
}
|
|
141
347
|
|
|
142
|
-
setWorkerIframePort(port: MessagePort) {
|
|
348
|
+
public setWorkerIframePort(port: MessagePort) {
|
|
349
|
+
if (!this.peerOrigin) throw new Error('Unexpected iframe port');
|
|
350
|
+
|
|
143
351
|
this.iframePort = port;
|
|
144
352
|
|
|
353
|
+
setInterval(() => {
|
|
354
|
+
console.log('iframe-nip46 ping');
|
|
355
|
+
this.iframePort!.postMessage('ping');
|
|
356
|
+
}, 5000);
|
|
357
|
+
|
|
145
358
|
port.onmessage = async ev => {
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
this.emit(`
|
|
359
|
+
console.log('iframe-nip46 got response', ev.data);
|
|
360
|
+
if (typeof ev.data === 'string' && ev.data.startsWith('errorNoKey')) {
|
|
361
|
+
const event_id = ev.data.split(':')[1];
|
|
362
|
+
const { id = '', pubkey = '' } = this.iframeRequests.get(event_id) || {};
|
|
363
|
+
if (id && pubkey && this.requests.has(id)) this.emit(`iframeRestart-${pubkey}`);
|
|
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);
|
|
151
380
|
}
|
|
152
381
|
};
|
|
153
382
|
}
|
|
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
|
+
}
|
|
154
476
|
}
|
|
155
477
|
|
|
156
|
-
/* =========================
|
|
157
|
-
* Nip46Signer
|
|
158
|
-
* ========================= */
|
|
159
478
|
export class Nip46Signer extends NDKNip46Signer {
|
|
479
|
+
private _userPubkey: string = '';
|
|
160
480
|
private _rpc: IframeNostrRpc;
|
|
161
481
|
|
|
162
|
-
constructor(ndk: NDK, localSigner: PrivateKeySigner, signerPubkey: string) {
|
|
163
|
-
const pm = new ProcessManager();
|
|
482
|
+
constructor(ndk: NDK, localSigner: PrivateKeySigner, signerPubkey: string, iframeOrigin?: string) {
|
|
164
483
|
super(ndk, signerPubkey, localSigner);
|
|
165
484
|
|
|
166
|
-
this._rpc = new IframeNostrRpc(ndk, localSigner,
|
|
485
|
+
this._rpc = new IframeNostrRpc(ndk, localSigner, iframeOrigin);
|
|
486
|
+
this._rpc.setUseNip44(true);
|
|
487
|
+
this._rpc.on('authUrl', (url: string) => {
|
|
488
|
+
this.emit('authUrl', url);
|
|
489
|
+
});
|
|
490
|
+
|
|
167
491
|
this.rpc = this._rpc;
|
|
492
|
+
}
|
|
168
493
|
|
|
169
|
-
|
|
170
|
-
|
|
494
|
+
get userPubkey() {
|
|
495
|
+
return this._userPubkey;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
private async setSignerPubkey(signerPubkey: string, sameAsUser: boolean = false) {
|
|
499
|
+
console.log('setSignerPubkey', signerPubkey);
|
|
500
|
+
|
|
501
|
+
this.remotePubkey = signerPubkey;
|
|
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
|
+
});
|
|
171
535
|
});
|
|
172
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
|
+
});
|
|
570
|
+
});
|
|
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
|
+
}
|
|
173
579
|
}
|
|
@@ -2,80 +2,94 @@ import { EventEmitter } from 'tseep';
|
|
|
2
2
|
import { CALL_TIMEOUT } from '../const';
|
|
3
3
|
|
|
4
4
|
class ProcessManager extends EventEmitter {
|
|
5
|
-
private callCount = 0;
|
|
6
|
-
private callTimer?: NodeJS.Timeout;
|
|
7
5
|
private paused = false;
|
|
6
|
+
private callCount: number = 0;
|
|
7
|
+
private callTimer: NodeJS.Timeout | undefined;
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
}
|
|
8
12
|
|
|
9
|
-
/* 呼び出し元互換 */
|
|
10
13
|
public onAuthUrl() {
|
|
14
|
+
console.log('ProcessManager.onAuthUrl called, resetting timer');
|
|
11
15
|
this.resetTimer();
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
/* 呼び出し元互換 */
|
|
15
18
|
public onIframeUrl() {
|
|
19
|
+
console.log('ProcessManager.onIframeUrl called, resetting timer');
|
|
16
20
|
this.resetTimer();
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
private resetTimer() {
|
|
20
|
-
if (this.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
if (this.callTimer) {
|
|
25
|
+
clearTimeout(this.callTimer);
|
|
26
|
+
console.log('ProcessManager: timer reset');
|
|
27
|
+
}
|
|
24
28
|
if (this.callCount > 0) {
|
|
25
29
|
this.callTimer = setTimeout(() => {
|
|
30
|
+
console.log('ProcessManager: timeout reached, emitting onCallTimeout');
|
|
26
31
|
this.emit('onCallTimeout');
|
|
27
32
|
}, CALL_TIMEOUT);
|
|
33
|
+
console.log(`ProcessManager: new timer set for ${CALL_TIMEOUT} ms`);
|
|
28
34
|
}
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (this.callTimer) return;
|
|
34
|
-
|
|
35
|
-
this.callTimer = setTimeout(() => {
|
|
36
|
-
this.emit('onCallTimeout');
|
|
37
|
-
}, CALL_TIMEOUT);
|
|
38
|
-
}
|
|
37
|
+
public async wait<T>(cb: () => Promise<T>): Promise<T> {
|
|
38
|
+
console.log('ProcessManager.wait called, callTimer exists:', !!this.callTimer, 'callCount:', this.callCount);
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
if (!this.callTimer) {
|
|
41
|
+
this.callTimer = setTimeout(() => {
|
|
42
|
+
console.log('ProcessManager: timeout reached, emitting onCallTimeout');
|
|
43
|
+
this.callTimer = undefined; // タイムアウト時にタイマーIDをクリア
|
|
44
|
+
this.emit('onCallTimeout');
|
|
45
|
+
}, CALL_TIMEOUT);
|
|
46
|
+
console.log(`Setting up timeout timer for ${CALL_TIMEOUT} ms`);
|
|
44
47
|
}
|
|
45
|
-
}
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
if (this.callCount === 0) {
|
|
49
|
+
if (!this.callCount) {
|
|
49
50
|
this.emit('onCallStart');
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
this.callCount++;
|
|
53
54
|
|
|
55
|
+
let error;
|
|
56
|
+
let result;
|
|
57
|
+
|
|
54
58
|
try {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
} finally {
|
|
59
|
-
this.callCount--;
|
|
60
|
-
this.emit('onCallEnd');
|
|
61
|
-
this.stopTimerIfIdle();
|
|
59
|
+
result = await cb();
|
|
60
|
+
} catch (e) {
|
|
61
|
+
error = e;
|
|
62
62
|
}
|
|
63
|
-
}
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
this.
|
|
64
|
+
this.callCount--;
|
|
65
|
+
|
|
66
|
+
this.emit('onCallEnd');
|
|
67
|
+
|
|
68
68
|
if (this.callTimer) {
|
|
69
69
|
clearTimeout(this.callTimer);
|
|
70
|
-
this.callTimer = undefined;
|
|
71
70
|
}
|
|
71
|
+
|
|
72
|
+
this.callTimer = undefined;
|
|
73
|
+
|
|
74
|
+
if (error) {
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// @ts-ignore
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
public pause() {
|
|
82
|
+
if (this.callTimer) clearTimeout(this.callTimer);
|
|
83
|
+
this.callTimer = undefined;
|
|
84
|
+
this.paused = true;
|
|
72
85
|
}
|
|
73
86
|
|
|
74
|
-
/* 再接続完了後に再開 */
|
|
75
87
|
public resume() {
|
|
76
88
|
this.paused = false;
|
|
77
89
|
if (this.callCount > 0 && !this.callTimer) {
|
|
78
|
-
this.
|
|
90
|
+
this.callTimer = setTimeout(() => {
|
|
91
|
+
this.emit('onCallTimeout');
|
|
92
|
+
}, CALL_TIMEOUT);
|
|
79
93
|
}
|
|
80
94
|
}
|
|
81
95
|
}
|