extension-develop 3.17.0 → 3.18.1
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/0~dev-server.mjs +660 -59
- package/dist/0~rspack-config.mjs +867 -881
- package/dist/0~zip.mjs +39 -10
- package/dist/45.mjs +39 -0
- package/dist/946.mjs +142 -36
- package/dist/962.mjs +299 -250
- package/dist/bridge.mjs +228 -0
- package/dist/extension-js-devtools/chrome/content_scripts/content-0.js +2 -2
- package/dist/extension-js-devtools/chrome/pages/centralized-logger.js +1 -1
- package/dist/extension-js-devtools/chrome/pages/welcome.js +2 -2
- package/dist/extension-js-devtools/chrome/scripts/logger-client.js +1 -1
- package/dist/extension-js-devtools/chromium/content_scripts/content-0.js +2 -2
- package/dist/extension-js-devtools/chromium/pages/centralized-logger.js +1 -1
- package/dist/extension-js-devtools/chromium/pages/welcome.js +2 -2
- package/dist/extension-js-devtools/chromium/scripts/logger-client.js +1 -1
- package/dist/extension-js-devtools/edge/content_scripts/content-0.js +2 -2
- package/dist/extension-js-devtools/edge/pages/centralized-logger.js +1 -1
- package/dist/extension-js-devtools/edge/pages/welcome.js +2 -2
- package/dist/extension-js-devtools/edge/scripts/logger-client.js +1 -1
- package/dist/extension-js-devtools/extension-js/chrome/events.ndjson +2 -0
- package/dist/extension-js-devtools/extension-js/chrome/ready.json +16 -0
- package/dist/extension-js-devtools/extension-js/chromium/events.ndjson +2 -0
- package/dist/extension-js-devtools/extension-js/chromium/ready.json +16 -0
- package/dist/extension-js-devtools/extension-js/edge/events.ndjson +2 -0
- package/dist/extension-js-devtools/extension-js/edge/ready.json +16 -0
- package/dist/extension-js-devtools/extension-js/firefox/events.ndjson +2 -0
- package/dist/extension-js-devtools/extension-js/firefox/ready.json +16 -0
- package/dist/extension-js-devtools/firefox/content_scripts/content-0.js +2 -2
- package/dist/extension-js-devtools/firefox/pages/centralized-logger.js +1 -1
- package/dist/extension-js-devtools/firefox/pages/welcome.js +2 -2
- package/dist/extension-js-devtools/firefox/scripts/logger-client.js +1 -1
- package/dist/extension-js-theme/extension-js/chrome/events.ndjson +2 -0
- package/dist/extension-js-theme/extension-js/chrome/ready.json +16 -0
- package/dist/extension-js-theme/extension-js/chromium/events.ndjson +2 -0
- package/dist/extension-js-theme/extension-js/chromium/ready.json +16 -0
- package/dist/extension-js-theme/extension-js/edge/events.ndjson +2 -0
- package/dist/extension-js-theme/extension-js/edge/ready.json +16 -0
- package/dist/extension-js-theme/extension-js/firefox/events.ndjson +4 -0
- package/dist/extension-js-theme/extension-js/firefox/ready.json +16 -0
- package/dist/feature-scripts-content-script-wrapper.js +19 -2
- package/dist/feature-scripts-content-script-wrapper.mjs +19 -2
- package/package.json +14 -5
- package/runtime/process-shim.cjs +49 -0
package/dist/0~dev-server.mjs
CHANGED
|
@@ -4,7 +4,9 @@ import { Writable } from "stream";
|
|
|
4
4
|
import { rspack } from "@rspack/core";
|
|
5
5
|
import { RspackDevServer } from "@rspack/dev-server";
|
|
6
6
|
import { merge } from "webpack-merge";
|
|
7
|
+
import { WebSocket, WebSocketServer } from "ws";
|
|
7
8
|
import { createPlaywrightMetadataWriter, autoExitModeEnabled, autoExitForceKill, loadCommandConfig, extensionJsRunnerError, portInUse, devServerStartTimeout, autoExitTriggered, loadCustomConfig, loadBrowserConfig, sanitize, resolveCompanionExtensionsConfig, getSpecialFoldersDataForProjectRoot } from "./946.mjs";
|
|
9
|
+
import { CONTROL_WS_PATH, clearControlToken, writeControlToken } from "./45.mjs";
|
|
8
10
|
import { setupNoBrowserBannerOnFirstDone, webpackConfig, setupCompilerLifecycleHooks } from "./0~rspack-config.mjs";
|
|
9
11
|
import * as __rspack_external_path from "path";
|
|
10
12
|
import * as __rspack_external_crypto from "crypto";
|
|
@@ -47,65 +49,29 @@ function resolveInstanceIdOverride() {
|
|
|
47
49
|
class PortManager {
|
|
48
50
|
basePort;
|
|
49
51
|
currentInstance = null;
|
|
50
|
-
constructor(
|
|
52
|
+
constructor(basePort = 8080){
|
|
51
53
|
this.basePort = basePort;
|
|
52
54
|
}
|
|
53
|
-
async allocatePorts(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
port,
|
|
67
|
-
webSocketPort,
|
|
68
|
-
instanceId
|
|
69
|
-
};
|
|
70
|
-
} catch (error) {
|
|
71
|
-
throw error;
|
|
72
|
-
}
|
|
55
|
+
async allocatePorts(requestedPort) {
|
|
56
|
+
const isValidRequested = 'number' == typeof requestedPort && requestedPort >= 0 && requestedPort < 65536;
|
|
57
|
+
const base = isValidRequested ? requestedPort : this.basePort;
|
|
58
|
+
const port = await findAvailablePortNear(base);
|
|
59
|
+
const instanceId = resolveInstanceIdOverride() || __rspack_external_crypto.randomBytes(8).toString('hex');
|
|
60
|
+
this.currentInstance = {
|
|
61
|
+
instanceId,
|
|
62
|
+
port
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
port,
|
|
66
|
+
instanceId
|
|
67
|
+
};
|
|
73
68
|
}
|
|
74
69
|
getCurrentInstance() {
|
|
75
70
|
return this.currentInstance;
|
|
76
71
|
}
|
|
77
|
-
async updateExtensionId(extensionId) {
|
|
78
|
-
if (this.currentInstance) this.currentInstance.extensionId = extensionId;
|
|
79
|
-
}
|
|
80
72
|
async terminateCurrentInstance() {
|
|
81
73
|
this.currentInstance = null;
|
|
82
74
|
}
|
|
83
|
-
getPortInfo(allocation) {
|
|
84
|
-
return `Port: ${allocation.port}, WebSocket: ${allocation.webSocketPort}, Instance: ${allocation.instanceId.slice(0, 8)}`;
|
|
85
|
-
}
|
|
86
|
-
async isPortInUse(port) {
|
|
87
|
-
return new Promise((resolve)=>{
|
|
88
|
-
const server = __rspack_external_net.createServer();
|
|
89
|
-
server.once('error', ()=>resolve(true));
|
|
90
|
-
server.once('listening', ()=>{
|
|
91
|
-
server.close(()=>resolve(false));
|
|
92
|
-
});
|
|
93
|
-
server.listen(port, '127.0.0.1');
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
async getRunningInstances() {
|
|
97
|
-
return this.currentInstance ? [
|
|
98
|
-
this.currentInstance
|
|
99
|
-
] : [];
|
|
100
|
-
}
|
|
101
|
-
async getStats() {
|
|
102
|
-
return {
|
|
103
|
-
total: this.currentInstance ? 1 : 0,
|
|
104
|
-
running: this.currentInstance ? 1 : 0,
|
|
105
|
-
terminated: 0,
|
|
106
|
-
error: 0
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
75
|
}
|
|
110
76
|
function hasDependency(projectPath, dependency) {
|
|
111
77
|
const findNearestPackageJsonDirectory = (startPath)=>{
|
|
@@ -207,9 +173,8 @@ function setupCleanupHandlers(devServer, portManager) {
|
|
|
207
173
|
console.error('[Extension.js Runner] Uncaught exception.', error);
|
|
208
174
|
await cleanup();
|
|
209
175
|
});
|
|
210
|
-
process.on('unhandledRejection',
|
|
176
|
+
process.on('unhandledRejection', (reason, promise)=>{
|
|
211
177
|
console.error('[Extension.js Runner] Unhandled rejection.', promise, reason);
|
|
212
|
-
await cleanup();
|
|
213
178
|
});
|
|
214
179
|
const cancelAutoExit = setupAutoExit(process.env.EXTENSION_AUTO_EXIT_MS, process.env.EXTENSION_FORCE_KILL_MS, cleanup);
|
|
215
180
|
const cancelAndCleanup = async ()=>{
|
|
@@ -218,12 +183,590 @@ function setupCleanupHandlers(devServer, portManager) {
|
|
|
218
183
|
} catch {}
|
|
219
184
|
await cleanup();
|
|
220
185
|
};
|
|
221
|
-
process.on('ERROR', cancelAndCleanup);
|
|
222
186
|
process.on('SIGINT', cancelAndCleanup);
|
|
223
187
|
process.on('SIGTERM', cancelAndCleanup);
|
|
224
188
|
process.on('SIGHUP', cancelAndCleanup);
|
|
225
189
|
return cancelAutoExit;
|
|
226
190
|
}
|
|
191
|
+
const DEFAULT_RING_CAPACITY = 5000;
|
|
192
|
+
class LogRingBuffer {
|
|
193
|
+
capacity;
|
|
194
|
+
buf = [];
|
|
195
|
+
nextSeq = 1;
|
|
196
|
+
droppedSinceDrain = 0;
|
|
197
|
+
constructor(capacity = DEFAULT_RING_CAPACITY){
|
|
198
|
+
this.capacity = Math.max(1, Math.floor(capacity));
|
|
199
|
+
}
|
|
200
|
+
push(incoming) {
|
|
201
|
+
const event = {
|
|
202
|
+
...incoming,
|
|
203
|
+
v: 1,
|
|
204
|
+
seq: this.nextSeq++
|
|
205
|
+
};
|
|
206
|
+
this.buf.push(event);
|
|
207
|
+
if (this.buf.length > this.capacity) {
|
|
208
|
+
this.buf.shift();
|
|
209
|
+
this.droppedSinceDrain++;
|
|
210
|
+
}
|
|
211
|
+
return event;
|
|
212
|
+
}
|
|
213
|
+
get size() {
|
|
214
|
+
return this.buf.length;
|
|
215
|
+
}
|
|
216
|
+
get bufferedFrom() {
|
|
217
|
+
return this.buf.length ? this.buf[0].seq : this.nextSeq;
|
|
218
|
+
}
|
|
219
|
+
get nextSequence() {
|
|
220
|
+
return this.nextSeq;
|
|
221
|
+
}
|
|
222
|
+
snapshot() {
|
|
223
|
+
return this.buf.slice();
|
|
224
|
+
}
|
|
225
|
+
since(seq) {
|
|
226
|
+
if (!Number.isFinite(seq)) return this.snapshot();
|
|
227
|
+
return this.buf.filter((e)=>e.seq > seq);
|
|
228
|
+
}
|
|
229
|
+
drainDropped() {
|
|
230
|
+
if (0 === this.droppedSinceDrain) return null;
|
|
231
|
+
const dropped = this.droppedSinceDrain;
|
|
232
|
+
this.droppedSinceDrain = 0;
|
|
233
|
+
return {
|
|
234
|
+
dropped,
|
|
235
|
+
reason: 'ring_overflow',
|
|
236
|
+
sinceSeq: this.bufferedFrom
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const DEFAULTS = {
|
|
241
|
+
maxBytes: 8388608,
|
|
242
|
+
maxLines: 50000,
|
|
243
|
+
generations: 3,
|
|
244
|
+
flushIntervalMs: 250,
|
|
245
|
+
maxQueue: 20000
|
|
246
|
+
};
|
|
247
|
+
class LogsFileWriter {
|
|
248
|
+
opts;
|
|
249
|
+
queue = [];
|
|
250
|
+
bytes = 0;
|
|
251
|
+
lines = 0;
|
|
252
|
+
droppedToDisk = 0;
|
|
253
|
+
timer = null;
|
|
254
|
+
started = false;
|
|
255
|
+
constructor(options){
|
|
256
|
+
this.opts = {
|
|
257
|
+
...DEFAULTS,
|
|
258
|
+
...options
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
start() {
|
|
262
|
+
if (this.started) return;
|
|
263
|
+
this.started = true;
|
|
264
|
+
try {
|
|
265
|
+
__rspack_external_fs.mkdirSync(__rspack_external_path.dirname(this.opts.filePath), {
|
|
266
|
+
recursive: true
|
|
267
|
+
});
|
|
268
|
+
if (__rspack_external_fs.existsSync(this.opts.filePath)) this.rotate();
|
|
269
|
+
} catch {}
|
|
270
|
+
this.writeHeader(null);
|
|
271
|
+
this.timer = setInterval(()=>this.flush(), this.opts.flushIntervalMs);
|
|
272
|
+
if (this.timer.unref) this.timer.unref();
|
|
273
|
+
}
|
|
274
|
+
write(event) {
|
|
275
|
+
this.enqueue(JSON.stringify(event));
|
|
276
|
+
}
|
|
277
|
+
enqueue(line) {
|
|
278
|
+
this.queue.push(line);
|
|
279
|
+
if (this.queue.length > this.opts.maxQueue) {
|
|
280
|
+
this.queue.shift();
|
|
281
|
+
this.droppedToDisk++;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
flush() {
|
|
285
|
+
if (!this.started || 0 === this.queue.length) return void this.maybeNoteDrops();
|
|
286
|
+
const batch = this.queue;
|
|
287
|
+
this.queue = [];
|
|
288
|
+
const payload = batch.join('\n') + '\n';
|
|
289
|
+
try {
|
|
290
|
+
__rspack_external_fs.appendFileSync(this.opts.filePath, payload, 'utf-8');
|
|
291
|
+
this.bytes += Buffer.byteLength(payload);
|
|
292
|
+
this.lines += batch.length;
|
|
293
|
+
} catch {
|
|
294
|
+
this.droppedToDisk += batch.length;
|
|
295
|
+
}
|
|
296
|
+
this.maybeNoteDrops();
|
|
297
|
+
if (this.bytes >= this.opts.maxBytes || this.lines >= this.opts.maxLines) {
|
|
298
|
+
this.rotate();
|
|
299
|
+
this.writeHeader(this.opts.runId);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
close() {
|
|
303
|
+
if (this.timer) {
|
|
304
|
+
clearInterval(this.timer);
|
|
305
|
+
this.timer = null;
|
|
306
|
+
}
|
|
307
|
+
this.flush();
|
|
308
|
+
this.started = false;
|
|
309
|
+
}
|
|
310
|
+
maybeNoteDrops() {
|
|
311
|
+
if (0 === this.droppedToDisk) return;
|
|
312
|
+
const dropped = this.droppedToDisk;
|
|
313
|
+
this.droppedToDisk = 0;
|
|
314
|
+
try {
|
|
315
|
+
const sentinel = JSON.stringify({
|
|
316
|
+
v: 1,
|
|
317
|
+
type: 'gap',
|
|
318
|
+
reason: 'disk_slow',
|
|
319
|
+
dropped
|
|
320
|
+
}) + '\n';
|
|
321
|
+
__rspack_external_fs.appendFileSync(this.opts.filePath, sentinel, 'utf-8');
|
|
322
|
+
this.bytes += Buffer.byteLength(sentinel);
|
|
323
|
+
this.lines += 1;
|
|
324
|
+
} catch {}
|
|
325
|
+
}
|
|
326
|
+
writeHeader(rotatedFrom) {
|
|
327
|
+
const header = JSON.stringify({
|
|
328
|
+
v: 1,
|
|
329
|
+
type: 'header',
|
|
330
|
+
runId: this.opts.runId,
|
|
331
|
+
startedAt: new Date().toISOString(),
|
|
332
|
+
rotatedFrom
|
|
333
|
+
}) + '\n';
|
|
334
|
+
try {
|
|
335
|
+
__rspack_external_fs.writeFileSync(this.opts.filePath, header, 'utf-8');
|
|
336
|
+
this.bytes = Buffer.byteLength(header);
|
|
337
|
+
this.lines = 1;
|
|
338
|
+
} catch {
|
|
339
|
+
this.bytes = 0;
|
|
340
|
+
this.lines = 0;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
rotatedName(n) {
|
|
344
|
+
return this.opts.filePath.replace(/\.ndjson$/, `.${n}.ndjson`);
|
|
345
|
+
}
|
|
346
|
+
rotate() {
|
|
347
|
+
const { generations } = this.opts;
|
|
348
|
+
try {
|
|
349
|
+
const oldest = this.rotatedName(generations);
|
|
350
|
+
if (__rspack_external_fs.existsSync(oldest)) __rspack_external_fs.rmSync(oldest, {
|
|
351
|
+
force: true
|
|
352
|
+
});
|
|
353
|
+
for(let n = generations - 1; n >= 1; n--){
|
|
354
|
+
const from = this.rotatedName(n);
|
|
355
|
+
if (__rspack_external_fs.existsSync(from)) __rspack_external_fs.renameSync(from, this.rotatedName(n + 1));
|
|
356
|
+
}
|
|
357
|
+
if (__rspack_external_fs.existsSync(this.opts.filePath)) __rspack_external_fs.renameSync(this.opts.filePath, this.rotatedName(1));
|
|
358
|
+
} catch {}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const actions_file_DEFAULTS = {
|
|
362
|
+
maxBytes: 8388608,
|
|
363
|
+
maxLines: 50000,
|
|
364
|
+
generations: 3
|
|
365
|
+
};
|
|
366
|
+
class ActionsFileWriter {
|
|
367
|
+
opts;
|
|
368
|
+
bytes = 0;
|
|
369
|
+
lines = 0;
|
|
370
|
+
started = false;
|
|
371
|
+
constructor(options){
|
|
372
|
+
this.opts = {
|
|
373
|
+
...actions_file_DEFAULTS,
|
|
374
|
+
...options
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
start() {
|
|
378
|
+
if (this.started) return;
|
|
379
|
+
this.started = true;
|
|
380
|
+
try {
|
|
381
|
+
__rspack_external_fs.mkdirSync(__rspack_external_path.dirname(this.opts.filePath), {
|
|
382
|
+
recursive: true
|
|
383
|
+
});
|
|
384
|
+
if (__rspack_external_fs.existsSync(this.opts.filePath)) this.rotate();
|
|
385
|
+
} catch {}
|
|
386
|
+
this.bytes = 0;
|
|
387
|
+
this.lines = 0;
|
|
388
|
+
}
|
|
389
|
+
write(record) {
|
|
390
|
+
if (!this.started) this.start();
|
|
391
|
+
const line = JSON.stringify(record) + '\n';
|
|
392
|
+
try {
|
|
393
|
+
__rspack_external_fs.appendFileSync(this.opts.filePath, line, 'utf-8');
|
|
394
|
+
this.bytes += Buffer.byteLength(line);
|
|
395
|
+
this.lines += 1;
|
|
396
|
+
} catch {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (this.bytes >= this.opts.maxBytes || this.lines >= this.opts.maxLines) {
|
|
400
|
+
this.rotate();
|
|
401
|
+
this.bytes = 0;
|
|
402
|
+
this.lines = 0;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
close() {
|
|
406
|
+
this.started = false;
|
|
407
|
+
}
|
|
408
|
+
rotatedName(n) {
|
|
409
|
+
return this.opts.filePath.replace(/\.ndjson$/, `.${n}.ndjson`);
|
|
410
|
+
}
|
|
411
|
+
rotate() {
|
|
412
|
+
const { generations } = this.opts;
|
|
413
|
+
try {
|
|
414
|
+
const oldest = this.rotatedName(generations);
|
|
415
|
+
if (__rspack_external_fs.existsSync(oldest)) __rspack_external_fs.rmSync(oldest, {
|
|
416
|
+
force: true
|
|
417
|
+
});
|
|
418
|
+
for(let n = generations - 1; n >= 1; n--){
|
|
419
|
+
const from = this.rotatedName(n);
|
|
420
|
+
if (__rspack_external_fs.existsSync(from)) __rspack_external_fs.renameSync(from, this.rotatedName(n + 1));
|
|
421
|
+
}
|
|
422
|
+
if (__rspack_external_fs.existsSync(this.opts.filePath)) __rspack_external_fs.renameSync(this.opts.filePath, this.rotatedName(1));
|
|
423
|
+
} catch {}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function fnv1aHex(input) {
|
|
427
|
+
let hash = 0x811c9dc5;
|
|
428
|
+
for(let i = 0; i < input.length; i++){
|
|
429
|
+
hash ^= input.charCodeAt(i);
|
|
430
|
+
hash = Math.imul(hash, 0x01000193);
|
|
431
|
+
}
|
|
432
|
+
return (hash >>> 0).toString(16).padStart(8, '0');
|
|
433
|
+
}
|
|
434
|
+
const CLOSE_BAD_INSTANCE = 4001;
|
|
435
|
+
const CLOSE_BAD_HELLO = 4002;
|
|
436
|
+
const CLOSE_CONTROL_UNAVAILABLE = 4003;
|
|
437
|
+
const DEFAULT_CMD_TIMEOUT_MS = 5000;
|
|
438
|
+
const MAX_CMD_TIMEOUT_MS = 30000;
|
|
439
|
+
const CONTROL_OPS = new Set([
|
|
440
|
+
'eval',
|
|
441
|
+
'storage.get',
|
|
442
|
+
'storage.set',
|
|
443
|
+
'reload',
|
|
444
|
+
'open',
|
|
445
|
+
'tabs.query',
|
|
446
|
+
'inspect'
|
|
447
|
+
]);
|
|
448
|
+
class BridgeBroker {
|
|
449
|
+
instanceId;
|
|
450
|
+
runId;
|
|
451
|
+
engine;
|
|
452
|
+
ring;
|
|
453
|
+
file;
|
|
454
|
+
roles = new Map();
|
|
455
|
+
allowControl;
|
|
456
|
+
allowEval;
|
|
457
|
+
controlToken;
|
|
458
|
+
actions;
|
|
459
|
+
authorMode;
|
|
460
|
+
now;
|
|
461
|
+
setTimer;
|
|
462
|
+
clearTimer;
|
|
463
|
+
evalAllowed = new Map();
|
|
464
|
+
pending = new Map();
|
|
465
|
+
constructor(options){
|
|
466
|
+
this.instanceId = options.instanceId;
|
|
467
|
+
this.runId = options.runId;
|
|
468
|
+
this.engine = options.engine;
|
|
469
|
+
this.ring = options.ring ?? new LogRingBuffer();
|
|
470
|
+
this.file = options.file;
|
|
471
|
+
this.allowControl = options.allowControl ?? false;
|
|
472
|
+
this.allowEval = options.allowEval ?? false;
|
|
473
|
+
this.controlToken = options.controlToken;
|
|
474
|
+
this.actions = options.actions;
|
|
475
|
+
this.authorMode = options.authorMode ?? false;
|
|
476
|
+
this.now = options.now ?? (()=>Date.now());
|
|
477
|
+
this.setTimer = options.setTimer ?? ((fn, ms)=>setTimeout(fn, ms));
|
|
478
|
+
this.clearTimer = options.clearTimer ?? ((h)=>clearTimeout(h));
|
|
479
|
+
}
|
|
480
|
+
get consumerCount() {
|
|
481
|
+
return this.countRole('consumer');
|
|
482
|
+
}
|
|
483
|
+
get producerCount() {
|
|
484
|
+
return this.countRole('producer');
|
|
485
|
+
}
|
|
486
|
+
get controllerCount() {
|
|
487
|
+
return this.countRole('controller');
|
|
488
|
+
}
|
|
489
|
+
get pendingCount() {
|
|
490
|
+
return this.pending.size;
|
|
491
|
+
}
|
|
492
|
+
onFrame(conn, frame) {
|
|
493
|
+
switch(frame.type){
|
|
494
|
+
case 'hello':
|
|
495
|
+
this.onHello(conn, frame);
|
|
496
|
+
return;
|
|
497
|
+
case 'log':
|
|
498
|
+
if ('producer' === this.roles.get(conn)) this.ingestLog(frame.event);
|
|
499
|
+
return;
|
|
500
|
+
case 'command':
|
|
501
|
+
if ('controller' === this.roles.get(conn)) this.onCommand(conn, frame);
|
|
502
|
+
return;
|
|
503
|
+
case 'result':
|
|
504
|
+
if ('producer' === this.roles.get(conn)) this.onResult(frame);
|
|
505
|
+
return;
|
|
506
|
+
default:
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
onClose(conn) {
|
|
511
|
+
this.roles.delete(conn);
|
|
512
|
+
this.evalAllowed.delete(conn);
|
|
513
|
+
for (const [cmdId, p] of this.pending)if (p.controller === conn) {
|
|
514
|
+
this.clearTimer(p.timer);
|
|
515
|
+
this.pending.delete(cmdId);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
ingestLog(incoming) {
|
|
519
|
+
const event = this.ring.push(incoming);
|
|
520
|
+
this.file?.write(event);
|
|
521
|
+
this.fanOut({
|
|
522
|
+
type: 'log',
|
|
523
|
+
event
|
|
524
|
+
});
|
|
525
|
+
const gap = this.ring.drainDropped();
|
|
526
|
+
if (gap) this.fanOut({
|
|
527
|
+
type: 'gap',
|
|
528
|
+
dropped: gap.dropped,
|
|
529
|
+
reason: gap.reason,
|
|
530
|
+
sinceSeq: gap.sinceSeq
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
onHello(conn, hello) {
|
|
534
|
+
if (1 !== hello.v) return void conn.close(CLOSE_BAD_HELLO, 'unsupported envelope version');
|
|
535
|
+
if (hello.instanceId !== this.instanceId) return void conn.close(CLOSE_BAD_INSTANCE, 'instanceId mismatch');
|
|
536
|
+
if ('controller' === hello.role) {
|
|
537
|
+
if (!this.allowControl) return void conn.close(CLOSE_CONTROL_UNAVAILABLE, 'control channel not available');
|
|
538
|
+
this.roles.set(conn, 'controller');
|
|
539
|
+
this.evalAllowed.set(conn, this.allowEval && !!hello.token && !!this.controlToken && hello.token === this.controlToken);
|
|
540
|
+
conn.send(this.controllerReady());
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
if ('producer' !== hello.role && 'consumer' !== hello.role) return void conn.close(CLOSE_BAD_HELLO, 'unknown role');
|
|
544
|
+
this.roles.set(conn, hello.role);
|
|
545
|
+
if ('consumer' === hello.role) {
|
|
546
|
+
const ready = {
|
|
547
|
+
type: 'ready',
|
|
548
|
+
runId: this.runId,
|
|
549
|
+
bufferedFrom: this.ring.bufferedFrom,
|
|
550
|
+
engine: this.engine
|
|
551
|
+
};
|
|
552
|
+
conn.send(ready);
|
|
553
|
+
for (const event of this.ring.snapshot())conn.send({
|
|
554
|
+
type: 'log',
|
|
555
|
+
event
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
controllerReady() {
|
|
560
|
+
const isFirefox = 'firefox' === this.engine;
|
|
561
|
+
return {
|
|
562
|
+
type: 'ready',
|
|
563
|
+
runId: this.runId,
|
|
564
|
+
bufferedFrom: this.ring.bufferedFrom,
|
|
565
|
+
engine: this.engine,
|
|
566
|
+
capabilities: {
|
|
567
|
+
eval: this.allowEval,
|
|
568
|
+
storage: this.allowControl,
|
|
569
|
+
reload: this.allowControl,
|
|
570
|
+
open: this.allowControl ? isFirefox ? [
|
|
571
|
+
'popup',
|
|
572
|
+
'options'
|
|
573
|
+
] : [
|
|
574
|
+
'popup',
|
|
575
|
+
'options',
|
|
576
|
+
'sidebar'
|
|
577
|
+
] : [],
|
|
578
|
+
deepDom: 'chromium' === this.engine
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
onCommand(controller, cmd) {
|
|
583
|
+
const principal = 'controller';
|
|
584
|
+
const isEval = 'eval' === cmd.op;
|
|
585
|
+
const exprHash = isEval && 'string' == typeof cmd.args?.expression ? fnv1aHex(cmd.args.expression) : void 0;
|
|
586
|
+
const expr = isEval && this.authorMode && 'string' == typeof cmd.args?.expression ? cmd.args.expression : void 0;
|
|
587
|
+
const deny = (name, message)=>{
|
|
588
|
+
const result = {
|
|
589
|
+
type: 'result',
|
|
590
|
+
cmdId: cmd.cmdId,
|
|
591
|
+
ok: false,
|
|
592
|
+
error: {
|
|
593
|
+
name,
|
|
594
|
+
message
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
controller.send(result);
|
|
598
|
+
this.audit({
|
|
599
|
+
cmdId: cmd.cmdId,
|
|
600
|
+
op: cmd.op,
|
|
601
|
+
target: cmd.target,
|
|
602
|
+
ok: false,
|
|
603
|
+
durationMs: 0,
|
|
604
|
+
principal,
|
|
605
|
+
exprHash,
|
|
606
|
+
expr,
|
|
607
|
+
errorName: name
|
|
608
|
+
});
|
|
609
|
+
};
|
|
610
|
+
if (!CONTROL_OPS.has(cmd.op)) return void deny('BadRequest', `unknown op: ${String(cmd.op)}`);
|
|
611
|
+
if (isEval && !this.evalAllowed.get(controller)) return void deny('Forbidden', 'eval requires --allow-eval and a valid session token in the hello');
|
|
612
|
+
const executor = this.firstExecutor();
|
|
613
|
+
if (!executor) return void deny('Unavailable', 'no executor connected (is the dev session running?)');
|
|
614
|
+
const timeoutMs = Math.min(Math.max(cmd.timeoutMs ?? DEFAULT_CMD_TIMEOUT_MS, 1), MAX_CMD_TIMEOUT_MS);
|
|
615
|
+
const timer = this.setTimer(()=>this.onTimeout(cmd.cmdId), timeoutMs);
|
|
616
|
+
this.pending.set(cmd.cmdId, {
|
|
617
|
+
controller,
|
|
618
|
+
op: cmd.op,
|
|
619
|
+
target: cmd.target,
|
|
620
|
+
issuedAt: this.now(),
|
|
621
|
+
timer,
|
|
622
|
+
principal,
|
|
623
|
+
exprHash,
|
|
624
|
+
expr
|
|
625
|
+
});
|
|
626
|
+
executor.send({
|
|
627
|
+
type: 'command',
|
|
628
|
+
cmdId: cmd.cmdId,
|
|
629
|
+
op: cmd.op,
|
|
630
|
+
target: cmd.target,
|
|
631
|
+
args: cmd.args,
|
|
632
|
+
timeoutMs
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
onResult(result) {
|
|
636
|
+
const p = this.pending.get(result.cmdId);
|
|
637
|
+
if (!p) return;
|
|
638
|
+
this.clearTimer(p.timer);
|
|
639
|
+
this.pending.delete(result.cmdId);
|
|
640
|
+
p.controller.send(result);
|
|
641
|
+
this.audit({
|
|
642
|
+
cmdId: result.cmdId,
|
|
643
|
+
op: p.op,
|
|
644
|
+
target: p.target,
|
|
645
|
+
ok: result.ok,
|
|
646
|
+
durationMs: result.durationMs ?? this.now() - p.issuedAt,
|
|
647
|
+
principal: p.principal,
|
|
648
|
+
exprHash: p.exprHash,
|
|
649
|
+
expr: p.expr,
|
|
650
|
+
errorName: result.ok ? void 0 : result.error?.name
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
onTimeout(cmdId) {
|
|
654
|
+
const p = this.pending.get(cmdId);
|
|
655
|
+
if (!p) return;
|
|
656
|
+
this.pending.delete(cmdId);
|
|
657
|
+
const result = {
|
|
658
|
+
type: 'result',
|
|
659
|
+
cmdId,
|
|
660
|
+
ok: false,
|
|
661
|
+
error: {
|
|
662
|
+
name: 'Timeout',
|
|
663
|
+
message: 'command timed out'
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
p.controller.send(result);
|
|
667
|
+
this.audit({
|
|
668
|
+
cmdId,
|
|
669
|
+
op: p.op,
|
|
670
|
+
target: p.target,
|
|
671
|
+
ok: false,
|
|
672
|
+
durationMs: this.now() - p.issuedAt,
|
|
673
|
+
principal: p.principal,
|
|
674
|
+
exprHash: p.exprHash,
|
|
675
|
+
expr: p.expr,
|
|
676
|
+
errorName: 'Timeout'
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
firstExecutor() {
|
|
680
|
+
for (const [conn, role] of this.roles)if ('producer' === role) return conn;
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
audit(record) {
|
|
684
|
+
if (!this.actions) return;
|
|
685
|
+
const line = {
|
|
686
|
+
v: 1,
|
|
687
|
+
ts: new Date(this.now()).toISOString(),
|
|
688
|
+
...record
|
|
689
|
+
};
|
|
690
|
+
if (void 0 === line.exprHash) delete line.exprHash;
|
|
691
|
+
if (void 0 === line.expr) delete line.expr;
|
|
692
|
+
if (void 0 === line.errorName) delete line.errorName;
|
|
693
|
+
if (void 0 === line.durationMs) delete line.durationMs;
|
|
694
|
+
this.actions.write(line);
|
|
695
|
+
}
|
|
696
|
+
fanOut(frame) {
|
|
697
|
+
for (const [conn, role] of this.roles)if ('consumer' === role) try {
|
|
698
|
+
conn.send(frame);
|
|
699
|
+
} catch {}
|
|
700
|
+
}
|
|
701
|
+
countRole(role) {
|
|
702
|
+
let n = 0;
|
|
703
|
+
for (const r of this.roles.values())if (r === role) n++;
|
|
704
|
+
return n;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
const SLOW_CONSUMER_BYTES = 8388608;
|
|
708
|
+
const CLOSE_SLOW_CONSUMER = 4008;
|
|
709
|
+
let connSeq = 0;
|
|
710
|
+
function startControlServer(options) {
|
|
711
|
+
const { broker } = options;
|
|
712
|
+
const host = options.host ?? '127.0.0.1';
|
|
713
|
+
const path = options.path ?? CONTROL_WS_PATH;
|
|
714
|
+
return new Promise((resolve, reject)=>{
|
|
715
|
+
const wss = new WebSocketServer({
|
|
716
|
+
host,
|
|
717
|
+
port: options.port ?? 0,
|
|
718
|
+
path
|
|
719
|
+
});
|
|
720
|
+
wss.on('error', reject);
|
|
721
|
+
wss.on('connection', (socket)=>{
|
|
722
|
+
const conn = {
|
|
723
|
+
id: `c${++connSeq}`,
|
|
724
|
+
send (frame) {
|
|
725
|
+
if (socket.readyState !== WebSocket.OPEN) return;
|
|
726
|
+
if (socket.bufferedAmount > SLOW_CONSUMER_BYTES) {
|
|
727
|
+
try {
|
|
728
|
+
socket.close(CLOSE_SLOW_CONSUMER, 'slow consumer');
|
|
729
|
+
} catch {}
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
try {
|
|
733
|
+
socket.send(JSON.stringify(frame));
|
|
734
|
+
} catch {}
|
|
735
|
+
},
|
|
736
|
+
close (code, reason) {
|
|
737
|
+
try {
|
|
738
|
+
socket.close(code, reason);
|
|
739
|
+
} catch {}
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
socket.on('message', (data)=>{
|
|
743
|
+
let frame;
|
|
744
|
+
try {
|
|
745
|
+
frame = JSON.parse(data.toString());
|
|
746
|
+
} catch {
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
broker.onFrame(conn, frame);
|
|
750
|
+
});
|
|
751
|
+
socket.on('close', ()=>broker.onClose(conn));
|
|
752
|
+
socket.on('error', ()=>broker.onClose(conn));
|
|
753
|
+
});
|
|
754
|
+
wss.on('listening', ()=>{
|
|
755
|
+
const address = wss.address();
|
|
756
|
+
const port = 'object' == typeof address && address ? address.port : 0;
|
|
757
|
+
resolve({
|
|
758
|
+
port,
|
|
759
|
+
broker,
|
|
760
|
+
close: ()=>new Promise((res)=>{
|
|
761
|
+
for (const client of wss.clients)try {
|
|
762
|
+
client.terminate();
|
|
763
|
+
} catch {}
|
|
764
|
+
wss.close(()=>res());
|
|
765
|
+
})
|
|
766
|
+
});
|
|
767
|
+
});
|
|
768
|
+
});
|
|
769
|
+
}
|
|
227
770
|
function shouldWriteAssetToDisk(filePath) {
|
|
228
771
|
return !/(?:^|[/\\])manifest\.json$/i.test(filePath);
|
|
229
772
|
}
|
|
@@ -363,13 +906,12 @@ function installManifestDiskWriteGuard(manifestOutputPath) {
|
|
|
363
906
|
async function dev_server_devServer(projectStructure, devOptions) {
|
|
364
907
|
process.env.EXTENSION_BROWSER_LAUNCH_ENABLED = devOptions.noBrowser ? '0' : '1';
|
|
365
908
|
const { manifestPath, packageJsonPath } = projectStructure;
|
|
366
|
-
__rspack_external_path.dirname(manifestPath);
|
|
367
909
|
const packageJsonDir = __rspack_external_path.dirname(packageJsonPath);
|
|
368
910
|
const commandConfig = await loadCommandConfig(packageJsonDir, 'dev');
|
|
369
911
|
const browserConfig = await loadBrowserConfig(packageJsonDir, devOptions.browser);
|
|
370
|
-
const portManager = new PortManager(
|
|
912
|
+
const portManager = new PortManager(8080);
|
|
371
913
|
const desiredPort = 'string' == typeof devOptions.port ? parseInt(devOptions.port, 10) : devOptions.port;
|
|
372
|
-
const portAllocation = await portManager.allocatePorts(
|
|
914
|
+
const portAllocation = await portManager.allocatePorts(desiredPort);
|
|
373
915
|
const currentInstance = portManager.getCurrentInstance();
|
|
374
916
|
if (!currentInstance) throw new Error('Failed to create instance');
|
|
375
917
|
const port = portAllocation.port;
|
|
@@ -383,6 +925,56 @@ async function dev_server_devServer(projectStructure, devOptions) {
|
|
|
383
925
|
process.env.EXTENSION_DEV_SERVER_HOST = devServerHost;
|
|
384
926
|
process.env.EXTENSION_DEV_SERVER_PORT = String(port);
|
|
385
927
|
process.env.EXTENSION_DEV_SERVER_PATH = '/ws';
|
|
928
|
+
const browserName = String(devOptions.browser || 'chromium');
|
|
929
|
+
const bridgeLogsRelPath = __rspack_external_path.join('dist', 'extension-js', browserName, 'logs.ndjson');
|
|
930
|
+
const bridgeLogFile = new LogsFileWriter({
|
|
931
|
+
filePath: __rspack_external_path.join(packageJsonDir, bridgeLogsRelPath),
|
|
932
|
+
runId: currentInstance.instanceId
|
|
933
|
+
});
|
|
934
|
+
const allowControl = Boolean(devOptions.allowControl);
|
|
935
|
+
const allowEval = Boolean(devOptions.allowEval);
|
|
936
|
+
const authorMode = Boolean(devOptions.authorMode) || 'development' === process.env.EXTENSION_AUTHOR_MODE;
|
|
937
|
+
const bridgeActionsFile = allowControl ? new ActionsFileWriter({
|
|
938
|
+
filePath: __rspack_external_path.join(packageJsonDir, 'dist', 'extension-js', browserName, 'actions.ndjson')
|
|
939
|
+
}) : void 0;
|
|
940
|
+
const bridgeControlToken = allowControl && allowEval ? writeControlToken(packageJsonDir) : void 0;
|
|
941
|
+
const bridgeBroker = new BridgeBroker({
|
|
942
|
+
instanceId: currentInstance.instanceId,
|
|
943
|
+
runId: currentInstance.instanceId,
|
|
944
|
+
engine: browserName.includes('firefox') || browserName.includes('gecko') ? 'firefox' : 'chromium',
|
|
945
|
+
ring: new LogRingBuffer(),
|
|
946
|
+
file: bridgeLogFile,
|
|
947
|
+
allowControl,
|
|
948
|
+
allowEval,
|
|
949
|
+
controlToken: bridgeControlToken,
|
|
950
|
+
actions: bridgeActionsFile,
|
|
951
|
+
authorMode
|
|
952
|
+
});
|
|
953
|
+
let bridgeControlPort = null;
|
|
954
|
+
try {
|
|
955
|
+
bridgeLogFile.start();
|
|
956
|
+
bridgeActionsFile?.start();
|
|
957
|
+
const controlServer = await startControlServer({
|
|
958
|
+
broker: bridgeBroker,
|
|
959
|
+
host: devServerHost
|
|
960
|
+
});
|
|
961
|
+
bridgeControlPort = controlServer.port;
|
|
962
|
+
} catch {
|
|
963
|
+
try {
|
|
964
|
+
bridgeLogFile.close();
|
|
965
|
+
} catch {}
|
|
966
|
+
}
|
|
967
|
+
process.env.EXTENSION_INSTANCE_ID = currentInstance.instanceId;
|
|
968
|
+
process.env.EXTENSION_CONTROL_PORT = null != bridgeControlPort ? String(bridgeControlPort) : '';
|
|
969
|
+
process.once('exit', ()=>{
|
|
970
|
+
try {
|
|
971
|
+
bridgeLogFile.close();
|
|
972
|
+
} catch {}
|
|
973
|
+
try {
|
|
974
|
+
bridgeActionsFile?.close();
|
|
975
|
+
} catch {}
|
|
976
|
+
if (bridgeControlToken) clearControlToken(packageJsonDir);
|
|
977
|
+
});
|
|
386
978
|
const safeBrowserConfig = sanitize(browserConfig);
|
|
387
979
|
const safeCommandConfig = sanitize(commandConfig);
|
|
388
980
|
const safeDevOptions = sanitize(devOptions);
|
|
@@ -401,6 +993,9 @@ async function dev_server_devServer(projectStructure, devOptions) {
|
|
|
401
993
|
browser: devOptions.browser,
|
|
402
994
|
mode: 'development',
|
|
403
995
|
instanceId: currentInstance.instanceId,
|
|
996
|
+
controlPort: bridgeControlPort,
|
|
997
|
+
controlPath: CONTROL_WS_PATH,
|
|
998
|
+
logsPath: bridgeLogsRelPath,
|
|
404
999
|
port: portAllocation.port,
|
|
405
1000
|
output: {
|
|
406
1001
|
clean: false,
|
|
@@ -420,7 +1015,11 @@ async function dev_server_devServer(projectStructure, devOptions) {
|
|
|
420
1015
|
command: 'dev',
|
|
421
1016
|
distPath: __rspack_external_path.join(packageJsonDir, 'dist', String(devOptions.browser || 'chromium')),
|
|
422
1017
|
manifestPath,
|
|
423
|
-
port
|
|
1018
|
+
port,
|
|
1019
|
+
instanceId: currentInstance.instanceId,
|
|
1020
|
+
controlPort: bridgeControlPort,
|
|
1021
|
+
controlPath: CONTROL_WS_PATH,
|
|
1022
|
+
logsPath: bridgeLogsRelPath
|
|
424
1023
|
});
|
|
425
1024
|
setupCompilerLifecycleHooks(compiler);
|
|
426
1025
|
if (devOptions.noBrowser) setupNoBrowserBannerOnFirstDone({
|
|
@@ -456,8 +1055,10 @@ async function dev_server_devServer(projectStructure, devOptions) {
|
|
|
456
1055
|
__rspack_external_path.join(packageJsonDir, 'dist', '**/*')
|
|
457
1056
|
],
|
|
458
1057
|
ignoreInitial: true,
|
|
459
|
-
|
|
460
|
-
|
|
1058
|
+
...'true' === process.env.EXTENSION_WATCH_POLL ? {
|
|
1059
|
+
usePolling: true,
|
|
1060
|
+
interval: parseInt(String(process.env.EXTENSION_WATCH_POLL_INTERVAL || '1000'), 10)
|
|
1061
|
+
} : {}
|
|
461
1062
|
}
|
|
462
1063
|
},
|
|
463
1064
|
client: false,
|