personal-agent-benchmark 0.0.0-stage → 0.2.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.
Files changed (81) hide show
  1. package/README.md +128 -2
  2. package/bin/pab.js +5 -0
  3. package/dist/src/browser.d.ts +29 -0
  4. package/dist/src/browser.js +37 -0
  5. package/dist/src/browser.js.map +1 -0
  6. package/dist/src/cli.d.ts +20 -0
  7. package/dist/src/cli.js +331 -0
  8. package/dist/src/cli.js.map +1 -0
  9. package/dist/src/core/archive.d.ts +49 -0
  10. package/dist/src/core/archive.js +213 -0
  11. package/dist/src/core/archive.js.map +1 -0
  12. package/dist/src/core/audit.d.ts +15 -0
  13. package/dist/src/core/audit.js +85 -0
  14. package/dist/src/core/audit.js.map +1 -0
  15. package/dist/src/core/bundle.d.ts +42 -0
  16. package/dist/src/core/bundle.js +80 -0
  17. package/dist/src/core/bundle.js.map +1 -0
  18. package/dist/src/core/lookup.d.ts +15 -0
  19. package/dist/src/core/lookup.js +82 -0
  20. package/dist/src/core/lookup.js.map +1 -0
  21. package/dist/src/core/normalize.d.ts +45 -0
  22. package/dist/src/core/normalize.js +143 -0
  23. package/dist/src/core/normalize.js.map +1 -0
  24. package/dist/src/core/rules.d.ts +38 -0
  25. package/dist/src/core/rules.js +187 -0
  26. package/dist/src/core/rules.js.map +1 -0
  27. package/dist/src/core/store.d.ts +26 -0
  28. package/dist/src/core/store.js +78 -0
  29. package/dist/src/core/store.js.map +1 -0
  30. package/dist/src/core/types.d.ts +125 -0
  31. package/dist/src/core/types.js +3 -0
  32. package/dist/src/core/types.js.map +1 -0
  33. package/dist/src/core/util.d.ts +21 -0
  34. package/dist/src/core/util.js +150 -0
  35. package/dist/src/core/util.js.map +1 -0
  36. package/dist/src/core/warc.d.ts +55 -0
  37. package/dist/src/core/warc.js +258 -0
  38. package/dist/src/core/warc.js.map +1 -0
  39. package/dist/src/core/writer.d.ts +26 -0
  40. package/dist/src/core/writer.js +119 -0
  41. package/dist/src/core/writer.js.map +1 -0
  42. package/dist/src/grading.d.ts +63 -0
  43. package/dist/src/grading.js +173 -0
  44. package/dist/src/grading.js.map +1 -0
  45. package/dist/src/judge.d.ts +25 -0
  46. package/dist/src/judge.js +119 -0
  47. package/dist/src/judge.js.map +1 -0
  48. package/dist/src/paths.d.ts +2 -0
  49. package/dist/src/paths.js +40 -0
  50. package/dist/src/paths.js.map +1 -0
  51. package/dist/src/replay/clock.d.ts +19 -0
  52. package/dist/src/replay/clock.js +60 -0
  53. package/dist/src/replay/clock.js.map +1 -0
  54. package/dist/src/replay/engine.d.ts +53 -0
  55. package/dist/src/replay/engine.js +280 -0
  56. package/dist/src/replay/engine.js.map +1 -0
  57. package/dist/src/replay/launcher.d.ts +36 -0
  58. package/dist/src/replay/launcher.js +163 -0
  59. package/dist/src/replay/launcher.js.map +1 -0
  60. package/dist/src/replay/whistle-plugin.d.ts +24 -0
  61. package/dist/src/replay/whistle-plugin.js +82 -0
  62. package/dist/src/replay/whistle-plugin.js.map +1 -0
  63. package/dist/src/report.d.ts +3 -0
  64. package/dist/src/report.js +143 -0
  65. package/dist/src/report.js.map +1 -0
  66. package/dist/src/scoring.d.ts +87 -0
  67. package/dist/src/scoring.js +232 -0
  68. package/dist/src/scoring.js.map +1 -0
  69. package/dist/src/tasks.d.ts +61 -0
  70. package/dist/src/tasks.js +152 -0
  71. package/dist/src/tasks.js.map +1 -0
  72. package/docs/bundle-format.md +114 -0
  73. package/docs/deploy.md +78 -0
  74. package/docs/grading.md +81 -0
  75. package/docs/site-rules.md +114 -0
  76. package/docs/tasks.md +63 -0
  77. package/package.json +121 -4
  78. package/plugins/whistle.bench-replay/index.js +1 -0
  79. package/plugins/whistle.bench-replay/package.json +7 -0
  80. package/sites/_default.yaml +80 -0
  81. package/sites/amazon.com.yaml +125 -0
