@volter/tabnode 0.5.21 → 0.5.23
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/__sw__.js
CHANGED
|
@@ -1,48 +1,123 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Service Worker for Mini WebContainers
|
|
3
3
|
* Intercepts fetch requests and routes them to virtual servers
|
|
4
|
-
* Version:
|
|
4
|
+
* Version: 16 - a host per page: each page's channel, servers and preview are its own
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const DEBUG = false;
|
|
8
8
|
|
|
9
|
-
//
|
|
10
|
-
|
|
9
|
+
// Every page that holds a container's servers is a host of its own: its own
|
|
10
|
+
// channel, its own servers, its own preview. Two pages of one origin, each
|
|
11
|
+
// with its container, share this worker and never each other's ports. A host
|
|
12
|
+
// is keyed by the id of the window that sent `init`.
|
|
13
|
+
const hosts = new Map();
|
|
14
|
+
let latestHost = null;
|
|
15
|
+
// The documents and workers a host's servers made, by client id, so their
|
|
16
|
+
// requests reach the host that served them.
|
|
17
|
+
const clientHosts = new Map();
|
|
18
|
+
// The event a request arrived with, so every route to a server can ask which
|
|
19
|
+
// host it is for without the event being passed along.
|
|
20
|
+
const requestEvents = new WeakMap();
|
|
21
|
+
|
|
22
|
+
function openHost(id, channel) {
|
|
23
|
+
const previous = hosts.get(id);
|
|
24
|
+
if (previous) {
|
|
25
|
+
for (const pending of pendingRequests.values()) {
|
|
26
|
+
if (pending.host === previous && pending.flowControl === 1) pending.fail(new Error('Host connection replaced'));
|
|
27
|
+
}
|
|
28
|
+
for (const upload of controlledUploads) {
|
|
29
|
+
if (upload.host === previous) upload.controller.abort(new Error('Host connection replaced'));
|
|
30
|
+
}
|
|
31
|
+
previous.port.onmessage = null;
|
|
32
|
+
previous.port.close();
|
|
33
|
+
}
|
|
34
|
+
const host = {
|
|
35
|
+
id,
|
|
36
|
+
port: channel,
|
|
37
|
+
registeredPorts: new Set(),
|
|
38
|
+
flowControlledPorts: new Map(),
|
|
39
|
+
primaryPort: null,
|
|
40
|
+
ownDocuments: new Set(),
|
|
41
|
+
};
|
|
42
|
+
hosts.set(id, host);
|
|
43
|
+
latestHost = host;
|
|
44
|
+
void pruneClients();
|
|
45
|
+
return host;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The host a client belongs to: a host's own window, or a document or worker one of its servers made. */
|
|
49
|
+
function ownerOf(clientId) {
|
|
50
|
+
if (!clientId) return null;
|
|
51
|
+
return hosts.get(clientId) ?? hosts.get(clientHosts.get(clientId)) ?? null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The host a request is for. One host is the answer while there is one. With
|
|
56
|
+
* more, the requesting client's own host; else the host that owns the
|
|
57
|
+
* document named by the referrer, which is how a frame's navigation carries
|
|
58
|
+
* its parent; else the only host holding the port; else the latest host.
|
|
59
|
+
*/
|
|
60
|
+
async function hostForEvent(event, port) {
|
|
61
|
+
if (hosts.size === 0) return null;
|
|
62
|
+
if (hosts.size === 1) return hosts.values().next().value;
|
|
63
|
+
const direct = ownerOf(event.clientId);
|
|
64
|
+
if (direct) return direct;
|
|
65
|
+
const referrer = event.request.referrer;
|
|
66
|
+
if (referrer) {
|
|
67
|
+
const all = await self.clients.matchAll({ type: 'all', includeUncontrolled: true });
|
|
68
|
+
const owners = new Set(all.filter((client) => client.url === referrer).map((client) => ownerOf(client.id)).filter(Boolean));
|
|
69
|
+
if (owners.size === 1) return owners.values().next().value;
|
|
70
|
+
}
|
|
71
|
+
if (port !== undefined) {
|
|
72
|
+
const holding = [...hosts.values()].filter((host) => host.registeredPorts.has(port));
|
|
73
|
+
if (holding.length === 1) return holding[0];
|
|
74
|
+
}
|
|
75
|
+
return latestHost;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The host a request is for, found through the event it arrived with; remembers the clients it makes. */
|
|
79
|
+
async function hostForRequest(request, port) {
|
|
80
|
+
const event = requestEvents.get(request);
|
|
81
|
+
const host = event ? await hostForEvent(event, port) : latestHost;
|
|
82
|
+
if (host && event) {
|
|
83
|
+
if (event.resultingClientId) clientHosts.set(event.resultingClientId, host.id);
|
|
84
|
+
if (event.clientId && !hosts.has(event.clientId)) clientHosts.set(event.clientId, host.id);
|
|
85
|
+
}
|
|
86
|
+
return host;
|
|
87
|
+
}
|
|
11
88
|
|
|
12
89
|
// Pending requests waiting for response
|
|
13
90
|
const pendingRequests = new Map();
|
|
14
91
|
let requestId = 0;
|
|
15
92
|
|
|
16
|
-
// Registered virtual server ports
|
|
17
|
-
const registeredPorts = new Set();
|
|
18
93
|
// HTTP response flow control is a transport property, never a path/package
|
|
19
|
-
// heuristic. Legacy ports keep their existing protocol until their host opts
|
|
20
|
-
|
|
94
|
+
// heuristic. Legacy ports keep their existing protocol until their host opts
|
|
95
|
+
// in; each host's `flowControlledPorts` holds what its ports negotiated.
|
|
21
96
|
const controlledUploads = new Set();
|
|
22
97
|
const MAX_STREAM_CHUNK_BYTES = 65536;
|
|
23
98
|
|
|
24
|
-
function updateFlowControl(type, data) {
|
|
99
|
+
function updateFlowControl(host, type, data) {
|
|
25
100
|
if (type === 'server-registered' || type === 'server-unregistered') {
|
|
26
101
|
for (const upload of controlledUploads) {
|
|
27
|
-
if (upload.port === data?.port) upload.controller.abort(new Error('Virtual server registration changed'));
|
|
102
|
+
if (upload.host === host && upload.port === data?.port) upload.controller.abort(new Error('Virtual server registration changed'));
|
|
28
103
|
}
|
|
29
104
|
// A port number is not a listener identity. Rebinding must terminate the
|
|
30
105
|
// old connection, even when the replacement advertises identical limits.
|
|
31
106
|
// Otherwise an already-headed response can wait forever on its old owner.
|
|
32
107
|
for (const pending of pendingRequests.values()) {
|
|
33
|
-
if (pending.flowControl === 1 && pending.port === data?.port)
|
|
108
|
+
if (pending.host === host && pending.flowControl === 1 && pending.port === data?.port)
|
|
34
109
|
pending.fail(new Error(type === 'server-unregistered'
|
|
35
110
|
? 'Virtual server closed' : 'Virtual server registration changed'));
|
|
36
111
|
}
|
|
37
112
|
}
|
|
38
113
|
if (type === 'server-unregistered') {
|
|
39
|
-
flowControlledPorts.delete(data?.port);
|
|
114
|
+
host.flowControlledPorts.delete(data?.port);
|
|
40
115
|
}
|
|
41
116
|
if (type !== 'server-registered' || !data) return;
|
|
42
117
|
if (data.flowControl === 1 && Number.isSafeInteger(data.maxRequestBytes)
|
|
43
118
|
&& data.maxRequestBytes > 0 && data.maxChunkBytes === MAX_STREAM_CHUNK_BYTES) {
|
|
44
|
-
flowControlledPorts.set(data.port, { maxRequestBytes: data.maxRequestBytes, channel:
|
|
45
|
-
} else flowControlledPorts.delete(data.port);
|
|
119
|
+
host.flowControlledPorts.set(data.port, { maxRequestBytes: data.maxRequestBytes, channel: host.port });
|
|
120
|
+
} else host.flowControlledPorts.delete(data.port);
|
|
46
121
|
}
|
|
47
122
|
|
|
48
123
|
/**
|
|
@@ -66,42 +141,35 @@ self.addEventListener('message', (event) => {
|
|
|
66
141
|
DEBUG && console.log('[SW] Received message:', type, 'hasPort in event.ports:', event.ports?.length > 0);
|
|
67
142
|
|
|
68
143
|
// When a MessagePort is transferred, it's in event.ports[0], not event.data.port
|
|
144
|
+
const source = event.source && event.source.id ? hosts.get(event.source.id) : null;
|
|
145
|
+
|
|
69
146
|
if (type === 'init' && event.ports && event.ports[0]) {
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
mainPort.onmessage = null;
|
|
77
|
-
mainPort.close();
|
|
78
|
-
}
|
|
79
|
-
registeredPorts.clear();
|
|
80
|
-
flowControlledPorts.clear();
|
|
81
|
-
primaryPort = null;
|
|
82
|
-
// Existing preview documents/workers retain their virtual origin identity;
|
|
83
|
-
// a channel reconnect must not send their root-relative requests to network.
|
|
84
|
-
mainPort = event.ports[0];
|
|
147
|
+
// A page's channel replaces only that page's: another page's requests
|
|
148
|
+
// and servers are its own. Existing preview documents/workers retain
|
|
149
|
+
// their virtual origin identity; a channel reconnect must not send their
|
|
150
|
+
// root-relative requests to network.
|
|
151
|
+
const mainPort = event.ports[0];
|
|
152
|
+
const host = openHost(event.source && event.source.id ? event.source.id : `host-${++requestId}`, mainPort);
|
|
85
153
|
const initializedPort = mainPort;
|
|
86
154
|
mainPort.onmessage = (message) => {
|
|
87
|
-
if (
|
|
155
|
+
if (host.port === initializedPort) handleMainMessage(host, message);
|
|
88
156
|
};
|
|
89
|
-
if (data && Array.isArray(data.ownDocuments)) ownDocuments = new Set(data.ownDocuments.map((path) => ownPath(String(path))));
|
|
157
|
+
if (data && Array.isArray(data.ownDocuments)) host.ownDocuments = new Set(data.ownDocuments.map((path) => ownPath(String(path))));
|
|
90
158
|
DEBUG && console.log('[SW] Initialized communication channel with transferred port');
|
|
91
159
|
// Re-claim clients so that pages opened after SW activation get controlled.
|
|
92
160
|
// Without this, controllerchange never fires for late-arriving pages.
|
|
93
161
|
self.clients.claim();
|
|
94
162
|
}
|
|
95
163
|
|
|
96
|
-
if (type === 'server-registered' && data) {
|
|
97
|
-
registeredPorts.add(data.port);
|
|
98
|
-
if (data.primary) primaryPort = data.port;
|
|
164
|
+
if (type === 'server-registered' && data && source) {
|
|
165
|
+
source.registeredPorts.add(data.port);
|
|
166
|
+
if (data.primary) source.primaryPort = data.port;
|
|
99
167
|
DEBUG && console.log(`[SW] Server registered on port ${data.port}`);
|
|
100
168
|
}
|
|
101
169
|
|
|
102
|
-
if (type === 'server-unregistered' && data) {
|
|
103
|
-
registeredPorts.delete(data.port);
|
|
104
|
-
if (primaryPort === data.port) primaryPort = null;
|
|
170
|
+
if (type === 'server-unregistered' && data && source) {
|
|
171
|
+
source.registeredPorts.delete(data.port);
|
|
172
|
+
if (source.primaryPort === data.port) source.primaryPort = null;
|
|
105
173
|
DEBUG && console.log(`[SW] Server unregistered from port ${data.port}`);
|
|
106
174
|
}
|
|
107
175
|
});
|
|
@@ -109,11 +177,13 @@ self.addEventListener('message', (event) => {
|
|
|
109
177
|
/**
|
|
110
178
|
* Handle response messages from main thread
|
|
111
179
|
*/
|
|
112
|
-
function handleMainMessage(event) {
|
|
180
|
+
function handleMainMessage(host, event) {
|
|
113
181
|
const { type, id, data, error } = event.data;
|
|
114
|
-
updateFlowControl(type, data);
|
|
182
|
+
updateFlowControl(host, type, data);
|
|
115
183
|
|
|
116
184
|
const controlled = pendingRequests.get(id);
|
|
185
|
+
// A request is answered only by the host it was sent to.
|
|
186
|
+
if (controlled && controlled.host !== host) return;
|
|
117
187
|
if (controlled?.flowControl === 1) {
|
|
118
188
|
controlled.message(type, data, error);
|
|
119
189
|
return;
|
|
@@ -122,18 +192,18 @@ function handleMainMessage(event) {
|
|
|
122
192
|
// The page's bridge speaks over this port: which servers it holds, which
|
|
123
193
|
// is the preview's, and which documents are the page's own frames.
|
|
124
194
|
if (type === 'server-registered' && data) {
|
|
125
|
-
registeredPorts.add(data.port);
|
|
126
|
-
if (data.primary) primaryPort = data.port;
|
|
127
|
-
else if (primaryPort === data.port) primaryPort = null;
|
|
195
|
+
host.registeredPorts.add(data.port);
|
|
196
|
+
if (data.primary) host.primaryPort = data.port;
|
|
197
|
+
else if (host.primaryPort === data.port) host.primaryPort = null;
|
|
128
198
|
return;
|
|
129
199
|
}
|
|
130
200
|
if (type === 'server-unregistered' && data) {
|
|
131
|
-
registeredPorts.delete(data.port);
|
|
132
|
-
if (primaryPort === data.port) primaryPort = null;
|
|
201
|
+
host.registeredPorts.delete(data.port);
|
|
202
|
+
if (host.primaryPort === data.port) host.primaryPort = null;
|
|
133
203
|
return;
|
|
134
204
|
}
|
|
135
205
|
if (type === 'own-documents' && data && Array.isArray(data.paths)) {
|
|
136
|
-
ownDocuments = new Set(data.paths.map((path) => ownPath(String(path))));
|
|
206
|
+
host.ownDocuments = new Set(data.paths.map((path) => ownPath(String(path))));
|
|
137
207
|
return;
|
|
138
208
|
}
|
|
139
209
|
|
|
@@ -213,30 +283,17 @@ function handleMainMessage(event) {
|
|
|
213
283
|
/**
|
|
214
284
|
* Send request to main thread and wait for response
|
|
215
285
|
*/
|
|
216
|
-
async function sendRequest(port, method, url, headers, body) {
|
|
217
|
-
DEBUG && console.log('[SW] sendRequest called,
|
|
218
|
-
|
|
219
|
-
if (!
|
|
220
|
-
|
|
221
|
-
const allClients = await self.clients.matchAll({ type: 'window' });
|
|
222
|
-
for (const client of allClients) {
|
|
223
|
-
client.postMessage({ type: 'sw-needs-init' });
|
|
224
|
-
}
|
|
225
|
-
// Wait up to 5s for a client to re-initialize the port
|
|
226
|
-
// (main thread may be busy with heavy operations like CLI execution)
|
|
227
|
-
await new Promise(resolve => {
|
|
228
|
-
const check = setInterval(() => { if (mainPort) { clearInterval(check); resolve(); } }, 50);
|
|
229
|
-
setTimeout(() => { clearInterval(check); resolve(); }, 5000);
|
|
230
|
-
});
|
|
231
|
-
if (!mainPort) {
|
|
232
|
-
throw new Error('Service Worker not initialized - no connection to main thread');
|
|
233
|
-
}
|
|
286
|
+
async function sendRequest(host, port, method, url, headers, body) {
|
|
287
|
+
DEBUG && console.log('[SW] sendRequest called, host:', !!host, 'url:', url);
|
|
288
|
+
|
|
289
|
+
if (!host) {
|
|
290
|
+
throw new Error('Service Worker not initialized - no connection to main thread');
|
|
234
291
|
}
|
|
235
292
|
|
|
236
293
|
const id = ++requestId;
|
|
237
294
|
|
|
238
295
|
return new Promise((resolve, reject) => {
|
|
239
|
-
pendingRequests.set(id, { resolve, reject });
|
|
296
|
+
pendingRequests.set(id, { host, resolve, reject });
|
|
240
297
|
|
|
241
298
|
// Set timeout for request
|
|
242
299
|
setTimeout(() => {
|
|
@@ -246,7 +303,7 @@ async function sendRequest(port, method, url, headers, body) {
|
|
|
246
303
|
}
|
|
247
304
|
}, 30000);
|
|
248
305
|
|
|
249
|
-
|
|
306
|
+
host.port.postMessage({
|
|
250
307
|
type: 'request',
|
|
251
308
|
id,
|
|
252
309
|
data: { port, method, url, headers, body },
|
|
@@ -258,22 +315,11 @@ async function sendRequest(port, method, url, headers, body) {
|
|
|
258
315
|
* Send streaming request to main thread
|
|
259
316
|
* Returns a ReadableStream that receives chunks from main thread
|
|
260
317
|
*/
|
|
261
|
-
async function sendStreamingRequest(port, method, url, headers, body) {
|
|
318
|
+
async function sendStreamingRequest(host, port, method, url, headers, body) {
|
|
262
319
|
DEBUG && console.log('[SW] sendStreamingRequest called, url:', url);
|
|
263
320
|
|
|
264
|
-
if (!
|
|
265
|
-
|
|
266
|
-
const allClients = await self.clients.matchAll({ type: 'window' });
|
|
267
|
-
for (const client of allClients) {
|
|
268
|
-
client.postMessage({ type: 'sw-needs-init' });
|
|
269
|
-
}
|
|
270
|
-
await new Promise(resolve => {
|
|
271
|
-
const check = setInterval(() => { if (mainPort) { clearInterval(check); resolve(); } }, 50);
|
|
272
|
-
setTimeout(() => { clearInterval(check); resolve(); }, 5000);
|
|
273
|
-
});
|
|
274
|
-
if (!mainPort) {
|
|
275
|
-
throw new Error('Service Worker not initialized');
|
|
276
|
-
}
|
|
321
|
+
if (!host) {
|
|
322
|
+
throw new Error('Service Worker not initialized');
|
|
277
323
|
}
|
|
278
324
|
|
|
279
325
|
const id = ++requestId;
|
|
@@ -288,6 +334,7 @@ async function sendStreamingRequest(port, method, url, headers, body) {
|
|
|
288
334
|
|
|
289
335
|
// Store in pending requests so handleMainMessage can find it
|
|
290
336
|
pendingRequests.set(id, {
|
|
337
|
+
host,
|
|
291
338
|
resolve: () => {},
|
|
292
339
|
reject: (err) => controller.error(err),
|
|
293
340
|
streamController: controller,
|
|
@@ -295,7 +342,7 @@ async function sendStreamingRequest(port, method, url, headers, body) {
|
|
|
295
342
|
});
|
|
296
343
|
|
|
297
344
|
// Send request to main thread with streaming flag
|
|
298
|
-
|
|
345
|
+
host.port.postMessage({
|
|
299
346
|
type: 'request',
|
|
300
347
|
id,
|
|
301
348
|
data: { port, method, url, headers, body, streaming: true },
|
|
@@ -312,10 +359,10 @@ async function sendStreamingRequest(port, method, url, headers, body) {
|
|
|
312
359
|
// A Node HTTP producer must stop when its consumer stops reading and must
|
|
313
360
|
// observe disconnect. One pull credit bounds the service-worker response queue;
|
|
314
361
|
// this protocol requires a paired host bridge and is negotiated per port.
|
|
315
|
-
async function sendControlledRequest(port, method, url, headers, body, signal) {
|
|
316
|
-
if (!
|
|
362
|
+
async function sendControlledRequest(host, port, method, url, headers, body, signal) {
|
|
363
|
+
if (!host) throw new Error('Service Worker not initialized');
|
|
317
364
|
if (signal.aborted) throw signal.reason;
|
|
318
|
-
const channel =
|
|
365
|
+
const channel = host.port;
|
|
319
366
|
const id = ++requestId;
|
|
320
367
|
let controller, resolveHeaders, rejectHeaders, resolvePull;
|
|
321
368
|
let headed = false, credited = false, ended = false;
|
|
@@ -364,6 +411,7 @@ async function sendControlledRequest(port, method, url, headers, body, signal) {
|
|
|
364
411
|
},
|
|
365
412
|
}, { highWaterMark: 0 });
|
|
366
413
|
pendingRequests.set(id, {
|
|
414
|
+
host,
|
|
367
415
|
flowControl: 1,
|
|
368
416
|
fail,
|
|
369
417
|
port,
|
|
@@ -457,10 +505,9 @@ async function readRequestBody(request, limit, registrationSignal) {
|
|
|
457
505
|
// they would be at their own origin: an app reads its routes from
|
|
458
506
|
// `location.pathname`, and a document under /__virtual__/<port>/ is not at
|
|
459
507
|
// the path its router expects.
|
|
460
|
-
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
let ownDocuments = new Set();
|
|
508
|
+
// Each host's `primaryPort`. The page's own documents that are frames of it,
|
|
509
|
+
// told by `init`, are each host's `ownDocuments`, so a nested document at one
|
|
510
|
+
// of these paths is the page's and not the preview's.
|
|
464
511
|
// The documents and workers served from a virtual server at the root, by
|
|
465
512
|
// client id, so their requests reach that server without a prefix to read.
|
|
466
513
|
const rootedClients = new Map();
|
|
@@ -480,21 +527,23 @@ async function blobWorkerContext(client, requestUrl) {
|
|
|
480
527
|
url: requestUrl, referrer: owner.url, mode: 'cors', destination: '',
|
|
481
528
|
} });
|
|
482
529
|
if (!context) return null;
|
|
530
|
+
const host = ownerOf(owner.id);
|
|
483
531
|
return new Promise((resolve) => {
|
|
484
532
|
const channel = new MessageChannel();
|
|
485
533
|
const finish = (value) => { clearTimeout(timer); channel.port1.close(); resolve(value); };
|
|
486
534
|
const timer = setTimeout(() => finish(null), 1000);
|
|
487
|
-
channel.port1.onmessage = (event) => finish(event.data === true ? context : null);
|
|
535
|
+
channel.port1.onmessage = (event) => finish(event.data === true ? { context, host } : null);
|
|
488
536
|
try { owner.postMessage({ type: 'virtual-blob-owner', url: client.url }, [channel.port2]); }
|
|
489
537
|
catch { finish(null); }
|
|
490
538
|
});
|
|
491
539
|
}));
|
|
492
540
|
const owners = candidates.filter(Boolean);
|
|
493
541
|
if (owners.length === 0) return null;
|
|
494
|
-
if (owners.some(owner => owner.port !== owners[0].port)) throw new Error('Ambiguous virtual blob owner');
|
|
495
|
-
rootedClients.set(client.id, owners[0].port);
|
|
496
|
-
if (
|
|
497
|
-
|
|
542
|
+
if (owners.some(owner => owner.context.port !== owners[0].context.port || owner.host !== owners[0].host)) throw new Error('Ambiguous virtual blob owner');
|
|
543
|
+
rootedClients.set(client.id, owners[0].context.port);
|
|
544
|
+
if (owners[0].host) clientHosts.set(client.id, owners[0].host.id);
|
|
545
|
+
if (rootedClients.size > 256) pruneClients();
|
|
546
|
+
return owners[0].context;
|
|
498
547
|
})();
|
|
499
548
|
pendingBlobContexts.set(client.id, pending);
|
|
500
549
|
try { return await pending; } finally { pendingBlobContexts.delete(client.id); }
|
|
@@ -522,7 +571,7 @@ async function virtualContext(event) {
|
|
|
522
571
|
// or a frame's navigation, asks the page for its registrations first, as a
|
|
523
572
|
// virtual request asks for its port, so a restart does not send the
|
|
524
573
|
// preview's own requests to the network.
|
|
525
|
-
if (
|
|
574
|
+
if (hosts.size === 0 && ((client && client.frameType === 'nested') || (event.request.mode === 'navigate' && (event.request.destination === 'iframe' || event.request.destination === 'frame')))) {
|
|
526
575
|
await askForInit();
|
|
527
576
|
}
|
|
528
577
|
const contextUrl = event.request.referrer || (client ? client.url : '');
|
|
@@ -536,13 +585,14 @@ async function virtualContext(event) {
|
|
|
536
585
|
if (client && (client.type === 'worker' || client.type === 'sharedworker') && client.url.startsWith('blob:')) {
|
|
537
586
|
return blobWorkerContext(client, event.request.url);
|
|
538
587
|
}
|
|
539
|
-
|
|
588
|
+
const host = await hostForEvent(event);
|
|
589
|
+
if (!host || host.primaryPort === null) return null;
|
|
540
590
|
if (event.request.mode === 'navigate') {
|
|
541
591
|
if (event.request.destination !== 'iframe' && event.request.destination !== 'frame') return null;
|
|
542
|
-
if (ownDocuments.has(ownPath(url.pathname))) return null;
|
|
543
|
-
return { prefix: null, port: primaryPort };
|
|
592
|
+
if (host.ownDocuments.has(ownPath(url.pathname))) return null;
|
|
593
|
+
return { prefix: null, port: host.primaryPort };
|
|
544
594
|
}
|
|
545
|
-
if (client && client.frameType === 'nested' && !ownDocuments.has(ownPath(new URL(client.url).pathname))) return { prefix: null, port: primaryPort };
|
|
595
|
+
if (client && client.frameType === 'nested' && !host.ownDocuments.has(ownPath(new URL(client.url).pathname))) return { prefix: null, port: host.primaryPort };
|
|
546
596
|
return null;
|
|
547
597
|
}
|
|
548
598
|
|
|
@@ -556,7 +606,7 @@ async function askForInit() {
|
|
|
556
606
|
const allClients = await self.clients.matchAll({ type: 'window' });
|
|
557
607
|
for (const client of allClients) client.postMessage({ type: 'sw-needs-init' });
|
|
558
608
|
await new Promise((resolve) => {
|
|
559
|
-
const check = setInterval(() => { if (
|
|
609
|
+
const check = setInterval(() => { if (hosts.size > 0) { clearInterval(check); resolve(); } }, 50);
|
|
560
610
|
setTimeout(() => { clearInterval(check); resolve(); }, 5000);
|
|
561
611
|
});
|
|
562
612
|
}
|
|
@@ -565,15 +615,23 @@ async function askForInit() {
|
|
|
565
615
|
function handleRootedRequest(event, port, path) {
|
|
566
616
|
if (event.resultingClientId) {
|
|
567
617
|
rootedClients.set(event.resultingClientId, port);
|
|
568
|
-
if (rootedClients.size > 256)
|
|
618
|
+
if (rootedClients.size > 256) pruneClients();
|
|
569
619
|
}
|
|
570
620
|
return handleVirtualRequest(event.request, port, path, true);
|
|
571
621
|
}
|
|
572
622
|
|
|
573
|
-
/** Forgets the documents and
|
|
574
|
-
async function
|
|
575
|
-
const alive = new Set((await self.clients.matchAll({ type: 'all' })).map((client) => client.id));
|
|
623
|
+
/** Forgets the documents, workers and hosts that are gone. */
|
|
624
|
+
async function pruneClients() {
|
|
625
|
+
const alive = new Set((await self.clients.matchAll({ type: 'all', includeUncontrolled: true })).map((client) => client.id));
|
|
576
626
|
for (const id of [...rootedClients.keys()]) if (!alive.has(id)) rootedClients.delete(id);
|
|
627
|
+
for (const [id, host] of [...clientHosts]) if (!alive.has(id) || !hosts.has(host)) clientHosts.delete(id);
|
|
628
|
+
for (const [id, host] of [...hosts]) {
|
|
629
|
+
if (alive.has(id) || id.startsWith('host-')) continue;
|
|
630
|
+
hosts.delete(id);
|
|
631
|
+
host.port.onmessage = null;
|
|
632
|
+
host.port.close();
|
|
633
|
+
if (latestHost === host) latestHost = [...hosts.values()].pop() ?? null;
|
|
634
|
+
}
|
|
577
635
|
}
|
|
578
636
|
|
|
579
637
|
/**
|
|
@@ -581,8 +639,9 @@ async function pruneRootedClients() {
|
|
|
581
639
|
*/
|
|
582
640
|
self.addEventListener('fetch', (event) => {
|
|
583
641
|
const url = new URL(event.request.url);
|
|
642
|
+
requestEvents.set(event.request, event);
|
|
584
643
|
|
|
585
|
-
DEBUG && console.log('[SW] Fetch:', url.pathname, '
|
|
644
|
+
DEBUG && console.log('[SW] Fetch:', url.pathname, 'hosts:', hosts.size);
|
|
586
645
|
|
|
587
646
|
// Check if this is a virtual server request
|
|
588
647
|
const match = url.pathname.match(/^\/__virtual__\/(\d+)(\/.*)?$/);
|
|
@@ -595,7 +654,7 @@ self.addEventListener('fetch', (event) => {
|
|
|
595
654
|
// the referrer, which a document sent with no referrer never has, is
|
|
596
655
|
// the fallback. A document at the root is the preview's server's, and
|
|
597
656
|
// its own requests are that server's with it.
|
|
598
|
-
if (event.request.referrer || event.clientId || (event.request.mode === 'navigate' && primaryPort !== null)) {
|
|
657
|
+
if (event.request.referrer || event.clientId || (event.request.mode === 'navigate' && [...hosts.values()].some((host) => host.primaryPort !== null))) {
|
|
599
658
|
event.respondWith((async () => {
|
|
600
659
|
const context = await virtualContext(event);
|
|
601
660
|
if (!context) return fetch(event.request);
|
|
@@ -629,7 +688,9 @@ self.addEventListener('fetch', (event) => {
|
|
|
629
688
|
*/
|
|
630
689
|
async function handleVirtualRequest(request, port, path, rooted = false) {
|
|
631
690
|
try {
|
|
632
|
-
|
|
691
|
+
if (hosts.size === 0) await askForInit();
|
|
692
|
+
const host = await hostForRequest(request, port);
|
|
693
|
+
const registration = host ? host.flowControlledPorts.get(port) : undefined;
|
|
633
694
|
// Build headers object
|
|
634
695
|
const headers = {};
|
|
635
696
|
request.headers.forEach((value, key) => {
|
|
@@ -646,7 +707,7 @@ async function handleVirtualRequest(request, port, path, rooted = false) {
|
|
|
646
707
|
let body = null;
|
|
647
708
|
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
|
648
709
|
if (registration) {
|
|
649
|
-
const upload = { port, controller: new AbortController() };
|
|
710
|
+
const upload = { host, port, controller: new AbortController() };
|
|
650
711
|
controlledUploads.add(upload);
|
|
651
712
|
try {
|
|
652
713
|
body = await readRequestBody(request, registration.maxRequestBytes, upload.controller.signal);
|
|
@@ -661,9 +722,9 @@ async function handleVirtualRequest(request, port, path, rooted = false) {
|
|
|
661
722
|
}
|
|
662
723
|
|
|
663
724
|
if (registration) {
|
|
664
|
-
if (flowControlledPorts.get(port) !== registration ||
|
|
725
|
+
if (host.flowControlledPorts.get(port) !== registration || host.port !== registration.channel || hosts.get(host.id) !== host)
|
|
665
726
|
throw new Error('Virtual server registration changed during upload');
|
|
666
|
-
return await sendControlledRequest(port, request.method, path, headers, body, request.signal);
|
|
727
|
+
return await sendControlledRequest(host, port, request.method, path, headers, body, request.signal);
|
|
667
728
|
}
|
|
668
729
|
|
|
669
730
|
// Legacy peers retain their old selection until they negotiate flow control.
|
|
@@ -671,12 +732,12 @@ async function handleVirtualRequest(request, port, path, rooted = false) {
|
|
|
671
732
|
|
|
672
733
|
if (isStreamingCandidate) {
|
|
673
734
|
DEBUG && console.log('[SW] Using streaming mode for:', path);
|
|
674
|
-
return handleStreamingRequest(port, request.method, path, headers, body);
|
|
735
|
+
return handleStreamingRequest(host, port, request.method, path, headers, body);
|
|
675
736
|
}
|
|
676
737
|
DEBUG && console.log('[SW] Using non-streaming mode for:', request.method, path);
|
|
677
738
|
|
|
678
739
|
// Send to main thread
|
|
679
|
-
const response = await sendRequest(port, request.method, path, headers, body);
|
|
740
|
+
const response = await sendRequest(host, port, request.method, path, headers, body);
|
|
680
741
|
|
|
681
742
|
DEBUG && console.log('[SW] Got response from main thread:', {
|
|
682
743
|
statusCode: response.statusCode,
|
|
@@ -741,8 +802,8 @@ async function handleVirtualRequest(request, port, path, rooted = false) {
|
|
|
741
802
|
/**
|
|
742
803
|
* Handle a streaming request
|
|
743
804
|
*/
|
|
744
|
-
async function handleStreamingRequest(port, method, path, headers, body) {
|
|
745
|
-
const { stream, headersPromise, id } = await sendStreamingRequest(port, method, path, headers, body);
|
|
805
|
+
async function handleStreamingRequest(host, port, method, path, headers, body) {
|
|
806
|
+
const { stream, headersPromise, id } = await sendStreamingRequest(host, port, method, path, headers, body);
|
|
746
807
|
|
|
747
808
|
// Wait for headers to arrive
|
|
748
809
|
const responseData = await headersPromise;
|
|
@@ -23705,6 +23705,83 @@ function createCryptoModule(require2) {
|
|
|
23705
23705
|
const keyInfo = extractKeyInfo(key);
|
|
23706
23706
|
return new KeyObject2("private", keyInfo.keyData, keyInfo.algorithm);
|
|
23707
23707
|
}
|
|
23708
|
+
const NAMED_CURVES = {
|
|
23709
|
+
"prime256v1": "P-256",
|
|
23710
|
+
"p-256": "P-256",
|
|
23711
|
+
"secp256r1": "P-256",
|
|
23712
|
+
"secp384r1": "P-384",
|
|
23713
|
+
"p-384": "P-384",
|
|
23714
|
+
"secp521r1": "P-521",
|
|
23715
|
+
"p-521": "P-521"
|
|
23716
|
+
};
|
|
23717
|
+
function keyPairAlgorithm(type2, options) {
|
|
23718
|
+
const exponent = (value) => {
|
|
23719
|
+
let rest = value ?? 65537;
|
|
23720
|
+
const bytes2 = [];
|
|
23721
|
+
while (rest > 0) {
|
|
23722
|
+
bytes2.unshift(rest & 255);
|
|
23723
|
+
rest = Math.floor(rest / 256);
|
|
23724
|
+
}
|
|
23725
|
+
return new Uint8Array(bytes2);
|
|
23726
|
+
};
|
|
23727
|
+
switch (type2) {
|
|
23728
|
+
case "rsa":
|
|
23729
|
+
case "rsa-pss": {
|
|
23730
|
+
if (!Number.isInteger(options.modulusLength)) throw Object.assign(new TypeError('The "options.modulusLength" property must be of type number.'), { code: "ERR_INVALID_ARG_TYPE" });
|
|
23731
|
+
const name = type2 === "rsa" ? "RSASSA-PKCS1-v1_5" : "RSA-PSS";
|
|
23732
|
+
return { generate: { name, modulusLength: options.modulusLength, publicExponent: exponent(options.publicExponent), hash: "SHA-256" }, usages: ["sign", "verify"], name: type2 === "rsa" ? "RSA-SHA256" : "RSA-PSS" };
|
|
23733
|
+
}
|
|
23734
|
+
case "ec": {
|
|
23735
|
+
const curve = NAMED_CURVES[String(options.namedCurve ?? "").toLowerCase()];
|
|
23736
|
+
if (!curve) throw Object.assign(new TypeError(`Invalid EC curve name ${String(options.namedCurve)}`), { code: "ERR_CRYPTO_INVALID_CURVE" });
|
|
23737
|
+
return { generate: { name: "ECDSA", namedCurve: curve }, usages: ["sign", "verify"], name: curve === "P-256" ? "ES256" : curve === "P-384" ? "ES384" : "ES512" };
|
|
23738
|
+
}
|
|
23739
|
+
case "ed25519":
|
|
23740
|
+
return { generate: { name: "Ed25519" }, usages: ["sign", "verify"], name: "Ed25519" };
|
|
23741
|
+
case "x25519":
|
|
23742
|
+
return { generate: { name: "X25519" }, usages: ["deriveBits"], name: "X25519" };
|
|
23743
|
+
default:
|
|
23744
|
+
throw Object.assign(new TypeError(`The argument 'type' must be a supported key type. Received '${type2}'`), { code: "ERR_INVALID_ARG_VALUE" });
|
|
23745
|
+
}
|
|
23746
|
+
}
|
|
23747
|
+
function encodedKey(der, encoding, kind) {
|
|
23748
|
+
const expected = kind === "public" ? "spki" : "pkcs8";
|
|
23749
|
+
if (encoding.type !== void 0 && encoding.type !== expected) {
|
|
23750
|
+
unsupported2(`Encoding a ${kind} key as ${encoding.type}`);
|
|
23751
|
+
}
|
|
23752
|
+
const bytes2 = Buffer2.from(der);
|
|
23753
|
+
if (encoding.format === "der") return bytes2;
|
|
23754
|
+
if (encoding.format !== "pem") unsupported2(`Encoding a key in the ${String(encoding.format)} format`);
|
|
23755
|
+
const label = kind === "public" ? "PUBLIC KEY" : "PRIVATE KEY";
|
|
23756
|
+
const body = bytes2.toString("base64").match(/.{1,64}/g).join("\n");
|
|
23757
|
+
return `-----BEGIN ${label}-----
|
|
23758
|
+
${body}
|
|
23759
|
+
-----END ${label}-----
|
|
23760
|
+
`;
|
|
23761
|
+
}
|
|
23762
|
+
async function generateKeyPairAsync(type2, options) {
|
|
23763
|
+
const algorithm = keyPairAlgorithm(type2, options);
|
|
23764
|
+
const pair = await crypto.subtle.generateKey(algorithm.generate, true, algorithm.usages);
|
|
23765
|
+
const publicKey = options.publicKeyEncoding ? encodedKey(await crypto.subtle.exportKey("spki", pair.publicKey), options.publicKeyEncoding, "public") : new KeyObject2("public", pair.publicKey, algorithm.name);
|
|
23766
|
+
const privateKey = options.privateKeyEncoding ? encodedKey(await crypto.subtle.exportKey("pkcs8", pair.privateKey), options.privateKeyEncoding, "private") : new KeyObject2("private", pair.privateKey, algorithm.name);
|
|
23767
|
+
return { publicKey, privateKey };
|
|
23768
|
+
}
|
|
23769
|
+
function generateKeyPair2(type2, options, callback) {
|
|
23770
|
+
if (typeof options === "function") {
|
|
23771
|
+
callback = options;
|
|
23772
|
+
options = {};
|
|
23773
|
+
}
|
|
23774
|
+
if (typeof callback !== "function") throw Object.assign(new TypeError('The "callback" argument must be of type function.'), { code: "ERR_INVALID_ARG_TYPE" });
|
|
23775
|
+
const done = callback;
|
|
23776
|
+
keyPairAlgorithm(type2, options ?? {});
|
|
23777
|
+
generateKeyPairAsync(type2, options ?? {}).then(
|
|
23778
|
+
({ publicKey, privateKey }) => done(null, publicKey, privateKey),
|
|
23779
|
+
(error) => done(error instanceof Error ? error : new Error(String(error)))
|
|
23780
|
+
);
|
|
23781
|
+
}
|
|
23782
|
+
Object.defineProperty(generateKeyPair2, Symbol.for("nodejs.util.promisify.custom"), {
|
|
23783
|
+
value: (type2, options) => generateKeyPairAsync(type2, options ?? {})
|
|
23784
|
+
});
|
|
23708
23785
|
function timingSafeEqual2(a, b) {
|
|
23709
23786
|
if (a.length !== b.length) {
|
|
23710
23787
|
return false;
|
|
@@ -23861,11 +23938,12 @@ function createCryptoModule(require2) {
|
|
|
23861
23938
|
KeyObject: KeyObject2,
|
|
23862
23939
|
createSecretKey: createSecretKey2,
|
|
23863
23940
|
createPublicKey: createPublicKey2,
|
|
23864
|
-
createPrivateKey: createPrivateKey2
|
|
23941
|
+
createPrivateKey: createPrivateKey2,
|
|
23942
|
+
generateKeyPair: generateKeyPair2
|
|
23865
23943
|
};
|
|
23866
23944
|
}
|
|
23867
23945
|
const cryptoModule = createCryptoModule();
|
|
23868
|
-
const { randomBytes, randomFillSync, randomFill, randomUUID, randomInt, getRandomValues, unsupported, createHash, createHmac, hash, pbkdf2Sync, pbkdf2, sign, verify, createSign, createVerify, KeyObject, createSecretKey, createPublicKey, createPrivateKey, timingSafeEqual, getCiphers, getHashes } = cryptoModule;
|
|
23946
|
+
const { randomBytes, randomFillSync, randomFill, randomUUID, randomInt, getRandomValues, unsupported, createHash, createHmac, hash, pbkdf2Sync, pbkdf2, sign, verify, createSign, createVerify, KeyObject, createSecretKey, createPublicKey, createPrivateKey, generateKeyPair, timingSafeEqual, getCiphers, getHashes } = cryptoModule;
|
|
23869
23947
|
var cryptoShim = /* @__PURE__ */ Object.freeze({
|
|
23870
23948
|
__proto__: null,
|
|
23871
23949
|
KeyObject,
|
|
@@ -23879,6 +23957,7 @@ var cryptoShim = /* @__PURE__ */ Object.freeze({
|
|
|
23879
23957
|
createSign,
|
|
23880
23958
|
createVerify,
|
|
23881
23959
|
default: cryptoModule,
|
|
23960
|
+
generateKeyPair,
|
|
23882
23961
|
getCiphers,
|
|
23883
23962
|
getHashes,
|
|
23884
23963
|
getRandomValues,
|
package/dist/index.mjs
CHANGED
|
@@ -23874,6 +23874,83 @@ function createCryptoModule(require2) {
|
|
|
23874
23874
|
const keyInfo = extractKeyInfo(key);
|
|
23875
23875
|
return new KeyObject2("private", keyInfo.keyData, keyInfo.algorithm);
|
|
23876
23876
|
}
|
|
23877
|
+
const NAMED_CURVES = {
|
|
23878
|
+
"prime256v1": "P-256",
|
|
23879
|
+
"p-256": "P-256",
|
|
23880
|
+
"secp256r1": "P-256",
|
|
23881
|
+
"secp384r1": "P-384",
|
|
23882
|
+
"p-384": "P-384",
|
|
23883
|
+
"secp521r1": "P-521",
|
|
23884
|
+
"p-521": "P-521"
|
|
23885
|
+
};
|
|
23886
|
+
function keyPairAlgorithm(type2, options) {
|
|
23887
|
+
const exponent = (value) => {
|
|
23888
|
+
let rest = value ?? 65537;
|
|
23889
|
+
const bytes2 = [];
|
|
23890
|
+
while (rest > 0) {
|
|
23891
|
+
bytes2.unshift(rest & 255);
|
|
23892
|
+
rest = Math.floor(rest / 256);
|
|
23893
|
+
}
|
|
23894
|
+
return new Uint8Array(bytes2);
|
|
23895
|
+
};
|
|
23896
|
+
switch (type2) {
|
|
23897
|
+
case "rsa":
|
|
23898
|
+
case "rsa-pss": {
|
|
23899
|
+
if (!Number.isInteger(options.modulusLength)) throw Object.assign(new TypeError('The "options.modulusLength" property must be of type number.'), { code: "ERR_INVALID_ARG_TYPE" });
|
|
23900
|
+
const name = type2 === "rsa" ? "RSASSA-PKCS1-v1_5" : "RSA-PSS";
|
|
23901
|
+
return { generate: { name, modulusLength: options.modulusLength, publicExponent: exponent(options.publicExponent), hash: "SHA-256" }, usages: ["sign", "verify"], name: type2 === "rsa" ? "RSA-SHA256" : "RSA-PSS" };
|
|
23902
|
+
}
|
|
23903
|
+
case "ec": {
|
|
23904
|
+
const curve = NAMED_CURVES[String(options.namedCurve ?? "").toLowerCase()];
|
|
23905
|
+
if (!curve) throw Object.assign(new TypeError(`Invalid EC curve name ${String(options.namedCurve)}`), { code: "ERR_CRYPTO_INVALID_CURVE" });
|
|
23906
|
+
return { generate: { name: "ECDSA", namedCurve: curve }, usages: ["sign", "verify"], name: curve === "P-256" ? "ES256" : curve === "P-384" ? "ES384" : "ES512" };
|
|
23907
|
+
}
|
|
23908
|
+
case "ed25519":
|
|
23909
|
+
return { generate: { name: "Ed25519" }, usages: ["sign", "verify"], name: "Ed25519" };
|
|
23910
|
+
case "x25519":
|
|
23911
|
+
return { generate: { name: "X25519" }, usages: ["deriveBits"], name: "X25519" };
|
|
23912
|
+
default:
|
|
23913
|
+
throw Object.assign(new TypeError(`The argument 'type' must be a supported key type. Received '${type2}'`), { code: "ERR_INVALID_ARG_VALUE" });
|
|
23914
|
+
}
|
|
23915
|
+
}
|
|
23916
|
+
function encodedKey(der, encoding, kind) {
|
|
23917
|
+
const expected = kind === "public" ? "spki" : "pkcs8";
|
|
23918
|
+
if (encoding.type !== void 0 && encoding.type !== expected) {
|
|
23919
|
+
unsupported2(`Encoding a ${kind} key as ${encoding.type}`);
|
|
23920
|
+
}
|
|
23921
|
+
const bytes2 = Buffer2.from(der);
|
|
23922
|
+
if (encoding.format === "der") return bytes2;
|
|
23923
|
+
if (encoding.format !== "pem") unsupported2(`Encoding a key in the ${String(encoding.format)} format`);
|
|
23924
|
+
const label = kind === "public" ? "PUBLIC KEY" : "PRIVATE KEY";
|
|
23925
|
+
const body = bytes2.toString("base64").match(/.{1,64}/g).join("\n");
|
|
23926
|
+
return `-----BEGIN ${label}-----
|
|
23927
|
+
${body}
|
|
23928
|
+
-----END ${label}-----
|
|
23929
|
+
`;
|
|
23930
|
+
}
|
|
23931
|
+
async function generateKeyPairAsync(type2, options) {
|
|
23932
|
+
const algorithm = keyPairAlgorithm(type2, options);
|
|
23933
|
+
const pair = await crypto.subtle.generateKey(algorithm.generate, true, algorithm.usages);
|
|
23934
|
+
const publicKey = options.publicKeyEncoding ? encodedKey(await crypto.subtle.exportKey("spki", pair.publicKey), options.publicKeyEncoding, "public") : new KeyObject2("public", pair.publicKey, algorithm.name);
|
|
23935
|
+
const privateKey = options.privateKeyEncoding ? encodedKey(await crypto.subtle.exportKey("pkcs8", pair.privateKey), options.privateKeyEncoding, "private") : new KeyObject2("private", pair.privateKey, algorithm.name);
|
|
23936
|
+
return { publicKey, privateKey };
|
|
23937
|
+
}
|
|
23938
|
+
function generateKeyPair2(type2, options, callback) {
|
|
23939
|
+
if (typeof options === "function") {
|
|
23940
|
+
callback = options;
|
|
23941
|
+
options = {};
|
|
23942
|
+
}
|
|
23943
|
+
if (typeof callback !== "function") throw Object.assign(new TypeError('The "callback" argument must be of type function.'), { code: "ERR_INVALID_ARG_TYPE" });
|
|
23944
|
+
const done = callback;
|
|
23945
|
+
keyPairAlgorithm(type2, options ?? {});
|
|
23946
|
+
generateKeyPairAsync(type2, options ?? {}).then(
|
|
23947
|
+
({ publicKey, privateKey }) => done(null, publicKey, privateKey),
|
|
23948
|
+
(error) => done(error instanceof Error ? error : new Error(String(error)))
|
|
23949
|
+
);
|
|
23950
|
+
}
|
|
23951
|
+
Object.defineProperty(generateKeyPair2, Symbol.for("nodejs.util.promisify.custom"), {
|
|
23952
|
+
value: (type2, options) => generateKeyPairAsync(type2, options ?? {})
|
|
23953
|
+
});
|
|
23877
23954
|
function timingSafeEqual2(a, b) {
|
|
23878
23955
|
if (a.length !== b.length) {
|
|
23879
23956
|
return false;
|
|
@@ -24030,11 +24107,12 @@ function createCryptoModule(require2) {
|
|
|
24030
24107
|
KeyObject: KeyObject2,
|
|
24031
24108
|
createSecretKey: createSecretKey2,
|
|
24032
24109
|
createPublicKey: createPublicKey2,
|
|
24033
|
-
createPrivateKey: createPrivateKey2
|
|
24110
|
+
createPrivateKey: createPrivateKey2,
|
|
24111
|
+
generateKeyPair: generateKeyPair2
|
|
24034
24112
|
};
|
|
24035
24113
|
}
|
|
24036
24114
|
const cryptoModule = createCryptoModule();
|
|
24037
|
-
const { randomBytes, randomFillSync, randomFill, randomUUID, randomInt, getRandomValues, unsupported: unsupported$1, createHash, createHmac, hash, pbkdf2Sync, pbkdf2, sign, verify, createSign, createVerify, KeyObject, createSecretKey, createPublicKey, createPrivateKey, timingSafeEqual, getCiphers, getHashes } = cryptoModule;
|
|
24115
|
+
const { randomBytes, randomFillSync, randomFill, randomUUID, randomInt, getRandomValues, unsupported: unsupported$1, createHash, createHmac, hash, pbkdf2Sync, pbkdf2, sign, verify, createSign, createVerify, KeyObject, createSecretKey, createPublicKey, createPrivateKey, generateKeyPair, timingSafeEqual, getCiphers, getHashes } = cryptoModule;
|
|
24038
24116
|
const cryptoShim = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
24039
24117
|
__proto__: null,
|
|
24040
24118
|
KeyObject,
|
|
@@ -24048,6 +24126,7 @@ const cryptoShim = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePr
|
|
|
24048
24126
|
createSign,
|
|
24049
24127
|
createVerify,
|
|
24050
24128
|
default: cryptoModule,
|
|
24129
|
+
generateKeyPair,
|
|
24051
24130
|
getCiphers,
|
|
24052
24131
|
getHashes,
|
|
24053
24132
|
getRandomValues,
|
|
@@ -39839,7 +39918,7 @@ class WorkerRuntime {
|
|
|
39839
39918
|
this.worker = new Worker(
|
|
39840
39919
|
new URL(
|
|
39841
39920
|
/* @vite-ignore */
|
|
39842
|
-
"/assets/runtime-worker-
|
|
39921
|
+
"/assets/runtime-worker-Bw5ePWPu.js",
|
|
39843
39922
|
import.meta.url
|
|
39844
39923
|
),
|
|
39845
39924
|
{ type: "module" }
|
package/dist/shims/crypto.d.ts
CHANGED
|
@@ -245,6 +245,19 @@ export declare function createCryptoModule(require?: (name: string) => any): {
|
|
|
245
245
|
format?: string;
|
|
246
246
|
}): Buffer | string;
|
|
247
247
|
};
|
|
248
|
+
generateKeyPair: (type: string, options: {
|
|
249
|
+
modulusLength?: number;
|
|
250
|
+
publicExponent?: number;
|
|
251
|
+
namedCurve?: string;
|
|
252
|
+
publicKeyEncoding?: {
|
|
253
|
+
type?: string;
|
|
254
|
+
format?: string;
|
|
255
|
+
};
|
|
256
|
+
privateKeyEncoding?: {
|
|
257
|
+
type?: string;
|
|
258
|
+
format?: string;
|
|
259
|
+
};
|
|
260
|
+
} | ((error: Error | null, publicKey?: unknown, privateKey?: unknown) => void), callback?: (error: Error | null, publicKey?: unknown, privateKey?: unknown) => void) => void;
|
|
248
261
|
};
|
|
249
262
|
declare const cryptoModule: {
|
|
250
263
|
unsupported: (operation: string) => never;
|
|
@@ -477,6 +490,19 @@ declare const cryptoModule: {
|
|
|
477
490
|
format?: string;
|
|
478
491
|
}): Buffer | string;
|
|
479
492
|
};
|
|
493
|
+
generateKeyPair: (type: string, options: {
|
|
494
|
+
modulusLength?: number;
|
|
495
|
+
publicExponent?: number;
|
|
496
|
+
namedCurve?: string;
|
|
497
|
+
publicKeyEncoding?: {
|
|
498
|
+
type?: string;
|
|
499
|
+
format?: string;
|
|
500
|
+
};
|
|
501
|
+
privateKeyEncoding?: {
|
|
502
|
+
type?: string;
|
|
503
|
+
format?: string;
|
|
504
|
+
};
|
|
505
|
+
} | ((error: Error | null, publicKey?: unknown, privateKey?: unknown) => void), callback?: (error: Error | null, publicKey?: unknown, privateKey?: unknown) => void) => void;
|
|
480
506
|
};
|
|
481
507
|
export declare const randomBytes: {
|
|
482
508
|
(size: number): Buffer;
|
|
@@ -679,6 +705,18 @@ export declare const randomBytes: {
|
|
|
679
705
|
type?: string;
|
|
680
706
|
format?: string;
|
|
681
707
|
}): Buffer | string;
|
|
682
|
-
},
|
|
708
|
+
}, generateKeyPair: (type: string, options: {
|
|
709
|
+
modulusLength?: number;
|
|
710
|
+
publicExponent?: number;
|
|
711
|
+
namedCurve?: string;
|
|
712
|
+
publicKeyEncoding?: {
|
|
713
|
+
type?: string;
|
|
714
|
+
format?: string;
|
|
715
|
+
};
|
|
716
|
+
privateKeyEncoding?: {
|
|
717
|
+
type?: string;
|
|
718
|
+
format?: string;
|
|
719
|
+
};
|
|
720
|
+
} | ((error: Error | null, publicKey?: unknown, privateKey?: unknown) => void), callback?: (error: Error | null, publicKey?: unknown, privateKey?: unknown) => void) => void, timingSafeEqual: (a: Buffer | Uint8Array, b: Buffer | Uint8Array) => boolean, getCiphers: () => string[], getHashes: () => string[];
|
|
683
721
|
export type KeyObject = InstanceType<typeof KeyObject>;
|
|
684
722
|
export default cryptoModule;
|
package/package.json
CHANGED
package/src/shims/crypto.ts
CHANGED
|
@@ -800,6 +800,92 @@ function createPrivateKey(key: KeyLike): KeyObject {
|
|
|
800
800
|
return new KeyObject('private', keyInfo.keyData as Uint8Array, keyInfo.algorithm);
|
|
801
801
|
}
|
|
802
802
|
|
|
803
|
+
// ============================================================================
|
|
804
|
+
// generateKeyPair: Node's asynchronous key generation, done by WebCrypto.
|
|
805
|
+
// A browser has no synchronous key generation, so generateKeyPairSync stays
|
|
806
|
+
// absent; the callback form and its promisified form (which resolves
|
|
807
|
+
// { publicKey, privateKey }, as Node's custom promisify does) are complete for
|
|
808
|
+
// the types WebCrypto generates. jose's Node build promisifies this function
|
|
809
|
+
// when it loads, so its absence failed any module that imported jose.
|
|
810
|
+
// ============================================================================
|
|
811
|
+
|
|
812
|
+
type KeyEncoding = { type?: string; format?: string };
|
|
813
|
+
type KeyPairOptions = { modulusLength?: number; publicExponent?: number; namedCurve?: string; publicKeyEncoding?: KeyEncoding; privateKeyEncoding?: KeyEncoding };
|
|
814
|
+
|
|
815
|
+
const NAMED_CURVES: Record<string, string> = {
|
|
816
|
+
'prime256v1': 'P-256', 'p-256': 'P-256', 'secp256r1': 'P-256',
|
|
817
|
+
'secp384r1': 'P-384', 'p-384': 'P-384',
|
|
818
|
+
'secp521r1': 'P-521', 'p-521': 'P-521',
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
function keyPairAlgorithm(type: string, options: KeyPairOptions): { generate: AlgorithmIdentifier & Record<string, unknown>; usages: KeyUsage[]; name: string } {
|
|
822
|
+
const exponent = (value: number | undefined): Uint8Array => {
|
|
823
|
+
let rest = value ?? 0x10001;
|
|
824
|
+
const bytes: number[] = [];
|
|
825
|
+
while (rest > 0) { bytes.unshift(rest & 0xff); rest = Math.floor(rest / 256); }
|
|
826
|
+
return new Uint8Array(bytes);
|
|
827
|
+
};
|
|
828
|
+
switch (type) {
|
|
829
|
+
case 'rsa':
|
|
830
|
+
case 'rsa-pss': {
|
|
831
|
+
if (!Number.isInteger(options.modulusLength)) throw Object.assign(new TypeError('The "options.modulusLength" property must be of type number.'), { code: 'ERR_INVALID_ARG_TYPE' });
|
|
832
|
+
const name = type === 'rsa' ? 'RSASSA-PKCS1-v1_5' : 'RSA-PSS';
|
|
833
|
+
return { generate: { name, modulusLength: options.modulusLength, publicExponent: exponent(options.publicExponent), hash: 'SHA-256' }, usages: ['sign', 'verify'], name: type === 'rsa' ? 'RSA-SHA256' : 'RSA-PSS' };
|
|
834
|
+
}
|
|
835
|
+
case 'ec': {
|
|
836
|
+
const curve = NAMED_CURVES[String(options.namedCurve ?? '').toLowerCase()];
|
|
837
|
+
if (!curve) throw Object.assign(new TypeError(`Invalid EC curve name ${String(options.namedCurve)}`), { code: 'ERR_CRYPTO_INVALID_CURVE' });
|
|
838
|
+
return { generate: { name: 'ECDSA', namedCurve: curve }, usages: ['sign', 'verify'], name: curve === 'P-256' ? 'ES256' : curve === 'P-384' ? 'ES384' : 'ES512' };
|
|
839
|
+
}
|
|
840
|
+
case 'ed25519':
|
|
841
|
+
return { generate: { name: 'Ed25519' }, usages: ['sign', 'verify'], name: 'Ed25519' };
|
|
842
|
+
case 'x25519':
|
|
843
|
+
return { generate: { name: 'X25519' }, usages: ['deriveBits'], name: 'X25519' };
|
|
844
|
+
default:
|
|
845
|
+
throw Object.assign(new TypeError(`The argument 'type' must be a supported key type. Received '${type}'`), { code: 'ERR_INVALID_ARG_VALUE' });
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function encodedKey(der: ArrayBuffer, encoding: KeyEncoding, kind: 'public' | 'private'): string | Buffer {
|
|
850
|
+
const expected = kind === 'public' ? 'spki' : 'pkcs8';
|
|
851
|
+
if (encoding.type !== undefined && encoding.type !== expected) {
|
|
852
|
+
unsupported(`Encoding a ${kind} key as ${encoding.type}`);
|
|
853
|
+
}
|
|
854
|
+
const bytes = Buffer.from(der);
|
|
855
|
+
if (encoding.format === 'der') return bytes;
|
|
856
|
+
if (encoding.format !== 'pem') unsupported(`Encoding a key in the ${String(encoding.format)} format`);
|
|
857
|
+
const label = kind === 'public' ? 'PUBLIC KEY' : 'PRIVATE KEY';
|
|
858
|
+
const body = bytes.toString('base64').match(/.{1,64}/g)!.join('\n');
|
|
859
|
+
return `-----BEGIN ${label}-----\n${body}\n-----END ${label}-----\n`;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
async function generateKeyPairAsync(type: string, options: KeyPairOptions): Promise<{ publicKey: KeyObject | string | Buffer; privateKey: KeyObject | string | Buffer }> {
|
|
863
|
+
const algorithm = keyPairAlgorithm(type, options);
|
|
864
|
+
const pair = await crypto.subtle.generateKey(algorithm.generate as AlgorithmIdentifier, true, algorithm.usages) as CryptoKeyPair;
|
|
865
|
+
const publicKey = options.publicKeyEncoding
|
|
866
|
+
? encodedKey(await crypto.subtle.exportKey('spki', pair.publicKey), options.publicKeyEncoding, 'public')
|
|
867
|
+
: new KeyObject('public', pair.publicKey, algorithm.name);
|
|
868
|
+
const privateKey = options.privateKeyEncoding
|
|
869
|
+
? encodedKey(await crypto.subtle.exportKey('pkcs8', pair.privateKey), options.privateKeyEncoding, 'private')
|
|
870
|
+
: new KeyObject('private', pair.privateKey, algorithm.name);
|
|
871
|
+
return { publicKey, privateKey };
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
function generateKeyPair(type: string, options: KeyPairOptions | ((error: Error | null, publicKey?: unknown, privateKey?: unknown) => void), callback?: (error: Error | null, publicKey?: unknown, privateKey?: unknown) => void): void {
|
|
875
|
+
if (typeof options === 'function') { callback = options; options = {}; }
|
|
876
|
+
if (typeof callback !== 'function') throw Object.assign(new TypeError('The "callback" argument must be of type function.'), { code: 'ERR_INVALID_ARG_TYPE' });
|
|
877
|
+
const done = callback;
|
|
878
|
+
// Argument errors throw synchronously, as Node's do.
|
|
879
|
+
keyPairAlgorithm(type, options ?? {});
|
|
880
|
+
generateKeyPairAsync(type, options ?? {}).then(
|
|
881
|
+
({ publicKey, privateKey }) => done(null, publicKey, privateKey),
|
|
882
|
+
(error) => done(error instanceof Error ? error : new Error(String(error))),
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
Object.defineProperty(generateKeyPair, Symbol.for('nodejs.util.promisify.custom'), {
|
|
886
|
+
value: (type: string, options?: KeyPairOptions) => generateKeyPairAsync(type, options ?? {}),
|
|
887
|
+
});
|
|
888
|
+
|
|
803
889
|
// ============================================================================
|
|
804
890
|
// Utility functions
|
|
805
891
|
// ============================================================================
|
|
@@ -1067,10 +1153,11 @@ return {
|
|
|
1067
1153
|
createSecretKey,
|
|
1068
1154
|
createPublicKey,
|
|
1069
1155
|
createPrivateKey,
|
|
1156
|
+
generateKeyPair,
|
|
1070
1157
|
};
|
|
1071
1158
|
|
|
1072
1159
|
}
|
|
1073
1160
|
const cryptoModule = createCryptoModule();
|
|
1074
|
-
export const { randomBytes, randomFillSync, randomFill, randomUUID, randomInt, getRandomValues, unsupported, createHash, createHmac, hash, pbkdf2Sync, pbkdf2, sign, verify, createSign, createVerify, KeyObject, createSecretKey, createPublicKey, createPrivateKey, timingSafeEqual, getCiphers, getHashes } = cryptoModule;
|
|
1161
|
+
export const { randomBytes, randomFillSync, randomFill, randomUUID, randomInt, getRandomValues, unsupported, createHash, createHmac, hash, pbkdf2Sync, pbkdf2, sign, verify, createSign, createVerify, KeyObject, createSecretKey, createPublicKey, createPrivateKey, generateKeyPair, timingSafeEqual, getCiphers, getHashes } = cryptoModule;
|
|
1075
1162
|
export type KeyObject = InstanceType<typeof KeyObject>;
|
|
1076
1163
|
export default cryptoModule;
|