@konemono/nostr-login 1.10.15 → 1.11.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/index.esm.js +20 -15
- package/dist/index.esm.js.map +1 -1
- package/dist/modules/AuthNostrService.d.ts +14 -4
- package/dist/modules/Nip46.d.ts +52 -11
- package/dist/modules/Signer.d.ts +12 -6
- package/dist/unpkg.js +20 -15
- package/dist/utils/index.d.ts +6 -3
- package/dist/utils/nip44.d.ts +3 -3
- package/package.json +8 -8
- package/src/modules/AuthNostrService.ts +223 -123
- package/src/modules/ModalManager.ts +3 -3
- package/src/modules/Nip46.ts +273 -91
- package/src/modules/NostrExtensionService.ts +2 -0
- package/src/modules/Signer.ts +35 -12
- package/src/utils/index.ts +73 -23
- package/src/utils/nip44.ts +12 -7
- package/test-relay-management.html +407 -0
package/src/modules/Nip46.ts
CHANGED
|
@@ -1,60 +1,138 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { createRxForwardReq, createRxNostr, RxNostr } from 'rx-nostr';
|
|
2
|
+
import { EventEmitter } from 'tseep';
|
|
3
|
+
import { validateEvent, verifyEvent, type Event as NostrEventSDK } from 'nostr-tools';
|
|
3
4
|
import { PrivateKeySigner } from './Signer';
|
|
4
5
|
import { NIP46_REQUEST_TIMEOUT, NIP46_CONNECT_TIMEOUT } from '../const';
|
|
5
6
|
|
|
6
|
-
// タイムアウト付きPromiseラッパー
|
|
7
7
|
function withTimeout<T>(promise: Promise<T>, timeoutMs: number, errorMessage: string): Promise<T> {
|
|
8
8
|
return Promise.race([promise, new Promise<T>((_, reject) => setTimeout(() => reject(new Error(errorMessage)), timeoutMs))]);
|
|
9
9
|
}
|
|
10
|
+
// nostr-toolsに基づく厳格な型定義
|
|
11
|
+
type NostrFilter = {
|
|
12
|
+
ids?: string[];
|
|
13
|
+
kinds?: number[];
|
|
14
|
+
authors?: string[];
|
|
15
|
+
since?: number;
|
|
16
|
+
until?: number;
|
|
17
|
+
limit?: number;
|
|
18
|
+
search?: string;
|
|
19
|
+
[key: `#${string}`]: string[] | undefined; // タグフィルター (#e, #p, etc.)
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type NostrEvent = {
|
|
23
|
+
id?: string;
|
|
24
|
+
kind: number;
|
|
25
|
+
pubkey: string;
|
|
26
|
+
content: string;
|
|
27
|
+
tags: string[][];
|
|
28
|
+
created_at: number;
|
|
29
|
+
sig?: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type NostrSubscription = {
|
|
33
|
+
on: (event: string, cb: any) => void;
|
|
34
|
+
stop: () => void;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type NDKRpcResponse = {
|
|
38
|
+
id: string;
|
|
39
|
+
pubkey: string;
|
|
40
|
+
content: string;
|
|
41
|
+
result?: string;
|
|
42
|
+
error?: string;
|
|
43
|
+
[key: string]: any;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type NDKRpcRequest = {
|
|
47
|
+
id: string;
|
|
48
|
+
pubkey: string;
|
|
49
|
+
method: string;
|
|
50
|
+
params: any[];
|
|
51
|
+
[key: string]: any;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Helper to wrap rx-nostr subscription
|
|
55
|
+
class RxReqAdapter extends EventEmitter {
|
|
56
|
+
private sub: any;
|
|
57
|
+
constructor(rxNostr: RxNostr, filters: NostrFilter[], relays?: string[]) {
|
|
58
|
+
super();
|
|
59
|
+
const req = createRxForwardReq();
|
|
60
|
+
this.sub = rxNostr.use(req).subscribe((packet) => {
|
|
61
|
+
this.emit('event', packet.event);
|
|
62
|
+
});
|
|
63
|
+
// If relays are provided, we might want to ensure they are used?
|
|
64
|
+
// rx-nostr manages relays globally usually, but we can set default relays in the instance.
|
|
65
|
+
req.emit(filters as any);
|
|
66
|
+
}
|
|
67
|
+
stop() {
|
|
68
|
+
this.sub.unsubscribe();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
10
71
|
|
|
11
|
-
|
|
12
|
-
|
|
72
|
+
// TODO: NDKの継承を削除する
|
|
73
|
+
class NostrRpc extends EventEmitter {
|
|
74
|
+
public rxNostr: RxNostr;
|
|
13
75
|
protected _signer: PrivateKeySigner;
|
|
14
76
|
protected requests: Set<string> = new Set();
|
|
15
|
-
private sub?: NDKSubscription;
|
|
77
|
+
// private sub?: NDKSubscription;
|
|
78
|
+
private sub?: RxReqAdapter;
|
|
16
79
|
protected _useNip44: boolean = false;
|
|
17
80
|
private reconnectAttempts: number = 0;
|
|
18
81
|
private maxReconnectAttempts: number = 3;
|
|
19
82
|
private reconnectDelay: number = 2000;
|
|
20
83
|
|
|
21
|
-
public constructor(
|
|
22
|
-
super(
|
|
23
|
-
this.
|
|
84
|
+
public constructor(rxNostr: RxNostr, signer: PrivateKeySigner) {
|
|
85
|
+
super();
|
|
86
|
+
this.rxNostr = rxNostr;
|
|
24
87
|
this._signer = signer;
|
|
25
88
|
this.setupConnectionMonitoring();
|
|
26
89
|
}
|
|
27
90
|
|
|
28
|
-
public async subscribe(filter:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
91
|
+
public async subscribe(filter: NostrFilter): Promise<NostrSubscription> {
|
|
92
|
+
const f: any = { ...filter };
|
|
93
|
+
if (f.kinds) {
|
|
94
|
+
f.kinds = f.kinds.filter((k: number) => k === 24133);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.sub = new RxReqAdapter(this.rxNostr, [f]);
|
|
32
98
|
return this.sub;
|
|
33
99
|
}
|
|
34
100
|
|
|
35
|
-
public stop() {
|
|
101
|
+
public stop(): void {
|
|
36
102
|
if (this.sub) {
|
|
37
103
|
this.sub.stop();
|
|
38
104
|
this.sub = undefined;
|
|
39
105
|
}
|
|
40
106
|
}
|
|
41
107
|
|
|
42
|
-
public setUseNip44(useNip44: boolean) {
|
|
108
|
+
public setUseNip44(useNip44: boolean): void {
|
|
43
109
|
this._useNip44 = useNip44;
|
|
44
110
|
}
|
|
45
111
|
|
|
46
|
-
private isNip04(ciphertext: string) {
|
|
112
|
+
private isNip04(ciphertext: string): boolean {
|
|
47
113
|
const l = ciphertext.length;
|
|
48
114
|
if (l < 28) return false;
|
|
49
115
|
return ciphertext[l - 28] === '?' && ciphertext[l - 27] === 'i' && ciphertext[l - 26] === 'v' && ciphertext[l - 25] === '=';
|
|
50
116
|
}
|
|
51
117
|
|
|
52
118
|
// override to auto-decrypt nip04/nip44
|
|
53
|
-
public async parseEvent(event:
|
|
54
|
-
|
|
55
|
-
|
|
119
|
+
public async parseEvent(event: NostrEvent): Promise<any> {
|
|
120
|
+
// TODO: 元のNDKロジック:
|
|
121
|
+
// 1. NDKUser.from(event.pubkey) to get remote user.
|
|
122
|
+
// 2. Check content for NIP-04 pattern (?iv=...).
|
|
123
|
+
// 3. Call signer.decrypt(remoteUser, content) or signer.decryptNip44(remoteUser, content).
|
|
124
|
+
//
|
|
125
|
+
// New Implementation Plan:
|
|
126
|
+
// 1. Use direct NIP-04/NIP-44 decryption functions from local signer.
|
|
127
|
+
// 2. Pass event.pubkey (string) instead of NDKUser object.
|
|
128
|
+
|
|
129
|
+
// const remoteUser = this._ndk.getUser({ pubkey: event.pubkey });
|
|
130
|
+
// remoteUser.ndk = this._ndk;
|
|
131
|
+
|
|
56
132
|
const decrypt = this.isNip04(event.content) ? this._signer.decrypt : this._signer.decryptNip44;
|
|
57
|
-
|
|
133
|
+
|
|
134
|
+
// NOTE: We need to adjust _signer.decrypt signature to accept string pubkey
|
|
135
|
+
const decryptedContent = await decrypt.call(this._signer, event.pubkey as any, event.content);
|
|
58
136
|
const parsedContent = JSON.parse(decryptedContent);
|
|
59
137
|
const { id, method, params, result, error } = parsedContent;
|
|
60
138
|
|
|
@@ -65,8 +143,8 @@ class NostrRpc extends NDKNostrRpc {
|
|
|
65
143
|
}
|
|
66
144
|
}
|
|
67
145
|
|
|
68
|
-
public async parseNostrConnectReply(reply: any, secret: string) {
|
|
69
|
-
const event =
|
|
146
|
+
public async parseNostrConnectReply(reply: any, secret: string): Promise<string> {
|
|
147
|
+
const event = reply as NostrEvent;
|
|
70
148
|
const parsedEvent = await this.parseEvent(event);
|
|
71
149
|
console.log('nostr connect parsedEvent', parsedEvent);
|
|
72
150
|
if (!(parsedEvent as NDKRpcRequest).method) {
|
|
@@ -82,14 +160,15 @@ class NostrRpc extends NDKNostrRpc {
|
|
|
82
160
|
// we just listed to an unsolicited reply to
|
|
83
161
|
// our pubkey and if it's ack/secret - we're fine
|
|
84
162
|
public async listen(nostrConnectSecret: string): Promise<string> {
|
|
163
|
+
// TODO: rx-nostrを使用して実装する
|
|
85
164
|
const pubkey = this._signer.pubkey;
|
|
86
165
|
console.log('nostr-login listening for conn to', pubkey);
|
|
87
166
|
const sub = await this.subscribe({
|
|
88
167
|
'kinds': [24133],
|
|
89
168
|
'#p': [pubkey],
|
|
90
|
-
});
|
|
169
|
+
} as any); // Cast to any because RxReqAdapter expects NostrFilter but we pass explicit strict filters that might differ slightly in type def
|
|
91
170
|
return new Promise<string>((ok, err) => {
|
|
92
|
-
sub.on('event', async (event:
|
|
171
|
+
sub.on('event', async (event: NostrEvent) => {
|
|
93
172
|
try {
|
|
94
173
|
const parsedEvent = await this.parseEvent(event);
|
|
95
174
|
// console.log('ack parsedEvent', parsedEvent);
|
|
@@ -108,7 +187,7 @@ class NostrRpc extends NDKNostrRpc {
|
|
|
108
187
|
}
|
|
109
188
|
}
|
|
110
189
|
} catch (e) {
|
|
111
|
-
console.log('error parsing event', e, event.rawEvent());
|
|
190
|
+
console.log('error parsing event', e, (event as any).rawEvent?.());
|
|
112
191
|
}
|
|
113
192
|
// done
|
|
114
193
|
this.stop();
|
|
@@ -119,7 +198,8 @@ class NostrRpc extends NDKNostrRpc {
|
|
|
119
198
|
// since ndk doesn't yet support perms param
|
|
120
199
|
// we reimplement the 'connect' call here
|
|
121
200
|
// instead of await signer.blockUntilReady();
|
|
122
|
-
public async connect(pubkey: string, token?: string, perms?: string) {
|
|
201
|
+
public async connect(pubkey: string, token?: string, perms?: string): Promise<void> {
|
|
202
|
+
// TODO: nostr-toolsを使用して実装する
|
|
123
203
|
return new Promise<void>((ok, err) => {
|
|
124
204
|
const connectParams = [pubkey!, token || '', perms || ''];
|
|
125
205
|
this.sendRequest(pubkey!, 'connect', connectParams, 24133, (response: NDKRpcResponse) => {
|
|
@@ -161,7 +241,7 @@ class NostrRpc extends NDKNostrRpc {
|
|
|
161
241
|
}
|
|
162
242
|
|
|
163
243
|
// 接続監視のセットアップ
|
|
164
|
-
private setupConnectionMonitoring() {
|
|
244
|
+
private setupConnectionMonitoring(): void {
|
|
165
245
|
// アプリがフォアグラウンドに戻ったときの処理
|
|
166
246
|
if (typeof document !== 'undefined') {
|
|
167
247
|
document.addEventListener('visibilitychange', async () => {
|
|
@@ -179,11 +259,22 @@ class NostrRpc extends NDKNostrRpc {
|
|
|
179
259
|
await this.reconnect();
|
|
180
260
|
});
|
|
181
261
|
}
|
|
262
|
+
|
|
263
|
+
// 定期的な接続状態チェック(オプション)
|
|
264
|
+
setInterval(() => {
|
|
265
|
+
const stats = this.rxNostr.getAllRelayStatus();
|
|
266
|
+
const connected = Object.values(stats).filter(s => String(s) === 'connected').length;
|
|
267
|
+
if (connected === 0) {
|
|
268
|
+
console.warn('No relays connected, triggering reconnection...');
|
|
269
|
+
this.ensureConnected();
|
|
270
|
+
}
|
|
271
|
+
}, 60000); // 60秒ごとにチェック
|
|
182
272
|
}
|
|
183
273
|
|
|
184
274
|
// 接続を確認して必要なら再接続
|
|
185
275
|
private async ensureConnected(): Promise<void> {
|
|
186
|
-
const
|
|
276
|
+
const states = this.rxNostr.getAllRelayStatus();
|
|
277
|
+
const connectedRelays = Object.values(states).filter(s => String(s) === 'connected');
|
|
187
278
|
|
|
188
279
|
if (connectedRelays.length === 0) {
|
|
189
280
|
console.log('No connected relays, attempting reconnection...');
|
|
@@ -191,7 +282,7 @@ class NostrRpc extends NDKNostrRpc {
|
|
|
191
282
|
}
|
|
192
283
|
}
|
|
193
284
|
|
|
194
|
-
//
|
|
285
|
+
// 再接続処理(指数バックオフ付き)
|
|
195
286
|
protected async reconnect(): Promise<void> {
|
|
196
287
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
197
288
|
console.error('Max reconnection attempts reached');
|
|
@@ -208,25 +299,42 @@ class NostrRpc extends NDKNostrRpc {
|
|
|
208
299
|
this.sub.stop();
|
|
209
300
|
}
|
|
210
301
|
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
302
|
+
// 指数バックオフ: 2^attempt * baseDelay (最大30秒)
|
|
303
|
+
const backoffDelay = Math.min(
|
|
304
|
+
this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1),
|
|
305
|
+
30000
|
|
306
|
+
);
|
|
307
|
+
console.log(`Waiting ${backoffDelay}ms before reconnection...`);
|
|
308
|
+
await new Promise(resolve => setTimeout(resolve, backoffDelay));
|
|
309
|
+
|
|
310
|
+
// 全リレーに再接続を試みる
|
|
311
|
+
const relays = Object.keys(this.rxNostr.getAllRelayStatus());
|
|
312
|
+
if (relays.length > 0) {
|
|
313
|
+
for (const relay of relays) {
|
|
314
|
+
try {
|
|
315
|
+
this.rxNostr.reconnect(relay);
|
|
316
|
+
console.log(`Reconnected to ${relay}`);
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.warn(`Failed to reconnect to ${relay}:`, error);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
220
321
|
}
|
|
221
322
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
323
|
+
// 再接続成功後、カウンターをリセット
|
|
324
|
+
setTimeout(() => {
|
|
325
|
+
const states = this.rxNostr.getAllRelayStatus();
|
|
326
|
+
const connected = Object.values(states).filter(s => String(s) === 'connected').length;
|
|
327
|
+
if (connected > 0) {
|
|
328
|
+
console.log('Reconnection successful, resetting attempt counter');
|
|
329
|
+
this.reconnectAttempts = 0;
|
|
330
|
+
this.emit('reconnected');
|
|
331
|
+
}
|
|
332
|
+
}, 2000);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
console.error('Reconnection error:', error);
|
|
335
|
+
this.emit('reconnectError', error);
|
|
336
|
+
// 次の再接続を試みる
|
|
337
|
+
setTimeout(() => this.reconnect(), this.reconnectDelay);
|
|
230
338
|
}
|
|
231
339
|
}
|
|
232
340
|
|
|
@@ -245,7 +353,11 @@ class NostrRpc extends NDKNostrRpc {
|
|
|
245
353
|
console.log('sendRequest', { event, method, remotePubkey, params });
|
|
246
354
|
|
|
247
355
|
// send to relays
|
|
248
|
-
await event.publish();
|
|
356
|
+
// await event.publish();
|
|
357
|
+
this.rxNostr.send(event).subscribe({
|
|
358
|
+
error: (err) => console.error('Nip46 publish error', err),
|
|
359
|
+
complete: () => console.log('Nip46 publish complete')
|
|
360
|
+
});
|
|
249
361
|
|
|
250
362
|
// NOTE: ndk returns a promise that never resolves and
|
|
251
363
|
// in fact REQUIRES cb to be provided (otherwise no way
|
|
@@ -256,7 +368,8 @@ class NostrRpc extends NDKNostrRpc {
|
|
|
256
368
|
return undefined as NDKRpcResponse;
|
|
257
369
|
}
|
|
258
370
|
|
|
259
|
-
protected setResponseHandler(id: string, cb?: (res: NDKRpcResponse) => void) {
|
|
371
|
+
protected setResponseHandler(id: string, cb?: (res: NDKRpcResponse) => void): Promise<NDKRpcResponse> {
|
|
372
|
+
// TODO: 再実装する
|
|
260
373
|
let authUrlSent = false;
|
|
261
374
|
const now = Date.now();
|
|
262
375
|
return new Promise<NDKRpcResponse>(() => {
|
|
@@ -280,22 +393,43 @@ class NostrRpc extends NDKNostrRpc {
|
|
|
280
393
|
});
|
|
281
394
|
}
|
|
282
395
|
|
|
283
|
-
protected async createRequestEvent(id: string, remotePubkey: string, method: string, params: string[] = [], kind = 24133) {
|
|
396
|
+
protected async createRequestEvent(id: string, remotePubkey: string, method: string, params: string[] = [], kind = 24133): Promise<NostrEvent> {
|
|
397
|
+
// TODO: 元のNDKロジック:
|
|
398
|
+
// const event = new NDKEvent(this._ndk, { ... });
|
|
399
|
+
// await event.sign(this._signer);
|
|
400
|
+
//
|
|
401
|
+
// New Implementation Plan:
|
|
402
|
+
// 1. Create a plain event object:
|
|
403
|
+
// { kind, content, tags, pubkey, created_at: Math.floor(Date.now()/1000) }
|
|
404
|
+
// 2. Encrypt content:
|
|
405
|
+
// if (useNip44) await signer.encryptNip44(remotePubkey, content)
|
|
406
|
+
// else await signer.encrypt(remotePubkey, content)
|
|
407
|
+
// 3. Sign:
|
|
408
|
+
// event.id = getEventHash(event)
|
|
409
|
+
// event.sig = await signer.signEvent(event)
|
|
410
|
+
// 4. Return the signed event object.
|
|
411
|
+
|
|
284
412
|
this.requests.add(id);
|
|
285
|
-
const localUser = await this._signer.user();
|
|
286
|
-
const remoteUser = this._ndk.getUser({ pubkey: remotePubkey });
|
|
413
|
+
const localUser = await this._signer.user(); // TODO: signerプロパティから取得する
|
|
414
|
+
// const remoteUser = this._ndk.getUser({ pubkey: remotePubkey });
|
|
287
415
|
const request = { id, method, params };
|
|
288
416
|
|
|
289
|
-
|
|
417
|
+
// Placeholder event construction
|
|
418
|
+
const event = {
|
|
290
419
|
kind,
|
|
291
420
|
content: JSON.stringify(request),
|
|
292
421
|
tags: [['p', remotePubkey]],
|
|
293
422
|
pubkey: localUser.pubkey,
|
|
294
|
-
|
|
423
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
424
|
+
} as any;
|
|
295
425
|
|
|
296
426
|
const useNip44 = this._useNip44 && method !== 'create_account';
|
|
297
427
|
const encrypt = useNip44 ? this._signer.encryptNip44 : this._signer.encrypt;
|
|
298
|
-
|
|
428
|
+
|
|
429
|
+
// NOTE: Adjust encrypt to accept string pubkey
|
|
430
|
+
event.content = await encrypt.call(this._signer, remotePubkey as any, event.content);
|
|
431
|
+
|
|
432
|
+
// NOTE: This assumes _signer has a sign() method compatible with the event
|
|
299
433
|
await event.sign(this._signer);
|
|
300
434
|
|
|
301
435
|
return event;
|
|
@@ -310,27 +444,29 @@ export class IframeNostrRpc extends NostrRpc {
|
|
|
310
444
|
private lastResponseTime: number = Date.now();
|
|
311
445
|
private heartbeatTimeoutMs: number = 30000; // 30秒応答がなければ再接続
|
|
312
446
|
|
|
313
|
-
public constructor(
|
|
314
|
-
super(
|
|
315
|
-
this._ndk = ndk;
|
|
447
|
+
public constructor(rxNostr: RxNostr, localSigner: PrivateKeySigner, iframePeerOrigin?: string) {
|
|
448
|
+
super(rxNostr, localSigner);
|
|
316
449
|
this.peerOrigin = iframePeerOrigin;
|
|
317
450
|
}
|
|
318
451
|
|
|
319
|
-
public async subscribe(filter:
|
|
452
|
+
public async subscribe(filter: NostrFilter): Promise<NostrSubscription> {
|
|
320
453
|
if (!this.peerOrigin) return super.subscribe(filter);
|
|
454
|
+
return super.subscribe(filter);
|
|
455
|
+
/*
|
|
321
456
|
return new NDKSubscription(
|
|
322
457
|
this._ndk,
|
|
323
458
|
{},
|
|
324
459
|
{
|
|
325
460
|
// don't send to relay
|
|
326
461
|
closeOnEose: true,
|
|
327
|
-
cacheUsage:
|
|
462
|
+
cacheUsage: 'ONLY_CACHE',
|
|
328
463
|
},
|
|
329
464
|
);
|
|
465
|
+
*/
|
|
330
466
|
}
|
|
331
467
|
|
|
332
468
|
// ハートビート開始
|
|
333
|
-
private startHeartbeat() {
|
|
469
|
+
private startHeartbeat(): void {
|
|
334
470
|
this.stopHeartbeat();
|
|
335
471
|
|
|
336
472
|
this.heartbeatInterval = window.setInterval(async () => {
|
|
@@ -343,14 +479,14 @@ export class IframeNostrRpc extends NostrRpc {
|
|
|
343
479
|
}, 10000); // 10秒ごとにチェック
|
|
344
480
|
}
|
|
345
481
|
|
|
346
|
-
private stopHeartbeat() {
|
|
482
|
+
private stopHeartbeat(): void {
|
|
347
483
|
if (this.heartbeatInterval) {
|
|
348
484
|
clearInterval(this.heartbeatInterval);
|
|
349
485
|
this.heartbeatInterval = undefined;
|
|
350
486
|
}
|
|
351
487
|
}
|
|
352
488
|
|
|
353
|
-
public setWorkerIframePort(port: MessagePort) {
|
|
489
|
+
public setWorkerIframePort(port: MessagePort): void {
|
|
354
490
|
if (!this.peerOrigin) throw new Error('Unexpected iframe port');
|
|
355
491
|
|
|
356
492
|
this.iframePort = port;
|
|
@@ -376,8 +512,8 @@ export class IframeNostrRpc extends NostrRpc {
|
|
|
376
512
|
const event = ev.data;
|
|
377
513
|
|
|
378
514
|
if (!validateEvent(event)) throw new Error('Invalid event from iframe');
|
|
379
|
-
if (!
|
|
380
|
-
const nevent =
|
|
515
|
+
if (!verifyEvent(event)) throw new Error('Invalid event signature from iframe');
|
|
516
|
+
const nevent = event as NostrEvent;
|
|
381
517
|
const parsedEvent = await this.parseEvent(nevent);
|
|
382
518
|
// レスポンス受信時にタイムスタンプを更新
|
|
383
519
|
this.lastResponseTime = Date.now();
|
|
@@ -398,23 +534,22 @@ export class IframeNostrRpc extends NostrRpc {
|
|
|
398
534
|
// create and sign request event
|
|
399
535
|
const event = await this.createRequestEvent(id, remotePubkey, method, params, kind);
|
|
400
536
|
|
|
401
|
-
// set response handler
|
|
402
|
-
// and also dedup response handlers - we're sending
|
|
403
|
-
// to relays and to iframe
|
|
537
|
+
// set response handler
|
|
404
538
|
this.setResponseHandler(id, cb);
|
|
405
539
|
|
|
406
540
|
if (this.iframePort) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
this.iframeRequests.set(event.id, { id, pubkey: remotePubkey });
|
|
411
|
-
|
|
412
|
-
// send to iframe
|
|
413
|
-
console.log('iframe-nip46 sending request to', this.peerOrigin, event.rawEvent());
|
|
414
|
-
this.iframePort.postMessage(event.rawEvent());
|
|
541
|
+
this.iframeRequests.set(event.id!, { id, pubkey: remotePubkey });
|
|
542
|
+
console.log('iframe-nip46 sending request to', this.peerOrigin, event);
|
|
543
|
+
this.iframePort.postMessage(event);
|
|
415
544
|
} else {
|
|
416
|
-
// send to relays
|
|
417
|
-
|
|
545
|
+
// send to relays using rx-nostr
|
|
546
|
+
this.rxNostr.send(event).subscribe({
|
|
547
|
+
next: (packet) => {
|
|
548
|
+
console.log('Nip46 request published to', packet.from);
|
|
549
|
+
},
|
|
550
|
+
error: (err) => console.error('Nip46 publish error', err),
|
|
551
|
+
complete: () => console.log('Nip46 publish complete')
|
|
552
|
+
});
|
|
418
553
|
}
|
|
419
554
|
|
|
420
555
|
// see notes in 'super'
|
|
@@ -472,15 +607,20 @@ export class ReadyListener {
|
|
|
472
607
|
}
|
|
473
608
|
}
|
|
474
609
|
|
|
475
|
-
|
|
610
|
+
// TODO: NDKの継承を削除する
|
|
611
|
+
export class Nip46Signer extends EventEmitter {
|
|
612
|
+
public remotePubkey: string = '';
|
|
613
|
+
// @ts-ignore
|
|
614
|
+
public rpc: NostrRpc;
|
|
476
615
|
private _userPubkey: string = '';
|
|
477
616
|
private _rpc: IframeNostrRpc;
|
|
478
617
|
|
|
479
|
-
constructor(
|
|
480
|
-
super(
|
|
618
|
+
constructor(rxNostr: RxNostr, localSigner: PrivateKeySigner, signerPubkey: string, iframeOrigin?: string) {
|
|
619
|
+
super();
|
|
620
|
+
// super(ndk, signerPubkey, localSigner);
|
|
481
621
|
|
|
482
622
|
// override with our own rpc implementation
|
|
483
|
-
this._rpc = new IframeNostrRpc(
|
|
623
|
+
this._rpc = new IframeNostrRpc(rxNostr, localSigner, iframeOrigin);
|
|
484
624
|
this._rpc.setUseNip44(true); // !!this.params.optionsModal.dev);
|
|
485
625
|
this._rpc.on('authUrl', (url: string) => {
|
|
486
626
|
this.emit('authUrl', url);
|
|
@@ -489,11 +629,11 @@ export class Nip46Signer extends NDKNip46Signer {
|
|
|
489
629
|
this.rpc = this._rpc;
|
|
490
630
|
}
|
|
491
631
|
|
|
492
|
-
get userPubkey() {
|
|
632
|
+
get userPubkey(): string {
|
|
493
633
|
return this._userPubkey;
|
|
494
634
|
}
|
|
495
635
|
|
|
496
|
-
private async setSignerPubkey(signerPubkey: string, sameAsUser: boolean = false) {
|
|
636
|
+
private async setSignerPubkey(signerPubkey: string, sameAsUser: boolean = false): Promise<void> {
|
|
497
637
|
console.log('setSignerPubkey', signerPubkey);
|
|
498
638
|
|
|
499
639
|
// ensure it's set
|
|
@@ -508,7 +648,7 @@ export class Nip46Signer extends NDKNip46Signer {
|
|
|
508
648
|
await this.initUserPubkey(sameAsUser ? signerPubkey : '');
|
|
509
649
|
}
|
|
510
650
|
|
|
511
|
-
public async initUserPubkey(hintPubkey?: string) {
|
|
651
|
+
public async initUserPubkey(hintPubkey?: string): Promise<void> {
|
|
512
652
|
if (this._userPubkey) throw new Error('Already called initUserPubkey');
|
|
513
653
|
|
|
514
654
|
if (hintPubkey) {
|
|
@@ -525,7 +665,7 @@ export class Nip46Signer extends NDKNip46Signer {
|
|
|
525
665
|
if (response.error) {
|
|
526
666
|
err(new Error(response.error));
|
|
527
667
|
} else {
|
|
528
|
-
ok(response.result);
|
|
668
|
+
ok(response.result || '');
|
|
529
669
|
}
|
|
530
670
|
});
|
|
531
671
|
}),
|
|
@@ -534,23 +674,23 @@ export class Nip46Signer extends NDKNip46Signer {
|
|
|
534
674
|
);
|
|
535
675
|
}
|
|
536
676
|
|
|
537
|
-
public async listen(nostrConnectSecret: string) {
|
|
677
|
+
public async listen(nostrConnectSecret: string): Promise<void> {
|
|
538
678
|
const signerPubkey = await (this.rpc as IframeNostrRpc).listen(nostrConnectSecret);
|
|
539
679
|
await this.setSignerPubkey(signerPubkey);
|
|
540
680
|
}
|
|
541
681
|
|
|
542
|
-
public async connect(token?: string, perms?: string) {
|
|
682
|
+
public async connect(token?: string, perms?: string): Promise<void> {
|
|
543
683
|
if (!this.remotePubkey) throw new Error('No signer pubkey');
|
|
544
684
|
await (this._rpc as any).connectWithTimeout(this.remotePubkey, token, perms, NIP46_CONNECT_TIMEOUT);
|
|
545
685
|
await this.setSignerPubkey(this.remotePubkey);
|
|
546
686
|
}
|
|
547
687
|
|
|
548
|
-
public async setListenReply(reply: any, nostrConnectSecret: string) {
|
|
688
|
+
public async setListenReply(reply: any, nostrConnectSecret: string): Promise<void> {
|
|
549
689
|
const signerPubkey = await this._rpc.parseNostrConnectReply(reply, nostrConnectSecret);
|
|
550
690
|
await this.setSignerPubkey(signerPubkey, true);
|
|
551
691
|
}
|
|
552
692
|
|
|
553
|
-
public async createAccount2({ bunkerPubkey, name, domain, perms = '' }: { bunkerPubkey: string; name: string; domain: string; perms?: string }) {
|
|
693
|
+
public async createAccount2({ bunkerPubkey, name, domain, perms = '' }: { bunkerPubkey: string; name: string; domain: string; perms?: string }): Promise<string> {
|
|
554
694
|
const params = [
|
|
555
695
|
name,
|
|
556
696
|
domain,
|
|
@@ -563,10 +703,52 @@ export class Nip46Signer extends NDKNip46Signer {
|
|
|
563
703
|
});
|
|
564
704
|
|
|
565
705
|
console.log('create_account pubkey', r);
|
|
566
|
-
if (r.result === 'error') {
|
|
567
|
-
throw new Error(r.error);
|
|
706
|
+
if (r.result === 'error' || !r.result) {
|
|
707
|
+
throw new Error(r.error || 'Unknown error');
|
|
568
708
|
}
|
|
569
709
|
|
|
570
710
|
return r.result;
|
|
571
711
|
}
|
|
712
|
+
// TODO: 必要であればNDKNip46Signerから不足しているメソッドを実装する
|
|
713
|
+
public async encrypt(recipient: any, value: string): Promise<string> {
|
|
714
|
+
const recipientPubkey = typeof recipient === 'string' ? recipient : recipient.pubkey;
|
|
715
|
+
return await this.rpcSend('nip04_encrypt', [recipientPubkey, value]);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
public async decrypt(sender: any, value: string): Promise<string> {
|
|
719
|
+
const senderPubkey = typeof sender === 'string' ? sender : sender.pubkey;
|
|
720
|
+
return await this.rpcSend('nip04_decrypt', [senderPubkey, value]);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
public async encryptNip44(recipient: any, value: string): Promise<string> {
|
|
724
|
+
const recipientPubkey = typeof recipient === 'string' ? recipient : recipient.pubkey;
|
|
725
|
+
return await this.rpcSend('nip44_encrypt', [recipientPubkey, value]);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
public async decryptNip44(sender: any, value: string): Promise<string> {
|
|
729
|
+
const senderPubkey = typeof sender === 'string' ? sender : sender.pubkey;
|
|
730
|
+
return await this.rpcSend('nip44_decrypt', [senderPubkey, value]);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
public async sign(event: any): Promise<string> {
|
|
734
|
+
const eventString = JSON.stringify(event);
|
|
735
|
+
const res = await this.rpcSend('sign_event', [eventString]);
|
|
736
|
+
// The result matches NIP-46 sign_event response which is the signed event (stringified json)
|
|
737
|
+
const signedEvent = JSON.parse(res);
|
|
738
|
+
event.sig = signedEvent.sig;
|
|
739
|
+
return signedEvent.sig;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
public async user(): Promise<any> {
|
|
743
|
+
return { pubkey: this.userPubkey };
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
private async rpcSend(method: string, params: any[]): Promise<string> {
|
|
747
|
+
return new Promise<string>((resolve, reject) => {
|
|
748
|
+
this.rpc.sendRequest(this.remotePubkey, method, params, 24133, (response: any) => {
|
|
749
|
+
if (response.error) reject(new Error(response.error));
|
|
750
|
+
else resolve(response.result);
|
|
751
|
+
});
|
|
752
|
+
});
|
|
753
|
+
}
|
|
572
754
|
}
|
|
@@ -60,6 +60,7 @@ class NostrExtensionService extends EventEmitter {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
private async setExtensionReadPubkey(expectedPubkey?: string) {
|
|
63
|
+
// @ts-ignore
|
|
63
64
|
window.nostr = this.nostrExtension;
|
|
64
65
|
// @ts-ignore
|
|
65
66
|
const pubkey = await window.nostr.getPublicKey();
|
|
@@ -81,6 +82,7 @@ class NostrExtensionService extends EventEmitter {
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
public unsetExtension(nostr: Nostr) {
|
|
85
|
+
// @ts-ignore
|
|
84
86
|
if (window.nostr === this.nostrExtension) {
|
|
85
87
|
// @ts-ignore
|
|
86
88
|
window.nostr = nostr;
|