@@ -0,0 +1,280 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ReplayEngine = exports.CONTROL_HOST = void 0;
37
+ exports.classify = classify;
38
+ const fs = __importStar(require("node:fs"));
39
+ const path = __importStar(require("node:path"));
40
+ const bundle_1 = require("../core/bundle");
41
+ const lookup_1 = require("../core/lookup");
42
+ const normalize_1 = require("../core/normalize");
43
+ const rules_1 = require("../core/rules");
44
+ const store_1 = require("../core/store");
45
+ const util_1 = require("../core/util");
46
+ const clock_1 = require("./clock");
47
+ exports.CONTROL_HOST = 'rr.control';
48
+ const STATIC_DESTS = new Set(['image', 'font', 'style', 'script', 'audio', 'video', 'track', 'manifest', 'object', 'embed', 'worker', 'sharedworker', 'serviceworker', 'paintworklet', 'audioworklet']);
49
+ const DROP_RESPONSE_HEADERS = ['content-encoding', 'transfer-encoding', 'content-length', 'alt-svc', 'connection', 'keep-alive'];
50
+ /**
51
+ * Answers every request from a recording bundle. It never opens a network connection:
52
+ * misses are answered locally and logged.
53
+ */
54
+ class ReplayEngine {
55
+ bundle;
56
+ rules;
57
+ store;
58
+ manifest;
59
+ stats = emptyStats();
60
+ logFile;
61
+ clockMs;
62
+ constructor(bundleDir, opts = {}) {
63
+ this.bundle = new bundle_1.Bundle(path.resolve(bundleDir));
64
+ if (!this.bundle.isPacked())
65
+ throw new Error(`not a recording bundle: ${bundleDir}`);
66
+ this.manifest = this.bundle.manifest();
67
+ this.rules = rules_1.RuleSet.load([this.bundle.rulesDir, ...(opts.rulesDirs ?? [])]);
68
+ this.store = new store_1.RecordStore(this.bundle, this.rules);
69
+ this.logFile = opts.logFile;
70
+ if ((opts.clock ?? 'recorded') === 'recorded')
71
+ this.clockMs = Date.parse(this.manifest.captured_from);
72
+ if (this.logFile)
73
+ fs.mkdirSync(path.dirname(this.logFile), { recursive: true });
74
+ }
75
+ getStats() {
76
+ return JSON.parse(JSON.stringify(this.stats));
77
+ }
78
+ reset() {
79
+ this.stats = emptyStats();
80
+ }
81
+ async handle(req) {
82
+ const started = Date.now();
83
+ const url = new URL(req.url);
84
+ if (url.hostname === exports.CONTROL_HOST)
85
+ return this.control(url);
86
+ const method = req.method.toUpperCase();
87
+ const input = {
88
+ method: method === 'HEAD' ? 'GET' : method,
89
+ url: req.url,
90
+ contentType: (0, util_1.headerFromRecord)(req.headers, 'content-type'),
91
+ body: req.body,
92
+ };
93
+ const norm = (0, normalize_1.normalizeRequest)(input, this.rules);
94
+ const resourceClass = classify(req, norm);
95
+ let result;
96
+ try {
97
+ result = await this.resolve(req, input, norm, resourceClass);
98
+ }
99
+ catch (err) {
100
+ result = { resp: this.missResponse(resourceClass, norm, req), source: 'miss', detail: `error: ${err.message}` };
101
+ }
102
+ let resp = result.resp;
103
+ if (this.clockMs !== undefined && resourceClass === 'document' && (0, util_1.mimeOf)(resp.headers) === 'text/html' && method !== 'HEAD') {
104
+ const shifted = (0, clock_1.injectClock)(resp.body.toString('utf8'), resp.headers, this.clockMs);
105
+ resp = { ...resp, headers: shifted.headers, body: Buffer.from(shifted.html, 'utf8') };
106
+ }
107
+ const body = method === 'HEAD' ? Buffer.alloc(0) : resp.body;
108
+ const headers = (0, util_1.setHeader)((0, util_1.withoutHeaders)(resp.headers, DROP_RESPONSE_HEADERS), 'Content-Length', String(resp.body.length));
109
+ const out = {
110
+ status: resp.status,
111
+ statusText: resp.statusText,
112
+ headers,
113
+ body,
114
+ source: result.source,
115
+ key: norm.key,
116
+ kind: norm.kind,
117
+ resourceClass,
118
+ entryId: result.entryId,
119
+ detail: result.detail,
120
+ };
121
+ this.record(req, out, Date.now() - started);
122
+ return out;
123
+ }
124
+ async resolve(req, input, norm, resourceClass) {
125
+ if (norm.kind === 'block')
126
+ return { resp: empty(204), source: 'blocked' };
127
+ const found = (0, lookup_1.lookup)(this.store, norm);
128
+ if (found)
129
+ return { resp: await this.store.response(found.ix), source: found.source, entryId: found.ix.entry.id, detail: found.detail };
130
+ if (input.method === 'OPTIONS')
131
+ return { resp: preflight(req), source: 'preflight' };
132
+ return { resp: this.missResponse(resourceClass, norm, req), source: 'miss' };
133
+ }
134
+ missResponse(resourceClass, norm, req) {
135
+ if (resourceClass === 'tracker')
136
+ return empty(204);
137
+ if (resourceClass === 'static')
138
+ return { status: 404, statusText: 'Not Found', headers: [['Content-Type', 'text/plain']], body: Buffer.alloc(0) };
139
+ if (resourceClass === 'document') {
140
+ const notFound = this.notFoundPage(norm);
141
+ if (notFound)
142
+ return notFound;
143
+ const html = '<!doctype html><html><head><title>Page Not Found</title></head><body><h1>Page Not Found</h1></body></html>';
144
+ return { status: 404, statusText: 'Not Found', headers: [['Content-Type', 'text/html; charset=utf-8']], body: Buffer.from(html) };
145
+ }
146
+ const custom = norm.site?.xhr_not_found;
147
+ if (custom)
148
+ return { status: 404, statusText: 'Not Found', headers: [['Content-Type', custom.content_type]], body: Buffer.from(custom.body) };
149
+ const wantsJson = ((0, util_1.headerFromRecord)(req.headers, 'accept') ?? '').includes('json');
150
+ return wantsJson ? jsonResponse(404, {}) : { status: 404, statusText: 'Not Found', headers: [['Content-Type', 'text/plain']], body: Buffer.alloc(0) };
151
+ }
152
+ cachedNotFound = new Map();
153
+ notFoundPage(norm) {
154
+ const site = norm.site?.site;
155
+ if (!site)
156
+ return undefined;
157
+ const cached = this.cachedNotFound.get(site);
158
+ if (cached !== undefined)
159
+ return cached ?? undefined;
160
+ return undefined;
161
+ }
162
+ /** Loads each site's recorded 404 page once; call before serving. */
163
+ async warmUp() {
164
+ for (const [site, url] of Object.entries(this.manifest.not_found ?? {})) {
165
+ const ix = this.store.findByUrl(url);
166
+ if (!ix) {
167
+ this.cachedNotFound.set(site, null);
168
+ continue;
169
+ }
170
+ const resp = await this.store.response(ix);
171
+ this.cachedNotFound.set(site, { ...resp, status: 404, statusText: 'Not Found' });
172
+ }
173
+ }
174
+ control(url) {
175
+ let body;
176
+ switch (url.pathname) {
177
+ case '/health':
178
+ body = { ok: true, task: this.manifest.task_id, entries: this.store.all.length, captured_from: this.manifest.captured_from };
179
+ break;
180
+ case '/stats':
181
+ body = this.getStats();
182
+ break;
183
+ case '/reset':
184
+ this.reset();
185
+ body = { ok: true };
186
+ break;
187
+ default:
188
+ body = { error: 'unknown control path' };
189
+ }
190
+ const resp = jsonResponse(200, body);
191
+ return { ...resp, source: 'control', key: url.href, kind: 'browse', resourceClass: 'xhr' };
192
+ }
193
+ record(req, out, ms) {
194
+ const s = this.stats;
195
+ s.requests++;
196
+ s.bySource[out.source] = (s.bySource[out.source] ?? 0) + 1;
197
+ const c = s.byClass[out.resourceClass];
198
+ c.total++;
199
+ if (out.source === 'miss') {
200
+ c.miss++;
201
+ const m = (s.misses[out.key] ??= { count: 0, resourceClass: out.resourceClass, url: req.url });
202
+ m.count++;
203
+ }
204
+ else {
205
+ c.hit++;
206
+ }
207
+ if (this.logFile) {
208
+ const line = {
209
+ t: new Date().toISOString(),
210
+ method: req.method,
211
+ url: req.url,
212
+ key: out.key,
213
+ kind: out.kind,
214
+ class: out.resourceClass,
215
+ // what the browser loads it as: "document" is a page in a tab, "iframe" a frame inside one
216
+ dest: (0, util_1.headerFromRecord)(req.headers, 'sec-fetch-dest')?.toLowerCase(),
217
+ source: out.source,
218
+ status: out.status,
219
+ entry: out.entryId,
220
+ detail: out.detail,
221
+ ms,
222
+ // a missed request in full, so a rule fix can be worked out from the log
223
+ ...(out.source === 'miss' && req.body?.length ? { contentType: (0, util_1.headerFromRecord)(req.headers, 'content-type'), body: req.body.toString('base64') } : {}),
224
+ };
225
+ fs.appendFileSync(this.logFile, JSON.stringify(line) + '\n');
226
+ }
227
+ }
228
+ }
229
+ exports.ReplayEngine = ReplayEngine;
230
+ function classify(req, norm) {
231
+ if (norm.kind === 'block')
232
+ return 'tracker';
233
+ const dest = ((0, util_1.headerFromRecord)(req.headers, 'sec-fetch-dest') ?? '').toLowerCase();
234
+ if (dest === 'document' || dest === 'iframe' || dest === 'frame')
235
+ return 'document';
236
+ if (STATIC_DESTS.has(dest))
237
+ return 'static';
238
+ if (dest === 'empty')
239
+ return 'xhr';
240
+ if (norm.kind === 'static')
241
+ return 'static';
242
+ const accept = (0, util_1.headerFromRecord)(req.headers, 'accept') ?? '';
243
+ if (req.method.toUpperCase() === 'GET' && accept.includes('text/html'))
244
+ return 'document';
245
+ return 'xhr';
246
+ }
247
+ function emptyStats() {
248
+ return {
249
+ requests: 0,
250
+ bySource: {},
251
+ byClass: {
252
+ document: { total: 0, hit: 0, miss: 0 },
253
+ xhr: { total: 0, hit: 0, miss: 0 },
254
+ static: { total: 0, hit: 0, miss: 0 },
255
+ tracker: { total: 0, hit: 0, miss: 0 },
256
+ },
257
+ misses: {},
258
+ };
259
+ }
260
+ function empty(status) {
261
+ return { status, statusText: '', headers: [], body: Buffer.alloc(0) };
262
+ }
263
+ function jsonResponse(status, value) {
264
+ return { status, statusText: '', headers: [['Content-Type', 'application/json; charset=utf-8'], ['Cache-Control', 'no-store']], body: Buffer.from(JSON.stringify(value)) };
265
+ }
266
+ function preflight(req) {
267
+ const origin = (0, util_1.headerFromRecord)(req.headers, 'origin') ?? '*';
268
+ const headers = [
269
+ ['Access-Control-Allow-Origin', origin],
270
+ ['Access-Control-Allow-Credentials', 'true'],
271
+ ['Access-Control-Allow-Methods', (0, util_1.headerFromRecord)(req.headers, 'access-control-request-method') ?? 'GET, POST, PUT, DELETE, OPTIONS'],
272
+ ['Access-Control-Max-Age', '600'],
273
+ ['Vary', 'Origin'],
274
+ ];
275
+ const reqHeaders = (0, util_1.headerFromRecord)(req.headers, 'access-control-request-headers');
276
+ if (reqHeaders)
277
+ headers.push(['Access-Control-Allow-Headers', reqHeaders]);
278
+ return { status: 204, statusText: 'No Content', headers, body: Buffer.alloc(0) };
279
+ }
280
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/replay/engine.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,MAAY,EAAE,oCAAgB;AAC9B,MAAY,IAAI,sCAAkB;AAClC,2CAAwC;AACxC,2CAAwC;AACxC,iDAAgG;AAChG,yCAAwC;AACxC,yCAA4C;AAE5C,uCAAmF;AACnF,mCAAsC;AAEzB,QAAA,YAAY,GAAG,YAAY,CAAC;AAiBzC,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC;AACxM,MAAM,qBAAqB,GAAG,CAAC,kBAAkB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC;AAEjI;;;GAGG;AACH;IACW,MAAM,CAAS;IACf,KAAK,CAAU;IACf,KAAK,CAAc;IACnB,QAAQ,CAAW;IACpB,KAAK,GAAgB,UAAU,EAAE,CAAC;IACzB,OAAO,CAAU;IACjB,OAAO,CAAU;IAElC,YAAY,SAAiB,EAAE,IAAI,GAAkB,EAAE;QACrD,IAAI,CAAC,MAAM,GAAG,IAAI,eAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;QAClD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,SAAS,EAAE,CAAC,CAAC;QACrF,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACvC,IAAI,CAAC,KAAK,GAAG,eAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7E,IAAI,CAAC,KAAK,GAAG,IAAI,mBAAW,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACtD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,UAAU;YAAE,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;QACtG,IAAI,IAAI,CAAC,OAAO;YAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClF,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAgB,CAAC;IAC/D,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,GAAG,UAAU,EAAE,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAkB;QAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAA,YAAY;YAAE,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAE5D,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QACxC,MAAM,KAAK,GAAiB;YAC1B,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;YAC1C,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,WAAW,EAAE,IAAA,uBAAgB,EAAC,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC;YAC1D,IAAI,EAAE,GAAG,CAAC,IAAI;SACf,CAAC;QACF,MAAM,IAAI,GAAG,IAAA,4BAAgB,EAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACjD,MAAM,aAAa,GAAG,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAE1C,IAAI,MAAmF,CAAC;QACxF,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;QAC/D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,aAAa,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAW,GAAa,CAAC,OAAO,EAAE,EAAE,CAAC;QAC7H,CAAC;QAED,IAAI,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;QACvB,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,aAAa,KAAK,UAAU,IAAI,IAAA,aAAM,EAAC,IAAI,CAAC,OAAO,CAAC,KAAK,WAAW,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YAC5H,MAAM,OAAO,GAAG,IAAA,mBAAW,EAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;YACpF,IAAI,GAAG,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;QACxF,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAC7D,MAAM,OAAO,GAAG,IAAA,gBAAS,EAAC,IAAA,qBAAc,EAAC,IAAI,CAAC,OAAO,EAAE,qBAAqB,CAAC,EAAE,gBAAgB,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QAC3H,MAAM,GAAG,GAAmB;YAC1B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,OAAO;YACP,IAAI;YACJ,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,aAAa;YACb,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,MAAM,EAAE,MAAM,CAAC,MAAM;SACtB,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,CAAC;QAC5C,OAAO,GAAG,CAAC;IACb,CAAC;IAEO,KAAK,CAAC,OAAO,CACnB,GAAkB,EAClB,KAAmB,EACnB,IAAuB,EACvB,aAA4B;QAE5B,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;QAE1E,MAAM,KAAK,GAAG,IAAA,eAAM,EAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACvC,IAAI,KAAK;YAAE,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;QAExI,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;QACrF,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,aAAa,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC/E,CAAC;IAEO,YAAY,CAAC,aAA4B,EAAE,IAAuB,EAAE,GAAkB;QAC5F,IAAI,aAAa,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC;QACnD,IAAI,aAAa,KAAK,QAAQ;YAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAClJ,IAAI,aAAa,KAAK,UAAU,EAAE,CAAC;YACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;YACzC,IAAI,QAAQ;gBAAE,OAAO,QAAQ,CAAC;YAC9B,MAAM,IAAI,GAAG,4GAA4G,CAAC;YAC1H,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC,cAAc,EAAE,0BAA0B,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpI,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC;QACxC,IAAI,MAAM;YAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC,cAAc,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9I,MAAM,SAAS,GAAG,CAAC,IAAA,uBAAgB,EAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACnF,OAAO,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACxJ,CAAC;IAEO,cAAc,GAAG,IAAI,GAAG,EAAiC,CAAC;IAE1D,YAAY,CAAC,IAAuB;QAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC;QAC7B,IAAI,CAAC,IAAI;YAAE,OAAO,SAAS,CAAC;QAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,MAAM,IAAI,SAAS,CAAC;QACrD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,qEAAqE;IACrE,KAAK,CAAC,MAAM;QACV,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,IAAI,EAAE,CAAC,EAAE,CAAC;YACxE,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACrC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBACpC,SAAS;YACX,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC3C,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC,CAAC;QACnF,CAAC;IACH,CAAC;IAEO,OAAO,CAAC,GAAQ;QACtB,IAAI,IAAa,CAAC;QAClB,QAAQ,GAAG,CAAC,QAAQ,EAAE,CAAC;YACrB,KAAK,SAAS;gBACZ,IAAI,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;gBAC7H,MAAM;YACR,KAAK,QAAQ;gBACX,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACvB,MAAM;YACR,KAAK,QAAQ;gBACX,IAAI,CAAC,KAAK,EAAE,CAAC;gBACb,IAAI,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;gBACpB,MAAM;YACR;gBACE,IAAI,GAAG,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC;QAC7C,CAAC;QACD,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACrC,OAAO,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;IAC7F,CAAC;IAEO,MAAM,CAAC,GAAkB,EAAE,GAAmB,EAAE,EAAU;QAChE,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;QACrB,CAAC,CAAC,QAAQ,EAAE,CAAC;QACb,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAC3D,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACvC,CAAC,CAAC,KAAK,EAAE,CAAC;QACV,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC1B,CAAC,CAAC,IAAI,EAAE,CAAC;YACT,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,aAAa,EAAE,GAAG,CAAC,aAAa,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;YAC/F,CAAC,CAAC,KAAK,EAAE,CAAC;QACZ,CAAC;aAAM,CAAC;YACN,CAAC,CAAC,GAAG,EAAE,CAAC;QACV,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,IAAI,GAAG;gBACX,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBAC3B,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,GAAG,EAAE,GAAG,CAAC,GAAG;gBACZ,GAAG,EAAE,GAAG,CAAC,GAAG;gBACZ,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,KAAK,EAAE,GAAG,CAAC,aAAa;gBACxB,2FAA2F;gBAC3F,IAAI,EAAE,IAAA,uBAAgB,EAAC,GAAG,CAAC,OAAO,EAAE,gBAAgB,CAAC,EAAE,WAAW,EAAE;gBACpE,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,KAAK,EAAE,GAAG,CAAC,OAAO;gBAClB,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,EAAE;gBACF,yEAAyE;gBACzE,GAAG,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAA,uBAAgB,EAAC,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxJ,CAAC;YACF,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;CACF;;AAED,kBAAyB,GAAkB,EAAE,IAAuB;IAClE,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,SAAS,CAAC;IAC5C,MAAM,IAAI,GAAG,CAAC,IAAA,uBAAgB,EAAC,GAAG,CAAC,OAAO,EAAE,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACnF,IAAI,IAAI,KAAK,UAAU,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,UAAU,CAAC;IACpF,IAAI,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC5C,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IACnC,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC5C,MAAM,MAAM,GAAG,IAAA,uBAAgB,EAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC7D,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC;QAAE,OAAO,UAAU,CAAC;IAC1F,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,UAAU;IACjB,OAAO;QACL,QAAQ,EAAE,CAAC;QACX,QAAQ,EAAE,EAAE;QACZ,OAAO,EAAE;YACP,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE;YACvC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE;YAClC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE;YACrC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE;SACvC;QACD,MAAM,EAAE,EAAE;KACX,CAAC;AACJ,CAAC;AAED,SAAS,KAAK,CAAC,MAAc;IAC3B,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACxE,CAAC;AAED,SAAS,YAAY,CAAC,MAAc,EAAE,KAAc;IAClD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,cAAc,EAAE,iCAAiC,CAAC,EAAE,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;AAC7K,CAAC;AAED,SAAS,SAAS,CAAC,GAAkB;IACnC,MAAM,MAAM,GAAG,IAAA,uBAAgB,EAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,GAAG,CAAC;IAC9D,MAAM,OAAO,GAAuB;QAClC,CAAC,6BAA6B,EAAE,MAAM,CAAC;QACvC,CAAC,kCAAkC,EAAE,MAAM,CAAC;QAC5C,CAAC,8BAA8B,EAAE,IAAA,uBAAgB,EAAC,GAAG,CAAC,OAAO,EAAE,+BAA+B,CAAC,IAAI,iCAAiC,CAAC;QACrI,CAAC,wBAAwB,EAAE,KAAK,CAAC;QACjC,CAAC,MAAM,EAAE,QAAQ,CAAC;KACnB,CAAC;IACF,MAAM,UAAU,GAAG,IAAA,uBAAgB,EAAC,GAAG,CAAC,OAAO,EAAE,gCAAgC,CAAC,CAAC;IACnF,IAAI,UAAU;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,8BAA8B,EAAE,UAAU,CAAC,CAAC,CAAC;IAC3E,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACnF,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { type ChildProcess } from 'node:child_process';
2
+ import * as http from 'node:http';
3
+ export interface ReplayServerOptions {
4
+ bundleDir: string;
5
+ port: number;
6
+ host?: string;
7
+ logFile?: string;
8
+ /** Rule directories layered over the bundle's own rules/ (later wins). */
9
+ rulesDirs?: string[];
10
+ /** whistle's data directory parent; defaults to a directory under the system temp dir. */
11
+ stateDir?: string;
12
+ }
13
+ export interface ReplayServer {
14
+ port: number;
15
+ proxy: string;
16
+ logFile?: string;
17
+ whistleLog: string;
18
+ child: ChildProcess;
19
+ control<T = unknown>(pathname: string): Promise<T>;
20
+ stop(): Promise<void>;
21
+ }
22
+ /** The single whistle rule: decrypt HTTPS and hand every request to the replay plugin. */
23
+ export declare const REPLAY_RULES = "/./ enable://capture bench-replay://\n";
24
+ /**
25
+ * Starts an isolated whistle instance (own data dir, no global plugins) with the replay plugin.
26
+ * Its DNS points at a dead address and proxy variables are removed, so a request that somehow
27
+ * bypassed the plugin could not reach the internet either.
28
+ */
29
+ export declare function startReplayServer(o: ReplayServerOptions): Promise<ReplayServer>;
30
+ /** GET a URL through the proxy and parse JSON. */
31
+ export declare function proxiedJson<T>(port: number, url: string): Promise<T>;
32
+ export declare function proxiedRequest(port: number, url: string, headers?: Record<string, string>): Promise<{
33
+ status: number;
34
+ headers: http.IncomingHttpHeaders;
35
+ body: Buffer;
36
+ }>;
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.REPLAY_RULES = void 0;
37
+ exports.startReplayServer = startReplayServer;
38
+ exports.proxiedJson = proxiedJson;
39
+ exports.proxiedRequest = proxiedRequest;
40
+ const node_child_process_1 = require("node:child_process");
41
+ const node_crypto_1 = require("node:crypto");
42
+ const fs = __importStar(require("node:fs"));
43
+ const http = __importStar(require("node:http"));
44
+ const os = __importStar(require("node:os"));
45
+ const path = __importStar(require("node:path"));
46
+ const paths_1 = require("../paths");
47
+ const engine_1 = require("./engine");
48
+ /** The single whistle rule: decrypt HTTPS and hand every request to the replay plugin. */
49
+ exports.REPLAY_RULES = '/./ enable://capture bench-replay://\n';
50
+ /**
51
+ * Starts an isolated whistle instance (own data dir, no global plugins) with the replay plugin.
52
+ * Its DNS points at a dead address and proxy variables are removed, so a request that somehow
53
+ * bypassed the plugin could not reach the internet either.
54
+ */
55
+ async function startReplayServer(o) {
56
+ const baseDir = path.join(o.stateDir ?? path.join(os.tmpdir(), 'pab'), 'whistle', String(o.port));
57
+ fs.rmSync(baseDir, { recursive: true, force: true });
58
+ fs.mkdirSync(baseDir, { recursive: true });
59
+ const rulesFile = path.join(baseDir, 'replay.rules');
60
+ fs.writeFileSync(rulesFile, exports.REPLAY_RULES);
61
+ const whistleLog = path.join(baseDir, 'whistle.log');
62
+ const out = fs.openSync(whistleLog, 'a');
63
+ const env = { ...process.env, RR_BUNDLE: path.resolve(o.bundleDir), RR_LOG: o.logFile ? path.resolve(o.logFile) : '', RR_RULES: (o.rulesDirs ?? []).map((d) => path.resolve(d)).join(':'),
64
+ // inside an Electron app process.execPath is the app itself; this makes it behave as Node
65
+ ELECTRON_RUN_AS_NODE: '1',
66
+ // `whistle run` starts its server (STARTING_) and its plugins (PFORK_) with these binaries;
67
+ // unset, it spawns whatever "node" is on PATH, and an app opened from Finder has none
68
+ STARTING_EXEC_PATH: nodeBinary(),
69
+ PFORK_EXEC_PATH: nodeBinary(),
70
+ };
71
+ for (const k of ['HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'http_proxy', 'https_proxy', 'all_proxy'])
72
+ delete env[k];
73
+ if (o.logFile) {
74
+ fs.mkdirSync(path.dirname(o.logFile), { recursive: true });
75
+ fs.rmSync(o.logFile, { force: true });
76
+ }
77
+ const whistleBin = require.resolve('whistle/bin/whistle.js');
78
+ const args = [
79
+ whistleBin,
80
+ 'run',
81
+ '-p', String(o.port),
82
+ '-H', o.host ?? '127.0.0.1',
83
+ '-D', baseDir,
84
+ '-A', path.join(paths_1.PACKAGE_ROOT, 'plugins'),
85
+ '--no-global-plugins',
86
+ '--no-prev-options',
87
+ '-r', rulesFile,
88
+ '-n', 'rr',
89
+ '-w', (0, node_crypto_1.randomBytes)(12).toString('hex'),
90
+ '--dnsServer', '127.0.0.1:9',
91
+ ];
92
+ // whistle's `run` forks its real server; a separate process group lets stop() take both down.
93
+ const child = (0, node_child_process_1.spawn)(nodeBinary(), args, { env, stdio: ['ignore', out, out], detached: true });
94
+ const killGroup = (signal) => {
95
+ try {
96
+ if (child.pid)
97
+ process.kill(-child.pid, signal);
98
+ }
99
+ catch {
100
+ // already gone
101
+ }
102
+ };
103
+ const control = (pathname) => proxiedJson(o.port, `http://${engine_1.CONTROL_HOST}${pathname}`);
104
+ const deadline = Date.now() + 60_000;
105
+ for (;;) {
106
+ if (child.exitCode !== null)
107
+ throw new Error(`whistle exited (${child.exitCode}); see ${whistleLog}`);
108
+ try {
109
+ const health = await control('/health');
110
+ if (health.ok)
111
+ break;
112
+ }
113
+ catch {
114
+ // not up yet
115
+ }
116
+ if (Date.now() > deadline) {
117
+ killGroup('SIGTERM');
118
+ throw new Error(`replay server did not become healthy; see ${whistleLog}`);
119
+ }
120
+ await new Promise((r) => setTimeout(r, 500));
121
+ }
122
+ return {
123
+ port: o.port,
124
+ proxy: `http://127.0.0.1:${o.port}`,
125
+ logFile: o.logFile,
126
+ whistleLog,
127
+ child,
128
+ control,
129
+ stop: () => new Promise((resolve) => {
130
+ const done = () => {
131
+ killGroup('SIGTERM');
132
+ resolve();
133
+ };
134
+ if (child.exitCode !== null)
135
+ return done();
136
+ child.once('exit', done);
137
+ killGroup('SIGTERM');
138
+ setTimeout(() => killGroup('SIGKILL'), 5000).unref();
139
+ }),
140
+ };
141
+ }
142
+ /** The Node binary for whistle: RR_NODE when this process is not one (an Electron helper), else this one. */
143
+ function nodeBinary() {
144
+ return process.env.RR_NODE || process.execPath;
145
+ }
146
+ /** GET a URL through the proxy and parse JSON. */
147
+ function proxiedJson(port, url) {
148
+ return proxiedRequest(port, url).then((r) => JSON.parse(r.body.toString('utf8')));
149
+ }
150
+ function proxiedRequest(port, url, headers = {}) {
151
+ return new Promise((resolve, reject) => {
152
+ const u = new URL(url);
153
+ const req = http.request({ host: '127.0.0.1', port, method: 'GET', path: url, headers: { Host: u.host, ...headers }, timeout: 5000 }, (res) => {
154
+ const chunks = [];
155
+ res.on('data', (c) => chunks.push(c));
156
+ res.on('end', () => resolve({ status: res.statusCode ?? 0, headers: res.headers, body: Buffer.concat(chunks) }));
157
+ });
158
+ req.on('timeout', () => req.destroy(new Error('timeout')));
159
+ req.on('error', reject);
160
+ req.end();
161
+ });
162
+ }
163
+ //# sourceMappingURL=launcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher.js","sourceRoot":"","sources":["../../../src/replay/launcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { type IncomingMessage, type Server, type ServerResponse } from 'node:http';
2
+ /**
3
+ * whistle plugin entry (whistle.bench-replay). The only whistle rule is
4
+ * `/./ enable://capture bench-replay://`, so every request arrives here and is
5
+ * answered from the bundle; nothing is forwarded upstream.
6
+ *
7
+ * Configuration comes from the environment of the whistle process:
8
+ * RR_BUNDLE bundle directory (required)
9
+ * RR_LOG JSONL request log (optional)
10
+ * RR_RULES extra rule directories, separated by ":" (optional)
11
+ * RR_CLOCK "off" to leave page clocks alone (default: shift them to the recording time)
12
+ */
13
+ interface WhistleRequest extends IncomingMessage {
14
+ originalReq?: {
15
+ fullUrl?: string;
16
+ url?: string;
17
+ method?: string;
18
+ headers?: Record<string, string | string[]>;
19
+ };
20
+ fullUrl?: string;
21
+ }
22
+ export declare function handleWhistleRequest(req: WhistleRequest, res: ServerResponse): Promise<void>;
23
+ export declare function server(srv: Server): void;
24
+ export {};
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleWhistleRequest = handleWhistleRequest;
4
+ exports.server = server;
5
+ const node_http_1 = require("node:http");
6
+ const engine_1 = require("./engine");
7
+ let enginePromise;
8
+ function engine() {
9
+ if (!enginePromise) {
10
+ enginePromise = (async () => {
11
+ const bundle = process.env.RR_BUNDLE;
12
+ if (!bundle)
13
+ throw new Error('RR_BUNDLE is not set for the whistle process');
14
+ const e = new engine_1.ReplayEngine(bundle, {
15
+ logFile: process.env.RR_LOG || undefined,
16
+ rulesDirs: (process.env.RR_RULES ?? '').split(':').filter(Boolean),
17
+ clock: process.env.RR_CLOCK === 'off' ? 'off' : 'recorded',
18
+ });
19
+ await e.warmUp();
20
+ return e;
21
+ })();
22
+ }
23
+ return enginePromise;
24
+ }
25
+ function cleanHeaders(headers) {
26
+ const out = {};
27
+ for (const [k, v] of Object.entries(headers)) {
28
+ const lower = k.toLowerCase();
29
+ if (lower.startsWith('x-whistle') || lower.startsWith('x-forwarded-') || lower === 'proxy-connection')
30
+ continue;
31
+ out[k] = v;
32
+ }
33
+ return out;
34
+ }
35
+ /** Framing of the recorded connection; replay frames its own responses (Content-Length, no trailers). */
36
+ const FRAMING_HEADERS = new Set(['connection', 'keep-alive', 'proxy-connection', 'te', 'trailer', 'transfer-encoding', 'upgrade']);
37
+ /**
38
+ * Headers as Node sends them. Node refuses a whole response for one header it cannot send (a bad
39
+ * name, control characters, characters beyond Latin-1, a Trailer on a response with a length), so
40
+ * such a header is left out instead.
41
+ */
42
+ function toNodeHeaders(list) {
43
+ const out = {};
44
+ for (const [k, v] of list) {
45
+ if (FRAMING_HEADERS.has(k.toLowerCase()))
46
+ continue;
47
+ try {
48
+ (0, node_http_1.validateHeaderName)(k);
49
+ (0, node_http_1.validateHeaderValue)(k, v);
50
+ }
51
+ catch {
52
+ continue;
53
+ }
54
+ const prev = out[k];
55
+ out[k] = prev === undefined ? v : Array.isArray(prev) ? [...prev, v] : [prev, v];
56
+ }
57
+ return out;
58
+ }
59
+ async function handleWhistleRequest(req, res) {
60
+ const chunks = [];
61
+ for await (const chunk of req)
62
+ chunks.push(chunk);
63
+ const url = req.originalReq?.fullUrl ?? req.fullUrl ?? req.url ?? '';
64
+ const method = req.originalReq?.method ?? req.method ?? 'GET';
65
+ try {
66
+ const e = await engine();
67
+ const out = await e.handle({ method, url, headers: cleanHeaders(req.headers), body: chunks.length ? Buffer.concat(chunks) : undefined });
68
+ res.writeHead(out.status, out.statusText || undefined, toNodeHeaders(out.headers));
69
+ res.end(out.body);
70
+ }
71
+ catch (err) {
72
+ res.writeHead(502, { 'Content-Type': 'text/plain' });
73
+ res.end(`replay error: ${err.message}`);
74
+ }
75
+ }
76
+ function server(srv) {
77
+ void engine().catch((err) => console.error('[bench-replay]', err));
78
+ srv.on('request', (req, res) => {
79
+ void handleWhistleRequest(req, res);
80
+ });
81
+ }
82
+ //# sourceMappingURL=whistle-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whistle-plugin.js","sourceRoot":"","sources":["../../../src/replay/whistle-plugin.ts"],"names":[],"mappings":";;;;AAAA,yCAA4H;AAC5H,qCAAwC;AAkBxC,IAAI,aAAgD,CAAC;AAErD,SAAS,MAAM;IACb,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,aAAa,GAAG,CAAC,KAAK,IAAI,EAAE;YAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;YACrC,IAAI,CAAC,MAAM;gBAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;YAC7E,MAAM,CAAC,GAAG,IAAI,qBAAY,CAAC,MAAM,EAAE;gBACjC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,SAAS;gBACxC,SAAS,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;gBAClE,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU;aAC3D,CAAC,CAAC;YACH,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;YACjB,OAAO,CAAC,CAAC;QACX,CAAC,CAAC,EAAE,CAAC;IACP,CAAC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,SAAS,YAAY,CAAC,OAAmC;IACvD,MAAM,GAAG,GAAkD,EAAE,CAAC;IAC9D,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7C,MAAM,KAAK,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;QAC9B,IAAI,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,IAAI,KAAK,KAAK,kBAAkB;YAAE,SAAS;QAChH,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACb,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,yGAAyG;AACzG,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,IAAI,EAAE,SAAS,EAAE,mBAAmB,EAAE,SAAS,CAAC,CAAC,CAAC;AAEnI;;;;GAIG;AACH,SAAS,aAAa,CAAC,IAAwB;IAC7C,MAAM,GAAG,GAAsC,EAAE,CAAC;IAClD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QAC1B,IAAI,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YAAE,SAAS;QACnD,IAAI,CAAC;YACH,IAAA,8BAAkB,EAAC,CAAC,CAAC,CAAC;YACtB,IAAA,+BAAmB,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACpB,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACnF,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAEM,KAAK,+BAA+B,GAAmB,EAAE,GAAmB;IACjF,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG;QAAE,MAAM,CAAC,IAAI,CAAC,KAAe,CAAC,CAAC;IAC5D,MAAM,GAAG,GAAG,GAAG,CAAC,WAAW,EAAE,OAAO,IAAI,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC;IACrE,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC;IAC9D,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,MAAM,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;QACzI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,UAAU,IAAI,SAAS,EAAE,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;QACnF,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC;QACrD,GAAG,CAAC,GAAG,CAAC,iBAAkB,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IACrD,CAAC;AACH,CAAC;AAED,gBAAuB,GAAW;IAChC,KAAK,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC,CAAC;IACnE,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAmB,EAAE,GAAmB,EAAE,EAAE;QAC7D,KAAK,oBAAoB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { RunResult } from './scoring';
2
+ /** report.html: a self-contained page rendered from result.json. */
3
+ export declare function renderReport(r: RunResult): string;