@konemono/nostr-login 1.7.70 → 1.9.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 +10 -12
- package/dist/index.esm.js.map +1 -1
- package/dist/modules/AmberDirectSigner.d.ts +1 -0
- package/dist/modules/AuthNostrService.d.ts +5 -5
- package/dist/modules/Nip46.d.ts +47 -31
- package/dist/modules/NostrExtensionService.d.ts +5 -0
- package/dist/modules/Signer.d.ts +11 -4
- package/dist/modules/nip46/Nip46Adapter.d.ts +34 -0
- package/dist/modules/nip46/Nip46Client.d.ts +67 -0
- package/dist/modules/nip46/types.d.ts +32 -0
- package/dist/unpkg.js +10 -12
- package/dist/utils/index.d.ts +2 -3
- package/package.json +7 -4
- package/src/index.ts +1 -1
- package/src/modules/AmberDirectSigner.ts +205 -162
- package/src/modules/AuthNostrService.ts +186 -132
- package/src/modules/Nip46.iframe.test.ts +124 -0
- package/src/modules/Nip46.test.ts +31 -0
- package/src/modules/Nip46.ts +212 -261
- package/src/modules/NostrExtensionService.ts +6 -0
- package/src/modules/Signer.ts +39 -8
- package/src/modules/nip46/Nip46Adapter.ts +180 -0
- package/src/modules/nip46/Nip46Client.ts +356 -0
- package/src/modules/nip46/types.ts +34 -0
- package/src/utils/index.ts +63 -22
- package/tsconfig.json +2 -2
- package/vitest.config.ts +9 -0
package/src/modules/Nip46.ts
CHANGED
|
@@ -1,197 +1,190 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { validateEvent, verifySignature } from 'nostr-tools';
|
|
1
|
+
import { EventEmitter } from 'tseep';
|
|
2
|
+
import { validateEvent, verifySignature, nip04, getEventHash, getSignature } from 'nostr-tools';
|
|
3
|
+
import { SimplePool } from 'nostr-tools';
|
|
3
4
|
import { PrivateKeySigner } from './Signer';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
protected _ndk: NDK;
|
|
7
|
-
protected _signer: PrivateKeySigner;
|
|
8
|
-
protected requests: Set<string> = new Set();
|
|
9
|
-
private sub?: NDKSubscription;
|
|
10
|
-
protected _useNip44: boolean = false;
|
|
6
|
+
// Lightweight Nostr RPC for NIP-46 that does not rely on NDK
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
this._ndk = ndk;
|
|
15
|
-
this._signer = signer;
|
|
16
|
-
}
|
|
8
|
+
export type RpcRequest = { id: string; pubkey: string; method: string; params: any[]; event?: any };
|
|
9
|
+
export type RpcResponse = { id: string; result?: any; error?: any; event?: any };
|
|
17
10
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
11
|
+
export class NostrRpc extends EventEmitter {
|
|
12
|
+
protected localSigner: PrivateKeySigner;
|
|
13
|
+
protected localPubkey: string;
|
|
14
|
+
protected localPrivateKey: string;
|
|
15
|
+
protected remotePubkey: string = '';
|
|
16
|
+
protected pool: SimplePool;
|
|
17
|
+
protected relays: string[] = [];
|
|
18
|
+
protected subscription: any;
|
|
19
|
+
protected isSubscribed: boolean = false;
|
|
20
|
+
protected useNip44: boolean = false;
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
protected requests: Set<string> = new Set();
|
|
23
|
+
|
|
24
|
+
constructor(localSigner: PrivateKeySigner, relays: string[] = []) {
|
|
25
|
+
super();
|
|
26
|
+
this.localSigner = localSigner;
|
|
27
|
+
this.localPubkey = localSigner.pubkey;
|
|
28
|
+
this.localPrivateKey = (localSigner as any).privateKey;
|
|
29
|
+
this.pool = new SimplePool();
|
|
30
|
+
this.relays = relays && relays.length ? relays : [];
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
public setUseNip44(
|
|
33
|
-
this.
|
|
33
|
+
public setUseNip44(use: boolean) {
|
|
34
|
+
this.useNip44 = use;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
protected isNip04(ciphertext: string) {
|
|
37
38
|
const l = ciphertext.length;
|
|
38
39
|
if (l < 28) return false;
|
|
39
40
|
return ciphertext[l - 28] === '?' && ciphertext[l - 27] === 'i' && ciphertext[l - 26] === 'v' && ciphertext[l - 25] === '=';
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (method) {
|
|
52
|
-
return { id, pubkey: event.pubkey, method, params, event };
|
|
53
|
-
} else {
|
|
54
|
-
return { id, result, error, event };
|
|
43
|
+
protected async decryptEventContent(event: any) {
|
|
44
|
+
const decrypt = this.isNip04(event.content)
|
|
45
|
+
? this.localSigner.decrypt.bind(this.localSigner)
|
|
46
|
+
: this.localSigner.decryptNip44?.bind(this.localSigner) ?? this.localSigner.decrypt.bind(this.localSigner);
|
|
47
|
+
try {
|
|
48
|
+
const decrypted = await decrypt(event.pubkey || event.pubkey, event.content);
|
|
49
|
+
return JSON.parse(decrypted);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
throw e;
|
|
55
52
|
}
|
|
56
53
|
}
|
|
57
54
|
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const response = parsedEvent as NDKRpcResponse;
|
|
64
|
-
if (response.result !== secret) throw new Error(response.error);
|
|
65
|
-
return event.pubkey;
|
|
55
|
+
protected async parseEvent(event: any): Promise<RpcRequest | RpcResponse> {
|
|
56
|
+
const parsed = await this.decryptEventContent(event);
|
|
57
|
+
const { id, method, params, result, error } = parsed;
|
|
58
|
+
if (method) {
|
|
59
|
+
return { id, pubkey: event.pubkey, method, params, event } as RpcRequest;
|
|
66
60
|
} else {
|
|
67
|
-
|
|
61
|
+
return { id, result, error, event } as RpcResponse;
|
|
68
62
|
}
|
|
69
63
|
}
|
|
70
64
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
sub.on('event', async (event: NDKEvent) => {
|
|
83
|
-
try {
|
|
84
|
-
const parsedEvent = await this.parseEvent(event);
|
|
85
|
-
// console.log('ack parsedEvent', parsedEvent);
|
|
86
|
-
if (!(parsedEvent as NDKRpcRequest).method) {
|
|
87
|
-
const response = parsedEvent as NDKRpcResponse;
|
|
88
|
-
|
|
89
|
-
// ignore
|
|
90
|
-
if (response.result === 'auth_url') return;
|
|
91
|
-
|
|
92
|
-
// FIXME for now accept 'ack' replies, later on only
|
|
93
|
-
// accept secrets
|
|
94
|
-
if (response.result === 'ack' || response.result === nostrConnectSecret) {
|
|
95
|
-
ok(event.pubkey);
|
|
96
|
-
} else {
|
|
97
|
-
err(response.error);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
} catch (e) {
|
|
101
|
-
console.log('error parsing event', e, event.rawEvent());
|
|
102
|
-
}
|
|
103
|
-
// done
|
|
104
|
-
this.stop();
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// since ndk doesn't yet support perms param
|
|
110
|
-
// we reimplement the 'connect' call here
|
|
111
|
-
// instead of await signer.blockUntilReady();
|
|
112
|
-
public async connect(pubkey: string, token?: string, perms?: string) {
|
|
113
|
-
return new Promise<void>((ok, err) => {
|
|
114
|
-
const connectParams = [pubkey!, token || '', perms || ''];
|
|
115
|
-
this.sendRequest(pubkey!, 'connect', connectParams, 24133, (response: NDKRpcResponse) => {
|
|
116
|
-
if (response.result === 'ack') {
|
|
117
|
-
ok();
|
|
65
|
+
public subscribe(relays: string[], filter: any) {
|
|
66
|
+
if (this.isSubscribed) return;
|
|
67
|
+
this.relays = relays && relays.length ? relays : this.relays;
|
|
68
|
+
const since = Math.floor(Date.now() / 1000) - 60;
|
|
69
|
+
const filters = [{ ...filter, since }];
|
|
70
|
+
this.subscription = this.pool.sub(this.relays, filters);
|
|
71
|
+
this.subscription.on('event', async (ev: any) => {
|
|
72
|
+
try {
|
|
73
|
+
const parsed = await this.parseEvent(ev);
|
|
74
|
+
if (!(parsed as RpcRequest).method) {
|
|
75
|
+
this.emit(`response-${(parsed as RpcResponse).id}`, parsed as RpcResponse);
|
|
118
76
|
} else {
|
|
119
|
-
|
|
77
|
+
this.emit('request', parsed as RpcRequest);
|
|
120
78
|
}
|
|
121
|
-
})
|
|
79
|
+
} catch (e) {
|
|
80
|
+
// ignore parse errors
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
this.subscription.on('eose', () => {
|
|
84
|
+
/* noop */
|
|
122
85
|
});
|
|
86
|
+
this.isSubscribed = true;
|
|
123
87
|
}
|
|
124
88
|
|
|
125
|
-
|
|
126
|
-
|
|
89
|
+
public stop() {
|
|
90
|
+
if (this.subscription && this.subscription.unsub) {
|
|
91
|
+
try {
|
|
92
|
+
this.subscription.unsub();
|
|
93
|
+
} catch (e) {}
|
|
94
|
+
}
|
|
95
|
+
this.isSubscribed = false;
|
|
127
96
|
}
|
|
128
97
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
// response handler will deduplicate auth urls and responses
|
|
133
|
-
this.setResponseHandler(id, cb);
|
|
134
|
-
|
|
135
|
-
// create and sign request
|
|
136
|
-
const event = await this.createRequestEvent(id, remotePubkey, method, params, kind);
|
|
137
|
-
console.log("sendRequest", { event, method, remotePubkey, params });
|
|
138
|
-
|
|
139
|
-
// send to relays
|
|
140
|
-
await event.publish();
|
|
141
|
-
|
|
142
|
-
// NOTE: ndk returns a promise that never resolves and
|
|
143
|
-
// in fact REQUIRES cb to be provided (otherwise no way
|
|
144
|
-
// to consume the result), we've already stepped on the bug
|
|
145
|
-
// of waiting for this unresolvable result, so now we return
|
|
146
|
-
// undefined to make sure waiters fail, not hang.
|
|
147
|
-
// @ts-ignore
|
|
148
|
-
return undefined as NDKRpcResponse;
|
|
98
|
+
protected getId() {
|
|
99
|
+
return Math.random().toString(36).substring(7);
|
|
149
100
|
}
|
|
150
101
|
|
|
151
|
-
protected setResponseHandler(id: string, cb?: (res:
|
|
102
|
+
protected setResponseHandler(id: string, cb?: (res: RpcResponse) => void) {
|
|
152
103
|
let authUrlSent = false;
|
|
153
104
|
const now = Date.now();
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
} else if (cb) {
|
|
163
|
-
if (this.requests.has(id)) {
|
|
164
|
-
this.requests.delete(id);
|
|
165
|
-
console.log('nostr-login processed nip46 request in', Date.now() - now, 'ms');
|
|
166
|
-
cb(response);
|
|
167
|
-
}
|
|
105
|
+
|
|
106
|
+
const responseHandler = (response: RpcResponse) => {
|
|
107
|
+
if (response.result === 'auth_url') {
|
|
108
|
+
// reattach for auth_url so we can get final response later
|
|
109
|
+
this.once(`response-${id}`, responseHandler);
|
|
110
|
+
if (!authUrlSent) {
|
|
111
|
+
authUrlSent = true;
|
|
112
|
+
this.emit('authUrl', response.error);
|
|
168
113
|
}
|
|
169
|
-
}
|
|
114
|
+
} else if (cb) {
|
|
115
|
+
if (this.requests.has(id)) {
|
|
116
|
+
this.requests.delete(id);
|
|
117
|
+
cb(response);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
170
121
|
|
|
171
|
-
|
|
172
|
-
});
|
|
122
|
+
this.once(`response-${id}`, responseHandler);
|
|
173
123
|
}
|
|
174
124
|
|
|
175
|
-
protected async createRequestEvent(id: string, remotePubkey: string, method: string, params:
|
|
125
|
+
protected async createRequestEvent(id: string, remotePubkey: string, method: string, params: any[] = [], kind = 24133) {
|
|
176
126
|
this.requests.add(id);
|
|
177
|
-
const localUser = await this._signer.user();
|
|
178
|
-
const remoteUser = this._ndk.getUser({ pubkey: remotePubkey });
|
|
179
127
|
const request = { id, method, params };
|
|
128
|
+
// encrypt
|
|
129
|
+
const content =
|
|
130
|
+
this.useNip44 && method !== 'create_account' && this.localSigner.encryptNip44
|
|
131
|
+
? await this.localSigner.encryptNip44(remotePubkey, JSON.stringify(request))
|
|
132
|
+
: await this.localSigner.encrypt(remotePubkey, JSON.stringify(request));
|
|
180
133
|
|
|
181
|
-
const event =
|
|
134
|
+
const event: any = {
|
|
182
135
|
kind,
|
|
183
|
-
content
|
|
136
|
+
content,
|
|
184
137
|
tags: [['p', remotePubkey]],
|
|
185
|
-
pubkey:
|
|
186
|
-
|
|
138
|
+
pubkey: this.localPubkey,
|
|
139
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
140
|
+
};
|
|
187
141
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
event.content = await encrypt.call(this._signer, remoteUser, event.content);
|
|
191
|
-
await event.sign(this._signer);
|
|
142
|
+
// sign using signer
|
|
143
|
+
await this.localSigner.sign(event as any);
|
|
192
144
|
|
|
193
145
|
return event;
|
|
194
146
|
}
|
|
147
|
+
|
|
148
|
+
protected async publishRequest(event: any) {
|
|
149
|
+
try {
|
|
150
|
+
await Promise.any(this.pool.publish(this.relays, event));
|
|
151
|
+
} catch (e) {
|
|
152
|
+
// swallow publish errors
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public async sendRequest(remotePubkey: string, method: string, params: any[] = [], kind = 24133, cb?: (res: RpcResponse) => void): Promise<RpcResponse | undefined> {
|
|
157
|
+
const id = this.getId();
|
|
158
|
+
this.setResponseHandler(id, cb);
|
|
159
|
+
const event = await this.createRequestEvent(id, remotePubkey, method, params, kind);
|
|
160
|
+
await this.publishRequest(event);
|
|
161
|
+
return undefined as any;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
public async listen(nostrConnectSecret: string, relays?: string[]) {
|
|
165
|
+
const pubkey = this.localPubkey;
|
|
166
|
+
this.subscribe(relays || this.relays, { 'kinds': [24133], '#p': [pubkey] });
|
|
167
|
+
return new Promise<string>((ok, err) => {
|
|
168
|
+
const handler = async (event: any) => {
|
|
169
|
+
try {
|
|
170
|
+
const parsed = await this.parseEvent(event);
|
|
171
|
+
if ((parsed as RpcResponse).result === 'auth_url') return; // ignore
|
|
172
|
+
const response = parsed as RpcResponse;
|
|
173
|
+
if (response.result === 'ack' || response.result === nostrConnectSecret) {
|
|
174
|
+
ok(event.pubkey);
|
|
175
|
+
this.stop();
|
|
176
|
+
} else {
|
|
177
|
+
err(response.error);
|
|
178
|
+
this.stop();
|
|
179
|
+
}
|
|
180
|
+
} catch (e) {
|
|
181
|
+
// ignore
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
this.once('request', handler);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
195
188
|
}
|
|
196
189
|
|
|
197
190
|
export class IframeNostrRpc extends NostrRpc {
|
|
@@ -199,92 +192,66 @@ export class IframeNostrRpc extends NostrRpc {
|
|
|
199
192
|
private iframePort?: MessagePort;
|
|
200
193
|
private iframeRequests = new Map<string, { id: string; pubkey: string }>();
|
|
201
194
|
|
|
202
|
-
|
|
203
|
-
super(
|
|
204
|
-
this._ndk = ndk;
|
|
195
|
+
constructor(localSigner: PrivateKeySigner, iframePeerOrigin?: string, relays: string[] = []) {
|
|
196
|
+
super(localSigner, relays);
|
|
205
197
|
this.peerOrigin = iframePeerOrigin;
|
|
206
198
|
}
|
|
207
199
|
|
|
208
|
-
public async subscribe(filter: NDKFilter): Promise<NDKSubscription> {
|
|
209
|
-
if (!this.peerOrigin) return super.subscribe(filter);
|
|
210
|
-
return new NDKSubscription(
|
|
211
|
-
this._ndk,
|
|
212
|
-
{},
|
|
213
|
-
{
|
|
214
|
-
// don't send to relay
|
|
215
|
-
closeOnEose: true,
|
|
216
|
-
cacheUsage: NDKSubscriptionCacheUsage.ONLY_CACHE,
|
|
217
|
-
},
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
200
|
public setWorkerIframePort(port: MessagePort) {
|
|
222
201
|
if (!this.peerOrigin) throw new Error('Unexpected iframe port');
|
|
223
|
-
|
|
224
202
|
this.iframePort = port;
|
|
225
203
|
|
|
226
|
-
//
|
|
204
|
+
// keep the channel alive
|
|
227
205
|
setInterval(() => {
|
|
228
|
-
|
|
229
|
-
|
|
206
|
+
try {
|
|
207
|
+
this.iframePort!.postMessage('ping');
|
|
208
|
+
} catch (e) {}
|
|
230
209
|
}, 5000);
|
|
231
210
|
|
|
232
|
-
|
|
233
|
-
|
|
211
|
+
this.iframePort.onmessage = async ev => {
|
|
212
|
+
// handle special error reply
|
|
234
213
|
if (typeof ev.data === 'string' && ev.data.startsWith('errorNoKey')) {
|
|
235
214
|
const event_id = ev.data.split(':')[1];
|
|
236
|
-
const
|
|
215
|
+
const entry = this.iframeRequests.get(event_id) || { id: '', pubkey: '' };
|
|
216
|
+
const { id = '', pubkey = '' } = entry;
|
|
237
217
|
if (id && pubkey && this.requests.has(id)) this.emit(`iframeRestart-${pubkey}`);
|
|
238
218
|
return;
|
|
239
219
|
}
|
|
240
220
|
|
|
241
|
-
// a copy-paste from rpc.subscribe
|
|
242
221
|
try {
|
|
243
222
|
const event = ev.data;
|
|
244
|
-
|
|
245
223
|
if (!validateEvent(event)) throw new Error('Invalid event from iframe');
|
|
246
224
|
if (!verifySignature(event)) throw new Error('Invalid event signature from iframe');
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (!(parsedEvent as NDKRpcRequest).method) {
|
|
251
|
-
console.log('parsed response', parsedEvent);
|
|
252
|
-
this.emit(`response-${parsedEvent.id}`, parsedEvent);
|
|
225
|
+
const parsed = await this.parseEvent(event);
|
|
226
|
+
if (!(parsed as RpcRequest).method) {
|
|
227
|
+
this.emit(`response-${(parsed as RpcResponse).id}`, parsed as RpcResponse);
|
|
253
228
|
}
|
|
254
229
|
} catch (e) {
|
|
255
|
-
|
|
230
|
+
// ignore parse errors
|
|
256
231
|
}
|
|
257
232
|
};
|
|
258
233
|
}
|
|
259
234
|
|
|
260
|
-
public async sendRequest(remotePubkey: string, method: string, params:
|
|
235
|
+
public async sendRequest(remotePubkey: string, method: string, params: any[] = [], kind = 24133, cb?: (res: RpcResponse) => void): Promise<RpcResponse | undefined> {
|
|
261
236
|
const id = this.getId();
|
|
262
|
-
|
|
263
|
-
// create and sign request event
|
|
237
|
+
this.setResponseHandler(id, cb);
|
|
264
238
|
const event = await this.createRequestEvent(id, remotePubkey, method, params, kind);
|
|
265
239
|
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
// to relays and to iframe
|
|
269
|
-
this.setResponseHandler(id, cb);
|
|
240
|
+
// map request event id -> id for iframe restarts
|
|
241
|
+
this.iframeRequests.set(event.id, { id, pubkey: remotePubkey });
|
|
270
242
|
|
|
271
243
|
if (this.iframePort) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
console.log('iframe-nip46 sending request to', this.peerOrigin, event.rawEvent());
|
|
279
|
-
this.iframePort.postMessage(event.rawEvent());
|
|
244
|
+
try {
|
|
245
|
+
this.iframePort.postMessage(event);
|
|
246
|
+
} catch (e) {
|
|
247
|
+
// fallthrough to publish to relays as well
|
|
248
|
+
await this.publishRequest(event);
|
|
249
|
+
}
|
|
280
250
|
} else {
|
|
281
|
-
|
|
282
|
-
await event.publish();
|
|
251
|
+
await this.publishRequest(event);
|
|
283
252
|
}
|
|
284
253
|
|
|
285
|
-
|
|
286
|
-
// @ts-ignore
|
|
287
|
-
return undefined as NDKRpcResponse;
|
|
254
|
+
return undefined as any;
|
|
288
255
|
}
|
|
289
256
|
}
|
|
290
257
|
|
|
@@ -297,20 +264,14 @@ export class ReadyListener {
|
|
|
297
264
|
this.origin = origin;
|
|
298
265
|
this.messages = messages;
|
|
299
266
|
this.promise = new Promise<any>(ok => {
|
|
300
|
-
console.log(new Date(), 'started listener for', this.messages);
|
|
301
|
-
|
|
302
|
-
// ready message handler
|
|
303
267
|
const onReady = async (e: MessageEvent) => {
|
|
304
268
|
const originHostname = new URL(origin!).hostname;
|
|
305
269
|
const messageHostname = new URL(e.origin).hostname;
|
|
306
|
-
// same host or subdomain
|
|
307
270
|
const validHost = messageHostname === originHostname || messageHostname.endsWith('.' + originHostname);
|
|
308
271
|
if (!validHost || !Array.isArray(e.data) || !e.data.length || !this.messages.includes(e.data[0])) {
|
|
309
|
-
// console.log(new Date(), 'got invalid ready message', e.origin, e.data);
|
|
310
272
|
return;
|
|
311
273
|
}
|
|
312
274
|
|
|
313
|
-
console.log(new Date(), 'got ready message from', e.origin, e.data);
|
|
314
275
|
window.removeEventListener('message', onReady);
|
|
315
276
|
ok(e.data);
|
|
316
277
|
};
|
|
@@ -319,39 +280,32 @@ export class ReadyListener {
|
|
|
319
280
|
}
|
|
320
281
|
|
|
321
282
|
async wait(): Promise<any> {
|
|
322
|
-
console.log(new Date(), 'waiting for', this.messages);
|
|
323
283
|
const r = await this.promise;
|
|
324
|
-
// NOTE: timer here doesn't help bcs it must be activated when
|
|
325
|
-
// user "confirms", but that's happening on a different
|
|
326
|
-
// origin and we can't really know.
|
|
327
|
-
// await new Promise<any>((ok, err) => {
|
|
328
|
-
// // 10 sec should be more than enough
|
|
329
|
-
// setTimeout(() => err(new Date() + ' timeout for ' + this.message), 10000);
|
|
330
|
-
|
|
331
|
-
// // if promise already resolved or will resolve in the future
|
|
332
|
-
// this.promise.then(ok);
|
|
333
|
-
// });
|
|
334
|
-
|
|
335
|
-
console.log(new Date(), 'finished waiting for', this.messages, r);
|
|
336
284
|
return r;
|
|
337
285
|
}
|
|
338
286
|
}
|
|
339
287
|
|
|
340
|
-
export class Nip46Signer extends
|
|
288
|
+
export class Nip46Signer extends EventEmitter {
|
|
341
289
|
private _userPubkey: string = '';
|
|
342
|
-
|
|
290
|
+
public remotePubkey: string = '';
|
|
291
|
+
public rpc: IframeNostrRpc | NostrRpc;
|
|
292
|
+
private localSigner: PrivateKeySigner;
|
|
343
293
|
|
|
344
|
-
constructor(
|
|
345
|
-
super(
|
|
294
|
+
constructor(localSigner: PrivateKeySigner, signerPubkey: string, iframeOrigin?: string, relays: string[] = []) {
|
|
295
|
+
super();
|
|
296
|
+
this.remotePubkey = signerPubkey;
|
|
297
|
+
this.localSigner = localSigner;
|
|
298
|
+
|
|
299
|
+
if (iframeOrigin) {
|
|
300
|
+
this.rpc = new IframeNostrRpc(localSigner, iframeOrigin, relays);
|
|
301
|
+
} else {
|
|
302
|
+
this.rpc = new NostrRpc(localSigner, relays);
|
|
303
|
+
}
|
|
304
|
+
(this.rpc as any).setUseNip44(true);
|
|
346
305
|
|
|
347
|
-
|
|
348
|
-
this._rpc = new IframeNostrRpc(ndk, localSigner, iframeOrigin);
|
|
349
|
-
this._rpc.setUseNip44(true); // !!this.params.optionsModal.dev);
|
|
350
|
-
this._rpc.on('authUrl', (url: string) => {
|
|
306
|
+
this.rpc.on('authUrl', (url: string) => {
|
|
351
307
|
this.emit('authUrl', url);
|
|
352
308
|
});
|
|
353
|
-
|
|
354
|
-
this.rpc = this._rpc;
|
|
355
309
|
}
|
|
356
310
|
|
|
357
311
|
get userPubkey() {
|
|
@@ -359,17 +313,12 @@ export class Nip46Signer extends NDKNip46Signer {
|
|
|
359
313
|
}
|
|
360
314
|
|
|
361
315
|
private async setSignerPubkey(signerPubkey: string, sameAsUser: boolean = false) {
|
|
362
|
-
console.log("setSignerPubkey", signerPubkey);
|
|
363
|
-
|
|
364
|
-
// ensure it's set
|
|
365
316
|
this.remotePubkey = signerPubkey;
|
|
366
317
|
|
|
367
|
-
|
|
368
|
-
this._rpc.on(`iframeRestart-${signerPubkey}`, () => {
|
|
318
|
+
this.rpc.on(`iframeRestart-${signerPubkey}`, () => {
|
|
369
319
|
this.emit('iframeRestart');
|
|
370
320
|
});
|
|
371
321
|
|
|
372
|
-
// now call getPublicKey and swap remotePubkey w/ that
|
|
373
322
|
await this.initUserPubkey(sameAsUser ? signerPubkey : '');
|
|
374
323
|
}
|
|
375
324
|
|
|
@@ -383,47 +332,49 @@ export class Nip46Signer extends NDKNip46Signer {
|
|
|
383
332
|
|
|
384
333
|
this._userPubkey = await new Promise<string>((ok, err) => {
|
|
385
334
|
if (!this.remotePubkey) throw new Error('Signer pubkey not set');
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
this._rpc.sendRequest(this.remotePubkey, 'get_public_key', [], 24133, (response: NDKRpcResponse) => {
|
|
335
|
+
this.rpc.sendRequest(this.remotePubkey, 'get_public_key', [], 24133, (response: RpcResponse) => {
|
|
336
|
+
if (response.error) return err(response.error);
|
|
389
337
|
ok(response.result);
|
|
390
338
|
});
|
|
391
339
|
});
|
|
392
340
|
}
|
|
393
341
|
|
|
394
342
|
public async listen(nostrConnectSecret: string) {
|
|
395
|
-
const signerPubkey = await (this.rpc as
|
|
343
|
+
const signerPubkey = await (this.rpc as any).listen(nostrConnectSecret, (this.rpc as any).relays);
|
|
396
344
|
await this.setSignerPubkey(signerPubkey);
|
|
397
345
|
}
|
|
398
346
|
|
|
399
347
|
public async connect(token?: string, perms?: string) {
|
|
400
348
|
if (!this.remotePubkey) throw new Error('No signer pubkey');
|
|
401
|
-
|
|
402
|
-
|
|
349
|
+
return new Promise<void>((ok, err) => {
|
|
350
|
+
const params = [this.localSigner.pubkey, token || '', perms || ''];
|
|
351
|
+
this.rpc.sendRequest(this.remotePubkey, 'connect', params, 24133, (response: RpcResponse) => {
|
|
352
|
+
if (response.result === 'ack') ok();
|
|
353
|
+
else err(response.error);
|
|
354
|
+
});
|
|
355
|
+
});
|
|
403
356
|
}
|
|
404
357
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
358
|
+
// convenience wrappers
|
|
359
|
+
public async createAccount2(params: any) {
|
|
360
|
+
return new Promise((ok, err) => {
|
|
361
|
+
this.rpc.sendRequest(this.remotePubkey, 'create_account', [params], 24133, (response: RpcResponse) => {
|
|
362
|
+
if (response.error) err(response.error);
|
|
363
|
+
else ok(response.result);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
408
366
|
}
|
|
409
367
|
|
|
410
|
-
public async
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
domain,
|
|
414
|
-
'', // email
|
|
415
|
-
perms,
|
|
416
|
-
];
|
|
417
|
-
|
|
418
|
-
const r = await new Promise<NDKRpcResponse>(ok => {
|
|
419
|
-
this.rpc.sendRequest(bunkerPubkey, 'create_account', params, undefined, ok);
|
|
420
|
-
});
|
|
368
|
+
public async encrypt(pubkey: string, plaintext: string) {
|
|
369
|
+
return this.localSigner.encrypt(pubkey, plaintext);
|
|
370
|
+
}
|
|
421
371
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
372
|
+
public async decrypt(pubkey: string, ciphertext: string) {
|
|
373
|
+
return this.localSigner.decrypt(pubkey, ciphertext);
|
|
374
|
+
}
|
|
426
375
|
|
|
427
|
-
|
|
376
|
+
public async sign(event: any) {
|
|
377
|
+
await this.localSigner.sign(event);
|
|
378
|
+
return event.sig;
|
|
428
379
|
}
|
|
429
380
|
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { Nostr, NostrParams } from './';
|
|
2
2
|
import { EventEmitter } from 'tseep';
|
|
3
3
|
|
|
4
|
+
declare global {
|
|
5
|
+
interface Window {
|
|
6
|
+
nostr?: any;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
class NostrExtensionService extends EventEmitter {
|
|
5
11
|
private params: NostrParams;
|
|
6
12
|
private nostrExtension: any | undefined;
|