@wenathlan/extension 1.1.40 → 1.1.42
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/README.md +6 -4
- package/dist/httpclient.d.ts +158 -0
- package/dist/httpclient.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1138 -6
- package/dist/index.js.map +4 -4
- package/dist/media.d.ts +96 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/memory.d.ts +55 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/policy.d.ts +37 -0
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +37 -4
- package/dist/protocol.d.ts.map +1 -1
- package/dist/types.d.ts +336 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +1832 -13
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/manifest.json +1 -1
- package/extension/dist/pagebridge.js +203 -3
- package/extension/dist/pagebridge.js.map +3 -3
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +22 -2
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sidepanel.html +1 -1
- package/extension/dist/sidepanel.js +400 -2
- package/extension/dist/sidepanel.js.map +3 -3
- package/extension/dist/style.css +4 -0
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -196,6 +196,594 @@ function annotationplanof(input) {
|
|
|
196
196
|
return plan;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
// httpclient.ts
|
|
200
|
+
var httpkinds = ["fetchurl", "parsejson", "parsehtml", "callrest", "callgraphql"];
|
|
201
|
+
var redirectstatuses = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
202
|
+
var bodilessmethods = /* @__PURE__ */ new Set(["GET", "HEAD"]);
|
|
203
|
+
function statusclassof(status) {
|
|
204
|
+
if (status >= 100 && status < 200) return "informational";
|
|
205
|
+
if (status >= 200 && status < 300) return "success";
|
|
206
|
+
if (status >= 300 && status < 400) return "redirect";
|
|
207
|
+
if (status >= 400 && status < 500) return "clienterror";
|
|
208
|
+
if (status >= 500 && status < 600) return "servererror";
|
|
209
|
+
return "unknown";
|
|
210
|
+
}
|
|
211
|
+
function templateurl(template, values) {
|
|
212
|
+
return template.replace(/\{([a-z0-9_]+)\}/gi, (whole, name) => values[name] === void 0 ? whole : String(values[name]));
|
|
213
|
+
}
|
|
214
|
+
function fetchrequestof(value) {
|
|
215
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
216
|
+
const options = value;
|
|
217
|
+
if (typeof options.url !== "string" || !options.url.trim()) return void 0;
|
|
218
|
+
const request = { url: options.url.trim() };
|
|
219
|
+
if (typeof options.method === "string" && options.method.trim()) request.method = options.method.trim().toUpperCase();
|
|
220
|
+
if (options.headers && typeof options.headers === "object" && !Array.isArray(options.headers)) {
|
|
221
|
+
const headers = {};
|
|
222
|
+
for (const [name, headervalue] of Object.entries(options.headers)) {
|
|
223
|
+
if (typeof headervalue === "string") headers[name] = headervalue;
|
|
224
|
+
}
|
|
225
|
+
request.headers = headers;
|
|
226
|
+
}
|
|
227
|
+
if (typeof options.body === "string") request.body = options.body;
|
|
228
|
+
if (options.mode === "cors" || options.mode === "no-cors" || options.mode === "same-origin") request.mode = options.mode;
|
|
229
|
+
return request;
|
|
230
|
+
}
|
|
231
|
+
function fetchoptionsof(value) {
|
|
232
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
233
|
+
const options = value;
|
|
234
|
+
const normalized = {};
|
|
235
|
+
if (typeof options.timeout === "number" && Number.isFinite(options.timeout)) normalized.timeout = options.timeout;
|
|
236
|
+
if (typeof options.retries === "number" && Number.isFinite(options.retries)) normalized.retries = options.retries;
|
|
237
|
+
if (typeof options.backoff === "number" && Number.isFinite(options.backoff)) normalized.backoff = options.backoff;
|
|
238
|
+
if (typeof options.follow === "number" && Number.isFinite(options.follow)) normalized.follow = options.follow;
|
|
239
|
+
return normalized;
|
|
240
|
+
}
|
|
241
|
+
function streamwindowof(value) {
|
|
242
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
243
|
+
const options = value;
|
|
244
|
+
const window = {};
|
|
245
|
+
if (typeof options.budget === "number" && Number.isFinite(options.budget)) window.budget = options.budget;
|
|
246
|
+
return window;
|
|
247
|
+
}
|
|
248
|
+
function jsonpathrulesof(value) {
|
|
249
|
+
if (!Array.isArray(value)) return [];
|
|
250
|
+
const rules = [];
|
|
251
|
+
for (const item of value) {
|
|
252
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
253
|
+
const entry = item;
|
|
254
|
+
if (typeof entry.name !== "string" || !entry.name.trim()) continue;
|
|
255
|
+
if (typeof entry.path !== "string" || !entry.path.trim()) continue;
|
|
256
|
+
const rule = { name: entry.name.trim(), path: entry.path.trim() };
|
|
257
|
+
if (entry.kind === "text" || entry.kind === "number" || entry.kind === "boolean" || entry.kind === "json") rule.kind = entry.kind;
|
|
258
|
+
if (entry.default !== void 0) rule.default = entry.default;
|
|
259
|
+
rules.push(rule);
|
|
260
|
+
}
|
|
261
|
+
return rules;
|
|
262
|
+
}
|
|
263
|
+
function htmlqueriesof(value) {
|
|
264
|
+
if (!Array.isArray(value)) return [];
|
|
265
|
+
const queries = [];
|
|
266
|
+
for (const item of value) {
|
|
267
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
268
|
+
const entry = item;
|
|
269
|
+
if (typeof entry.selector !== "string" || !entry.selector.trim()) continue;
|
|
270
|
+
const query = { selector: entry.selector.trim() };
|
|
271
|
+
if (typeof entry.attribute === "string" && entry.attribute.trim()) query.attribute = entry.attribute.trim();
|
|
272
|
+
if (entry.multi === true) query.multi = true;
|
|
273
|
+
queries.push(query);
|
|
274
|
+
}
|
|
275
|
+
return queries;
|
|
276
|
+
}
|
|
277
|
+
function graphqlrequestof(value) {
|
|
278
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
279
|
+
const options = value;
|
|
280
|
+
if (typeof options.query !== "string" || !options.query.trim()) return void 0;
|
|
281
|
+
if (options.operationkind !== "query" && options.operationkind !== "mutation") return void 0;
|
|
282
|
+
const request = { query: options.query, operationkind: options.operationkind };
|
|
283
|
+
if (options.variables && typeof options.variables === "object" && !Array.isArray(options.variables)) request.variables = options.variables;
|
|
284
|
+
if (typeof options.operationname === "string" && options.operationname.trim()) request.operationname = options.operationname.trim();
|
|
285
|
+
return request;
|
|
286
|
+
}
|
|
287
|
+
var realsleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, Math.max(0, milliseconds)));
|
|
288
|
+
async function sendfetch(input) {
|
|
289
|
+
const options = input.options ?? {};
|
|
290
|
+
const sleep = input.sleep ?? realsleep;
|
|
291
|
+
const now = input.now ?? Date.now;
|
|
292
|
+
const attempts = Math.max(1, Math.floor(options.retries ?? 0) + 1);
|
|
293
|
+
const backoff = options.backoff ?? 0;
|
|
294
|
+
const follow = options.follow ?? Number.POSITIVE_INFINITY;
|
|
295
|
+
let url = input.request.url;
|
|
296
|
+
let method = (input.request.method ?? "GET").toUpperCase();
|
|
297
|
+
let retries = 0;
|
|
298
|
+
let redirects = 0;
|
|
299
|
+
let lastreason = "";
|
|
300
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
301
|
+
let hops = 0;
|
|
302
|
+
const startedat = now();
|
|
303
|
+
let response;
|
|
304
|
+
try {
|
|
305
|
+
const init = { method, headers: { ...input.request.headers ?? {} }, ...input.request.body !== void 0 && !bodilessmethods.has(method) ? { body: input.request.body } : {}, ...input.request.mode !== void 0 ? { mode: input.request.mode } : {}, redirect: follow <= 0 ? "error" : "follow" };
|
|
306
|
+
const sent = input.transport(url, init);
|
|
307
|
+
if (options.timeout !== void 0 && Number.isFinite(options.timeout) && options.timeout >= 0) {
|
|
308
|
+
let timedout = false;
|
|
309
|
+
response = await Promise.race([sent, sleep(options.timeout).then(() => {
|
|
310
|
+
timedout = true;
|
|
311
|
+
return void 0;
|
|
312
|
+
})]).then((value) => value ?? (timedout ? (() => {
|
|
313
|
+
throw new Error(`The request timed out after ${options.timeout} milliseconds.`);
|
|
314
|
+
})() : value));
|
|
315
|
+
} else {
|
|
316
|
+
response = await sent;
|
|
317
|
+
}
|
|
318
|
+
while (response !== void 0 && redirectstatuses.has(response.status) && typeof response.location === "string" && response.location) {
|
|
319
|
+
hops += 1;
|
|
320
|
+
if (hops > follow) throw new Error(`The redirect chain exceeded the reviewed follow limit of ${follow}.`);
|
|
321
|
+
url = new URL(response.location, url).toString();
|
|
322
|
+
if (method === "POST" && [301, 302, 303].includes(response.status)) method = "GET";
|
|
323
|
+
response = await input.transport(url, { ...init, method });
|
|
324
|
+
}
|
|
325
|
+
redirects = hops;
|
|
326
|
+
} catch (error) {
|
|
327
|
+
lastreason = error instanceof Error ? error.message : String(error);
|
|
328
|
+
response = void 0;
|
|
329
|
+
}
|
|
330
|
+
if (response !== void 0) {
|
|
331
|
+
const body = response.body;
|
|
332
|
+
return { url, status: response.status, statusclass: statusclassof(response.status), headernames: Object.keys(response.headers), body, bytes: body.length, duration: now() - startedat, retries, redirects };
|
|
333
|
+
}
|
|
334
|
+
if (attempt < attempts) {
|
|
335
|
+
const wait = backoff * attempt;
|
|
336
|
+
if (wait > 0) await sleep(wait);
|
|
337
|
+
input.onretry?.(attempt, wait, lastreason);
|
|
338
|
+
retries = attempt;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
throw new Error(`The request failed after ${attempts} attempt${attempts === 1 ? "" : "s"} with ${retries} retr${retries === 1 ? "y" : "ies"}: ${lastreason}`);
|
|
342
|
+
}
|
|
343
|
+
async function readstream(input) {
|
|
344
|
+
const pull = typeof input.chunks === "function" ? input.chunks : /* @__PURE__ */ ((source) => {
|
|
345
|
+
let index = 0;
|
|
346
|
+
return async () => source[index++];
|
|
347
|
+
})(input.chunks);
|
|
348
|
+
let count = 0;
|
|
349
|
+
let total = 0;
|
|
350
|
+
for (; ; ) {
|
|
351
|
+
if (input.window.abort?.() === true) return { chunks: count, bytes: total, aborted: true, reason: "The reviewed abort flag stopped the stream." };
|
|
352
|
+
const chunk = await pull();
|
|
353
|
+
if (chunk === void 0) return { chunks: count, bytes: total, aborted: false };
|
|
354
|
+
const next = total + chunk.length;
|
|
355
|
+
if (input.window.budget !== void 0 && next > input.window.budget) return { chunks: count, bytes: total, aborted: true, reason: `The stream aborted at ${next} bytes past the reviewed byte budget of ${input.window.budget}.` };
|
|
356
|
+
total = next;
|
|
357
|
+
count += 1;
|
|
358
|
+
input.window.onchunk?.(chunk, total);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function pathstep(current, segment) {
|
|
362
|
+
if (Array.isArray(current) && /^\d+$/.test(segment)) return current[Number.parseInt(segment, 10)];
|
|
363
|
+
if (current && typeof current === "object" && !Array.isArray(current)) return current[segment];
|
|
364
|
+
return void 0;
|
|
365
|
+
}
|
|
366
|
+
function coerce(value, kind, fallback) {
|
|
367
|
+
if (value === void 0 || value === null) return { value: fallback, missing: true };
|
|
368
|
+
if (kind === "text") return { value: String(value), missing: false };
|
|
369
|
+
if (kind === "number") {
|
|
370
|
+
const numeric = typeof value === "number" ? value : Number(value);
|
|
371
|
+
return Number.isFinite(numeric) ? { value: numeric, missing: false } : { value: fallback, missing: true };
|
|
372
|
+
}
|
|
373
|
+
if (kind === "boolean") return { value: value === true || value === "true", missing: false };
|
|
374
|
+
return { value, missing: false };
|
|
375
|
+
}
|
|
376
|
+
function readpath(parsed, rules) {
|
|
377
|
+
const fields = [];
|
|
378
|
+
for (const rule of rules) {
|
|
379
|
+
const kind = rule.kind ?? "text";
|
|
380
|
+
let current = parsed;
|
|
381
|
+
let missing = false;
|
|
382
|
+
for (const segment of rule.path.split(".")) {
|
|
383
|
+
const next = pathstep(current, segment);
|
|
384
|
+
if (next === void 0) {
|
|
385
|
+
missing = true;
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
current = next;
|
|
389
|
+
}
|
|
390
|
+
if (missing) fields.push({ name: rule.name, path: rule.path, kind, ...rule.default !== void 0 ? { value: rule.default } : {}, missing: true });
|
|
391
|
+
else {
|
|
392
|
+
const resolved = coerce(current, kind, rule.default);
|
|
393
|
+
fields.push({ name: rule.name, path: rule.path, kind, ...resolved.value !== void 0 ? { value: resolved.value } : {}, ...resolved.missing ? { missing: true } : {} });
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return fields;
|
|
397
|
+
}
|
|
398
|
+
function defaultparse() {
|
|
399
|
+
const parser = globalThis.DOMParser;
|
|
400
|
+
if (!parser) throw new Error("parsehtml needs a domparser seam outside the page context.");
|
|
401
|
+
return (markup) => {
|
|
402
|
+
const document = new parser().parseFromString(markup, "text/html");
|
|
403
|
+
return { query: (selector) => Array.from(document.querySelectorAll(selector)).map((element) => ({ text: element.textContent ?? "", attributes: Object.fromEntries(Array.from(element.attributes).map((attribute) => [attribute.name, attribute.value])) })) };
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function parsehtmlbody(input) {
|
|
407
|
+
const parse = input.parse ?? defaultparse();
|
|
408
|
+
const document = parse(input.body);
|
|
409
|
+
return input.queries.map((query) => {
|
|
410
|
+
const matches = document.query(query.selector);
|
|
411
|
+
const chosen = query.multi === true ? matches : matches.slice(0, 1);
|
|
412
|
+
const values = chosen.map((match) => query.attribute !== void 0 ? match.attributes[query.attribute] ?? "" : match.text);
|
|
413
|
+
return { selector: query.selector, ...query.attribute !== void 0 ? { attribute: query.attribute } : {}, multi: query.multi === true, count: matches.length, values };
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
function payloadvalid(payload, schema) {
|
|
417
|
+
if (!schema) return { ok: false, errors: ["The typed endpoint call needs a reviewed payload schema before it runs."] };
|
|
418
|
+
const errors = [];
|
|
419
|
+
for (const field of schema.fields) {
|
|
420
|
+
const value = payload[field.name];
|
|
421
|
+
if (value === void 0 || value === null) {
|
|
422
|
+
if (field.required === true) errors.push(`The required field ${field.name} of kind ${field.kind} is missing.`);
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (field.kind === "string" && typeof value !== "string") errors.push(`The field ${field.name} must be a string.`);
|
|
426
|
+
if (field.kind === "number" && (typeof value !== "number" || !Number.isFinite(value))) errors.push(`The field ${field.name} must be a finite number.`);
|
|
427
|
+
if (field.kind === "boolean" && typeof value !== "boolean") errors.push(`The field ${field.name} must be a boolean.`);
|
|
428
|
+
}
|
|
429
|
+
return { ok: errors.length === 0, errors };
|
|
430
|
+
}
|
|
431
|
+
function payloadwithdefaults(payload, schema) {
|
|
432
|
+
if (!schema) return payload;
|
|
433
|
+
const merged = { ...payload };
|
|
434
|
+
for (const field of schema.fields) {
|
|
435
|
+
if (merged[field.name] === void 0 && field.default !== void 0) merged[field.name] = field.default;
|
|
436
|
+
}
|
|
437
|
+
return merged;
|
|
438
|
+
}
|
|
439
|
+
function errorsof(body) {
|
|
440
|
+
try {
|
|
441
|
+
const parsed = JSON.parse(body);
|
|
442
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return [body];
|
|
443
|
+
const record2 = parsed;
|
|
444
|
+
for (const key of ["errors", "messages", "error", "message"]) {
|
|
445
|
+
const value = record2[key];
|
|
446
|
+
if (Array.isArray(value)) return value.map((item) => typeof item === "string" ? item : item && typeof item === "object" && typeof item.message === "string" ? item.message : String(item));
|
|
447
|
+
if (typeof value === "string") return [value];
|
|
448
|
+
}
|
|
449
|
+
return [body];
|
|
450
|
+
} catch {
|
|
451
|
+
return [body];
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
async function callrest(input) {
|
|
455
|
+
const check = payloadvalid(input.payload, input.endpoint.schema);
|
|
456
|
+
if (!check.ok) throw new Error(check.errors.join(" "));
|
|
457
|
+
const payload = payloadwithdefaults(input.payload, input.endpoint.schema);
|
|
458
|
+
const url = templateurl(input.endpoint.url, payload);
|
|
459
|
+
const method = input.endpoint.method.toUpperCase();
|
|
460
|
+
const request = { url, method, ...input.endpoint.headers !== void 0 ? { headers: input.endpoint.headers } : {}, ...bodilessmethods.has(method) ? {} : { body: JSON.stringify(payload) } };
|
|
461
|
+
const transport = await sendfetch({ request, ...input.options !== void 0 ? { options: input.options } : {}, transport: input.transport, ...input.sleep !== void 0 ? { sleep: input.sleep } : {}, ...input.onretry !== void 0 ? { onretry: input.onretry } : {}, ...input.now !== void 0 ? { now: input.now } : {} });
|
|
462
|
+
const ok = input.success !== void 0 ? input.success.includes(transport.status) : transport.statusclass === "success";
|
|
463
|
+
return { transport, url, payload, ok, errors: ok ? [] : errorsof(transport.body) };
|
|
464
|
+
}
|
|
465
|
+
function graphqlopenvelope(request) {
|
|
466
|
+
return JSON.stringify({ query: request.query, ...request.variables !== void 0 ? { variables: request.variables } : {}, ...request.operationname !== void 0 ? { operationName: request.operationname } : {} });
|
|
467
|
+
}
|
|
468
|
+
function unwrapgraphql(value) {
|
|
469
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return { errors: ["The graphql response is not a json object."] };
|
|
470
|
+
const record2 = value;
|
|
471
|
+
const errors = Array.isArray(record2.errors) ? record2.errors.map((item) => typeof item === "string" ? item : item && typeof item === "object" && typeof item.message === "string" ? item.message : String(item)) : [];
|
|
472
|
+
return { ...record2.data !== void 0 ? { data: record2.data } : {}, errors };
|
|
473
|
+
}
|
|
474
|
+
async function callgraphql(input) {
|
|
475
|
+
const request = { url: input.endpoint.url, method: "POST", ...input.endpoint.headers !== void 0 ? { headers: input.endpoint.headers } : {}, body: graphqlopenvelope(input.request) };
|
|
476
|
+
const transport = await sendfetch({ request, ...input.options !== void 0 ? { options: input.options } : {}, transport: input.transport, ...input.sleep !== void 0 ? { sleep: input.sleep } : {}, ...input.onretry !== void 0 ? { onretry: input.onretry } : {}, ...input.now !== void 0 ? { now: input.now } : {} });
|
|
477
|
+
try {
|
|
478
|
+
const unwrapped = unwrapgraphql(JSON.parse(transport.body));
|
|
479
|
+
return { transport, ...unwrapped.data !== void 0 ? { data: unwrapped.data } : {}, errors: unwrapped.errors };
|
|
480
|
+
} catch {
|
|
481
|
+
return { transport, errors: [transport.body] };
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// media.ts
|
|
486
|
+
var mediakinds = ["capturepdf", "recordscreen", "captureaudio", "captureframe", "downloadimages", "shotcanvas", "probestream", "readmedia", "readassets", "timelapse", "convertimage", "makethumbs"];
|
|
487
|
+
var defaultpaperwidth = 8.5;
|
|
488
|
+
var defaultpaperheight = 11;
|
|
489
|
+
var defaultmargins = { top: 0.4, right: 0.4, bottom: 0.4, left: 0.4 };
|
|
490
|
+
var pdfpointsperinch = 72;
|
|
491
|
+
var basefontsize = 11;
|
|
492
|
+
function pdfoptionsof(value) {
|
|
493
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
494
|
+
const options = value;
|
|
495
|
+
const normalized = {};
|
|
496
|
+
if (typeof options.paperwidth === "number" && Number.isFinite(options.paperwidth)) normalized.paperwidth = options.paperwidth;
|
|
497
|
+
if (typeof options.paperheight === "number" && Number.isFinite(options.paperheight)) normalized.paperheight = options.paperheight;
|
|
498
|
+
if (options.margins && typeof options.margins === "object" && !Array.isArray(options.margins)) {
|
|
499
|
+
const margins = options.margins;
|
|
500
|
+
const top = typeof margins.top === "number" ? margins.top : defaultmargins.top;
|
|
501
|
+
const right = typeof margins.right === "number" ? margins.right : defaultmargins.right;
|
|
502
|
+
const bottom = typeof margins.bottom === "number" ? margins.bottom : defaultmargins.bottom;
|
|
503
|
+
const left = typeof margins.left === "number" ? margins.left : defaultmargins.left;
|
|
504
|
+
normalized.margins = { top, right, bottom, left };
|
|
505
|
+
}
|
|
506
|
+
if (typeof options.scale === "number" && Number.isFinite(options.scale)) normalized.scale = options.scale;
|
|
507
|
+
if (typeof options.landscape === "boolean") normalized.landscape = options.landscape;
|
|
508
|
+
if (typeof options.paginate === "boolean") normalized.paginate = options.paginate;
|
|
509
|
+
return normalized;
|
|
510
|
+
}
|
|
511
|
+
function pdfpagesize(options) {
|
|
512
|
+
const width = (options.paperwidth ?? defaultpaperwidth) * pdfpointsperinch;
|
|
513
|
+
const height = (options.paperheight ?? defaultpaperheight) * pdfpointsperinch;
|
|
514
|
+
return options.landscape === true ? { width: height, height: width } : { width, height };
|
|
515
|
+
}
|
|
516
|
+
function pdfmargins(options) {
|
|
517
|
+
const margins = options.margins ?? defaultmargins;
|
|
518
|
+
return { top: margins.top * pdfpointsperinch, right: margins.right * pdfpointsperinch, bottom: margins.bottom * pdfpointsperinch, left: margins.left * pdfpointsperinch };
|
|
519
|
+
}
|
|
520
|
+
function pdffontsize(options) {
|
|
521
|
+
return basefontsize * (options.scale ?? 1);
|
|
522
|
+
}
|
|
523
|
+
function pdftextlayout(text2, options) {
|
|
524
|
+
const size = pdfpagesize(options);
|
|
525
|
+
const margins = pdfmargins(options);
|
|
526
|
+
const fontsize = pdffontsize(options);
|
|
527
|
+
const leading = fontsize * 1.35;
|
|
528
|
+
const linesperpage = Math.max(1, Math.floor((size.height - margins.top - margins.bottom) / leading));
|
|
529
|
+
const columns = Math.max(1, Math.floor((size.width - margins.left - margins.right) / (fontsize * 0.5)));
|
|
530
|
+
const wrapped = [];
|
|
531
|
+
for (const paragraph of text2.split(/\r?\n/)) {
|
|
532
|
+
let line = "";
|
|
533
|
+
for (const word of paragraph.split(/\s+/).filter(Boolean)) {
|
|
534
|
+
const candidate = line ? `${line} ${word}` : word;
|
|
535
|
+
if (candidate.length <= columns) {
|
|
536
|
+
line = candidate;
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
if (line) wrapped.push(line);
|
|
540
|
+
if (word.length <= columns) {
|
|
541
|
+
line = word;
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
for (let index = 0; index < word.length; index += columns) wrapped.push(word.slice(index, index + columns));
|
|
545
|
+
line = "";
|
|
546
|
+
}
|
|
547
|
+
wrapped.push(line);
|
|
548
|
+
if (wrapped.length >= linesperpage) break;
|
|
549
|
+
}
|
|
550
|
+
return wrapped.slice(0, linesperpage);
|
|
551
|
+
}
|
|
552
|
+
function pdfsegments(scrollheight, viewportheight, breaks) {
|
|
553
|
+
if (scrollheight <= 0) return [];
|
|
554
|
+
const step = viewportheight > 0 ? viewportheight : scrollheight;
|
|
555
|
+
const cuts = [0, ...breaks.filter((top) => Number.isFinite(top) && top > 0 && top < scrollheight).map((top) => Math.round(top))].filter((top, index2, list) => list.indexOf(top) === index2).sort((left, right) => left - right);
|
|
556
|
+
const segments = [];
|
|
557
|
+
let index = 0;
|
|
558
|
+
let cursor = 0;
|
|
559
|
+
while (cursor < scrollheight) {
|
|
560
|
+
while (index < cuts.length && (cuts[index] ?? 0) <= cursor) index += 1;
|
|
561
|
+
const nextcut = index < cuts.length ? cuts[index] : void 0;
|
|
562
|
+
const next = nextcut !== void 0 ? Math.min(nextcut, scrollheight) : Math.min(cursor + step, scrollheight);
|
|
563
|
+
if (next <= cursor) break;
|
|
564
|
+
segments.push({ top: cursor, height: next - cursor });
|
|
565
|
+
cursor = next;
|
|
566
|
+
}
|
|
567
|
+
return segments.length > 0 ? segments : [{ top: 0, height: scrollheight }];
|
|
568
|
+
}
|
|
569
|
+
function pdfescape(text2) {
|
|
570
|
+
let escaped = "";
|
|
571
|
+
for (const character of text2) {
|
|
572
|
+
const code = character.charCodeAt(0);
|
|
573
|
+
if (character === "(" || character === ")" || character === "\\") escaped += `\\${character}`;
|
|
574
|
+
else if (code >= 32 && code <= 255) escaped += character;
|
|
575
|
+
else escaped += "?";
|
|
576
|
+
}
|
|
577
|
+
return escaped;
|
|
578
|
+
}
|
|
579
|
+
function buildpdf(pages, options) {
|
|
580
|
+
const size = pdfpagesize(options);
|
|
581
|
+
const margins = pdfmargins(options);
|
|
582
|
+
const fontsize = pdffontsize(options);
|
|
583
|
+
const leading = fontsize * 1.35;
|
|
584
|
+
const laidout = (pages.length > 0 ? pages : [""]).map((text2) => pdftextlayout(text2, options));
|
|
585
|
+
const objects = [];
|
|
586
|
+
const kids = laidout.map((_, index) => `${4 + index * 2} 0 R`).join(" ");
|
|
587
|
+
objects.push(`<< /Type /Catalog /Pages 2 0 R >>`);
|
|
588
|
+
objects.push(`<< /Type /Pages /Kids [${kids}] /Count ${laidout.length} >>`);
|
|
589
|
+
objects.push(`<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>`);
|
|
590
|
+
for (let pageindex = 0; pageindex < laidout.length; pageindex += 1) {
|
|
591
|
+
const lines = laidout[pageindex] ?? [];
|
|
592
|
+
const operators = ["BT", `/F1 ${fontsize} Tf`, `${leading.toFixed(2)} TL`, `${margins.left.toFixed(2)} ${(size.height - margins.top - fontsize).toFixed(2)} Td`];
|
|
593
|
+
for (let lineindex = 0; lineindex < lines.length; lineindex += 1) {
|
|
594
|
+
if (lineindex > 0) operators.push("T*");
|
|
595
|
+
operators.push(`(${pdfescape(lines[lineindex] ?? "")}) Tj`);
|
|
596
|
+
}
|
|
597
|
+
operators.push("ET");
|
|
598
|
+
const content = operators.join("\n");
|
|
599
|
+
objects.push(`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${size.width.toFixed(2)} ${size.height.toFixed(2)}] /Resources << /Font << /F1 3 0 R >> >> /Contents ${5 + pageindex * 2} 0 R >>`);
|
|
600
|
+
objects.push(`<< /Length ${content.length} >>
|
|
601
|
+
stream
|
|
602
|
+
${content}
|
|
603
|
+
endstream`);
|
|
604
|
+
}
|
|
605
|
+
let document = "%PDF-1.4\n";
|
|
606
|
+
const offsets = [];
|
|
607
|
+
for (let index = 0; index < objects.length; index += 1) {
|
|
608
|
+
offsets.push(document.length);
|
|
609
|
+
document += `${index + 1} 0 obj
|
|
610
|
+
${objects[index]}
|
|
611
|
+
endobj
|
|
612
|
+
`;
|
|
613
|
+
}
|
|
614
|
+
const xrefstart = document.length;
|
|
615
|
+
document += `xref
|
|
616
|
+
0 ${objects.length + 1}
|
|
617
|
+
0000000000 65535 f
|
|
618
|
+
`;
|
|
619
|
+
for (const offset of offsets) document += `${String(offset).padStart(10, "0")} 00000 n
|
|
620
|
+
`;
|
|
621
|
+
document += `trailer
|
|
622
|
+
<< /Size ${objects.length + 1} /Root 1 0 R >>
|
|
623
|
+
startxref
|
|
624
|
+
${xrefstart}
|
|
625
|
+
%%EOF
|
|
626
|
+
`;
|
|
627
|
+
return { document, bytes: document.length, pages: laidout.length, pagewidth: Math.round(size.width), pageheight: Math.round(size.height) };
|
|
628
|
+
}
|
|
629
|
+
function recordingoptionsof(value) {
|
|
630
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
631
|
+
const options = value;
|
|
632
|
+
const normalized = {};
|
|
633
|
+
if (options.scope === "tab" || options.scope === "run") normalized.scope = options.scope;
|
|
634
|
+
if (typeof options.fps === "number" && Number.isFinite(options.fps)) normalized.fps = options.fps;
|
|
635
|
+
if (typeof options.bitrate === "number" && Number.isFinite(options.bitrate)) normalized.bitrate = options.bitrate;
|
|
636
|
+
if (typeof options.audio === "boolean") normalized.audio = options.audio;
|
|
637
|
+
return normalized;
|
|
638
|
+
}
|
|
639
|
+
function newrecording(input) {
|
|
640
|
+
return {
|
|
641
|
+
id: input.id,
|
|
642
|
+
runid: input.runid,
|
|
643
|
+
stepid: input.stepid,
|
|
644
|
+
tabid: input.tabid,
|
|
645
|
+
kind: input.kind,
|
|
646
|
+
scope: input.options.scope ?? "tab",
|
|
647
|
+
format: input.kind === "audio" ? "evidence" : "frames",
|
|
648
|
+
startedat: input.at,
|
|
649
|
+
at: input.at,
|
|
650
|
+
...input.options.fps !== void 0 ? { fps: input.options.fps } : {},
|
|
651
|
+
...input.options.bitrate !== void 0 ? { bitrate: input.options.bitrate } : {},
|
|
652
|
+
...input.options.audio !== void 0 ? { audio: input.options.audio } : {},
|
|
653
|
+
frames: []
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
function finishrecording(record2, endat) {
|
|
657
|
+
return { ...record2, endedat: endat, duration: Math.max(0, endat - record2.startedat) };
|
|
658
|
+
}
|
|
659
|
+
function frameinterval(fps) {
|
|
660
|
+
if (!Number.isFinite(fps) || fps <= 0) return 1e3;
|
|
661
|
+
return Math.max(1, Math.round(1e3 / fps));
|
|
662
|
+
}
|
|
663
|
+
function imagefilterof(value) {
|
|
664
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
665
|
+
const options = value;
|
|
666
|
+
const normalized = {};
|
|
667
|
+
if (typeof options.selector === "string" && options.selector.trim()) normalized.selector = options.selector.trim();
|
|
668
|
+
if (typeof options.minwidth === "number" && Number.isFinite(options.minwidth)) normalized.minwidth = options.minwidth;
|
|
669
|
+
if (typeof options.minheight === "number" && Number.isFinite(options.minheight)) normalized.minheight = options.minheight;
|
|
670
|
+
if (Array.isArray(options.formats) && options.formats.every((item) => typeof item === "string" && item.trim())) normalized.formats = options.formats;
|
|
671
|
+
return normalized;
|
|
672
|
+
}
|
|
673
|
+
function imagematches(image, filter) {
|
|
674
|
+
if (filter.minwidth !== void 0 && image.width < filter.minwidth) return false;
|
|
675
|
+
if (filter.minheight !== void 0 && image.height < filter.minheight) return false;
|
|
676
|
+
if (filter.formats !== void 0 && filter.formats.length > 0) {
|
|
677
|
+
const mime = image.mime.toLowerCase();
|
|
678
|
+
const matches = filter.formats.some((format) => {
|
|
679
|
+
const wanted = format.toLowerCase().trim();
|
|
680
|
+
return mime === wanted || mime === `image/${wanted}` || mime.endsWith(`/${wanted}`);
|
|
681
|
+
});
|
|
682
|
+
if (!matches) return false;
|
|
683
|
+
}
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
function dedupeimages(images) {
|
|
687
|
+
const seen = /* @__PURE__ */ new Set();
|
|
688
|
+
const unique = [];
|
|
689
|
+
for (const image of images) {
|
|
690
|
+
if (seen.has(image.url)) continue;
|
|
691
|
+
seen.add(image.url);
|
|
692
|
+
unique.push(image);
|
|
693
|
+
}
|
|
694
|
+
return unique;
|
|
695
|
+
}
|
|
696
|
+
function imagenames(rule, run, step, count, extension) {
|
|
697
|
+
const names = [];
|
|
698
|
+
for (let index = 1; index <= Math.max(0, Math.round(count)); index += 1) names.push(buildname(rule, { run, step, sequence: index, kind: "image" }, extension));
|
|
699
|
+
return names;
|
|
700
|
+
}
|
|
701
|
+
function lapseplanof(value) {
|
|
702
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
703
|
+
const options = value;
|
|
704
|
+
if (typeof options.interval !== "number" || !Number.isFinite(options.interval)) return void 0;
|
|
705
|
+
if (typeof options.duration !== "number" || !Number.isFinite(options.duration)) return void 0;
|
|
706
|
+
const format = options.format === "jpeg" || options.format === "webp" ? options.format : "png";
|
|
707
|
+
return { interval: options.interval, duration: options.duration, format };
|
|
708
|
+
}
|
|
709
|
+
function lapseframes(plan) {
|
|
710
|
+
if (!(plan.interval > 0) || !(plan.duration > 0)) return [];
|
|
711
|
+
const frames = [];
|
|
712
|
+
for (let time = 0; time < plan.duration; time += plan.interval) frames.push(Math.round(time));
|
|
713
|
+
return frames;
|
|
714
|
+
}
|
|
715
|
+
function convertdirectiveof(value) {
|
|
716
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
717
|
+
const options = value;
|
|
718
|
+
if (options.target !== "png" && options.target !== "jpeg" && options.target !== "webp") return void 0;
|
|
719
|
+
const normalized = { target: options.target };
|
|
720
|
+
if (options.source === "png" || options.source === "jpeg" || options.source === "webp") normalized.source = options.source;
|
|
721
|
+
if (typeof options.quality === "number" && Number.isFinite(options.quality)) normalized.quality = options.quality;
|
|
722
|
+
return normalized;
|
|
723
|
+
}
|
|
724
|
+
function thumbdirectiveof(value) {
|
|
725
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
726
|
+
const options = value;
|
|
727
|
+
if (typeof options.size !== "number" || !Number.isFinite(options.size) || options.size <= 0) return void 0;
|
|
728
|
+
if (options.fit !== "cover" && options.fit !== "contain") return void 0;
|
|
729
|
+
if (typeof options.suffix !== "string" || !options.suffix.trim()) return void 0;
|
|
730
|
+
return { size: options.size, fit: options.fit, suffix: options.suffix.trim() };
|
|
731
|
+
}
|
|
732
|
+
function thumbgeometry(source, directive) {
|
|
733
|
+
const size = Math.max(1, Math.round(directive.size));
|
|
734
|
+
if (directive.fit === "contain") {
|
|
735
|
+
const scale2 = Math.min(size / Math.max(1, source.width), size / Math.max(1, source.height));
|
|
736
|
+
const dw = Math.max(1, Math.round(source.width * scale2));
|
|
737
|
+
const dh = Math.max(1, Math.round(source.height * scale2));
|
|
738
|
+
return { sx: 0, sy: 0, sw: source.width, sh: source.height, dx: Math.floor((size - dw) / 2), dy: Math.floor((size - dh) / 2), dw, dh, width: size, height: size };
|
|
739
|
+
}
|
|
740
|
+
const scale = Math.max(size / Math.max(1, source.width), size / Math.max(1, source.height));
|
|
741
|
+
const sw = Math.min(source.width, Math.round(size / scale));
|
|
742
|
+
const sh = Math.min(source.height, Math.round(size / scale));
|
|
743
|
+
return { sx: Math.floor((source.width - sw) / 2), sy: Math.floor((source.height - sh) / 2), sw, sh, dx: 0, dy: 0, dw: size, dh: size, width: size, height: size };
|
|
744
|
+
}
|
|
745
|
+
function mediaentries(raw) {
|
|
746
|
+
return raw.map((entry) => ({
|
|
747
|
+
url: typeof entry.url === "string" ? entry.url : "",
|
|
748
|
+
mime: typeof entry.mime === "string" ? entry.mime : "",
|
|
749
|
+
duration: typeof entry.duration === "number" && Number.isFinite(entry.duration) ? entry.duration : 0,
|
|
750
|
+
width: typeof entry.width === "number" && Number.isFinite(entry.width) ? Math.round(entry.width) : 0,
|
|
751
|
+
height: typeof entry.height === "number" && Number.isFinite(entry.height) ? Math.round(entry.height) : 0,
|
|
752
|
+
codecs: typeof entry.codecs === "string" ? entry.codecs : "",
|
|
753
|
+
tracks: Array.isArray(entry.tracks) ? entry.tracks.filter((item) => typeof item === "string") : []
|
|
754
|
+
}));
|
|
755
|
+
}
|
|
756
|
+
function assetentries(raw) {
|
|
757
|
+
return raw.map((entry) => ({
|
|
758
|
+
kind: entry.kind === "logo" ? "logo" : "favicon",
|
|
759
|
+
url: typeof entry.url === "string" ? entry.url : "",
|
|
760
|
+
bytes: typeof entry.bytes === "number" && Number.isFinite(entry.bytes) ? entry.bytes : 0,
|
|
761
|
+
...typeof entry.sizes === "string" && entry.sizes.trim() ? { sizes: entry.sizes.trim() } : {}
|
|
762
|
+
}));
|
|
763
|
+
}
|
|
764
|
+
function streamsummaries(raw) {
|
|
765
|
+
return raw.map((entry) => {
|
|
766
|
+
const tracks = Array.isArray(entry.tracks) ? entry.tracks : [];
|
|
767
|
+
return {
|
|
768
|
+
kind: typeof entry.kind === "string" ? entry.kind : "stream",
|
|
769
|
+
tracks: tracks.length,
|
|
770
|
+
label: typeof entry.label === "string" ? entry.label : "",
|
|
771
|
+
live: entry.live === true,
|
|
772
|
+
detail: tracks.map((track) => {
|
|
773
|
+
const item = track;
|
|
774
|
+
return {
|
|
775
|
+
kind: typeof item.kind === "string" ? item.kind : "",
|
|
776
|
+
label: typeof item.label === "string" ? item.label : "",
|
|
777
|
+
...typeof item.width === "number" && Number.isFinite(item.width) ? { width: Math.round(item.width) } : {},
|
|
778
|
+
...typeof item.height === "number" && Number.isFinite(item.height) ? { height: Math.round(item.height) } : {},
|
|
779
|
+
...typeof item.framerate === "number" && Number.isFinite(item.framerate) ? { framerate: item.framerate } : {},
|
|
780
|
+
state: typeof item.state === "string" ? item.state : ""
|
|
781
|
+
};
|
|
782
|
+
})
|
|
783
|
+
};
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
|
|
199
787
|
// memory.ts
|
|
200
788
|
var sessionmemory = class {
|
|
201
789
|
constructor(adapter) {
|
|
@@ -963,23 +1551,172 @@ var sessionmemory = class {
|
|
|
963
1551
|
for (const capture of await this.getcaptures()) runs.set(capture.id, capture.runid);
|
|
964
1552
|
return records.filter((item) => runs.get(item.beforeid) === runid);
|
|
965
1553
|
}
|
|
1554
|
+
/** Stores one media record of the 1.1.41 family with its bytes and step linkage, replacing the previous record of that id; the user configured media retention window expires the oldest bytes while the metadata and the recording index always survive. */
|
|
1555
|
+
async addmedia(record2) {
|
|
1556
|
+
const records = await this.getmediarecords();
|
|
1557
|
+
const retention = (await this.getsettings())?.mediaretention;
|
|
1558
|
+
const combined = [record2, ...records.filter((item) => item.id !== record2.id)];
|
|
1559
|
+
const stored = retention === void 0 ? combined : combined.map((item, index) => index < retention ? item : expiremediabytes(item));
|
|
1560
|
+
await this.adapter.set("media", stored);
|
|
1561
|
+
}
|
|
1562
|
+
/** Returns every stored media record, newest first. */
|
|
1563
|
+
async getmediarecords() {
|
|
1564
|
+
return await this.adapter.get("media") ?? [];
|
|
1565
|
+
}
|
|
1566
|
+
/** Returns the media records filtered by run and kind; an absent filter returns every record. */
|
|
1567
|
+
async listmedia(filter) {
|
|
1568
|
+
const records = await this.getmediarecords();
|
|
1569
|
+
return records.filter((item) => (filter.runid === void 0 || item.runid === filter.runid) && (filter.kind === void 0 || mediakindof(item) === filter.kind));
|
|
1570
|
+
}
|
|
1571
|
+
/** Returns one media record by its id. */
|
|
1572
|
+
async getmediarecord(id) {
|
|
1573
|
+
return (await this.getmediarecords()).find((item) => item.id === id);
|
|
1574
|
+
}
|
|
1575
|
+
/** Returns one recording with its file reference and frame index by its id. */
|
|
1576
|
+
async getrecording(id) {
|
|
1577
|
+
const found = await this.getmediarecord(id);
|
|
1578
|
+
return found !== void 0 && "startedat" in found ? found : void 0;
|
|
1579
|
+
}
|
|
1580
|
+
/** Removes one media record by its id; the audit trail keeps its outcome evidence. */
|
|
1581
|
+
async removemedia(id) {
|
|
1582
|
+
await this.adapter.set("media", (await this.getmediarecords()).filter((item) => item.id !== id));
|
|
1583
|
+
}
|
|
1584
|
+
/** Stores one observed image batch of a downloadimages step, replacing the previous batch of that id. */
|
|
1585
|
+
async addimagebatch(batch) {
|
|
1586
|
+
const records = await this.adapter.get("imagebatches") ?? [];
|
|
1587
|
+
await this.adapter.set("imagebatches", [batch, ...records.filter((item) => item.id !== batch.id)]);
|
|
1588
|
+
}
|
|
1589
|
+
/** Returns every observed image batch with its filter match counts, newest first. */
|
|
1590
|
+
async getimagebatches() {
|
|
1591
|
+
return await this.adapter.get("imagebatches") ?? [];
|
|
1592
|
+
}
|
|
1593
|
+
/** Stores one recording consent decision of an origin, replacing the previous record of that id. */
|
|
1594
|
+
async setrecordingconsent(record2) {
|
|
1595
|
+
const records = (await this.adapter.get("recordingconsents") ?? []).filter((item) => item.id !== record2.id);
|
|
1596
|
+
await this.adapter.set("recordingconsents", [record2, ...records]);
|
|
1597
|
+
}
|
|
1598
|
+
/** Returns every recording consent decision with its prompt and origin, newest first. */
|
|
1599
|
+
async getrecordingconsents() {
|
|
1600
|
+
return await this.adapter.get("recordingconsents") ?? [];
|
|
1601
|
+
}
|
|
1602
|
+
/** Stores one outbound call record with its transport facts and body, replacing the previous record of that id; the user configured call retention window expires the oldest bodies while the metadata always survives. */
|
|
1603
|
+
async addcall(record2) {
|
|
1604
|
+
const records = await this.getcalls();
|
|
1605
|
+
const retention = (await this.getsettings())?.callretention;
|
|
1606
|
+
const combined = [record2, ...records.filter((item) => item.id !== record2.id)];
|
|
1607
|
+
const stored = retention === void 0 ? combined : combined.map((item, index) => index < retention ? item : expirecallbody(item));
|
|
1608
|
+
await this.adapter.set("calls", stored);
|
|
1609
|
+
}
|
|
1610
|
+
/** Returns every stored outbound call record, newest first. */
|
|
1611
|
+
async getcalls() {
|
|
1612
|
+
return await this.adapter.get("calls") ?? [];
|
|
1613
|
+
}
|
|
1614
|
+
/** Returns one outbound call record with its body by its id. */
|
|
1615
|
+
async getcall(id) {
|
|
1616
|
+
return (await this.getcalls()).find((item) => item.id === id);
|
|
1617
|
+
}
|
|
1618
|
+
/** Returns the outbound call records filtered by run and origin; an absent filter returns every call. */
|
|
1619
|
+
async listcalls(filter) {
|
|
1620
|
+
const records = await this.getcalls();
|
|
1621
|
+
return records.filter((item) => (filter.runid === void 0 || item.runid === filter.runid) && (filter.origin === void 0 || item.origin === filter.origin));
|
|
1622
|
+
}
|
|
1623
|
+
/** Stores one typed endpoint definition version, appending to the version history of that endpoint name. */
|
|
1624
|
+
async setendpoint(record2) {
|
|
1625
|
+
const records = await this.adapter.get("endpoints") ?? [];
|
|
1626
|
+
const prior = records.filter((item) => item.name === record2.name);
|
|
1627
|
+
const version = prior.length > 0 ? Math.max(...prior.map((item) => item.version)) + 1 : 1;
|
|
1628
|
+
await this.adapter.set("endpoints", [{ ...record2, version, at: record2.at }, ...records]);
|
|
1629
|
+
}
|
|
1630
|
+
/** Returns the newest endpointrecord definition of one name with its payload schema and version. */
|
|
1631
|
+
async getendpoint(name) {
|
|
1632
|
+
const records = await this.adapter.get("endpoints") ?? [];
|
|
1633
|
+
return records.find((item) => item.name === name);
|
|
1634
|
+
}
|
|
1635
|
+
/** Returns the newest definition of every typed endpoint name with its schema and version history. */
|
|
1636
|
+
async getendpoints() {
|
|
1637
|
+
const records = await this.adapter.get("endpoints") ?? [];
|
|
1638
|
+
const latest = /* @__PURE__ */ new Map();
|
|
1639
|
+
for (const record2 of records) if (!latest.has(record2.name)) latest.set(record2.name, record2);
|
|
1640
|
+
return [...latest.values()];
|
|
1641
|
+
}
|
|
1642
|
+
/** Stores one fetch consent decision per origin with its reviewed header names and values and its expiry window. */
|
|
1643
|
+
async setfetchconsent(consent) {
|
|
1644
|
+
const records = (await this.adapter.get("fetchconsents") ?? []).filter((item) => item.id !== consent.id);
|
|
1645
|
+
await this.adapter.set("fetchconsents", [consent, ...records]);
|
|
1646
|
+
}
|
|
1647
|
+
/** Returns every fetch consent decision with its origin, header names and expiry window, newest first. */
|
|
1648
|
+
async getfetchconsents() {
|
|
1649
|
+
return await this.adapter.get("fetchconsents") ?? [];
|
|
1650
|
+
}
|
|
1651
|
+
/** Stores one api key reference record without any key material; the secret value stays behind its storage id. */
|
|
1652
|
+
async setapikey(ref) {
|
|
1653
|
+
const records = (await this.adapter.get("apikeys") ?? []).filter((item) => item.name !== ref.name);
|
|
1654
|
+
await this.adapter.set("apikeys", [ref, ...records]);
|
|
1655
|
+
}
|
|
1656
|
+
/** Returns every stored api key reference with its origin scope, header name and storage id; key material never loads here. */
|
|
1657
|
+
async getapikeys() {
|
|
1658
|
+
return await this.adapter.get("apikeys") ?? [];
|
|
1659
|
+
}
|
|
1660
|
+
/** Removes one api key reference and its stored secret together. */
|
|
1661
|
+
async removeapikey(name) {
|
|
1662
|
+
const records = await this.getapikeys();
|
|
1663
|
+
const ref = records.find((item) => item.name === name);
|
|
1664
|
+
if (ref) await this.adapter.set(ref.storageid, void 0);
|
|
1665
|
+
await this.adapter.set("apikeys", records.filter((item) => item.name !== name));
|
|
1666
|
+
}
|
|
1667
|
+
/** Stores one api key secret under its storage id; the value never appears in reports, outcomes or the audit trail. */
|
|
1668
|
+
async setsecret(storageid, value) {
|
|
1669
|
+
return this.adapter.set(storageid, value);
|
|
1670
|
+
}
|
|
1671
|
+
/** Loads one api key secret under its storage id for the executor only. */
|
|
1672
|
+
async getsecret(storageid) {
|
|
1673
|
+
return this.adapter.get(storageid);
|
|
1674
|
+
}
|
|
966
1675
|
};
|
|
1676
|
+
function mediakindof(record2) {
|
|
1677
|
+
if ("pages" in record2) return "pdf";
|
|
1678
|
+
if ("startedat" in record2) return "recording";
|
|
1679
|
+
if ("timestamp" in record2) return "frame";
|
|
1680
|
+
if ("context" in record2) return "canvas";
|
|
1681
|
+
if ("tracks" in record2) return "stream";
|
|
1682
|
+
return "asset";
|
|
1683
|
+
}
|
|
1684
|
+
function expiremediabytes(record2) {
|
|
1685
|
+
if ("dataurl" in record2) {
|
|
1686
|
+
const source = record2;
|
|
1687
|
+
const copy = { ...source };
|
|
1688
|
+
delete copy.dataurl;
|
|
1689
|
+
return { ...copy, bytesexpired: true };
|
|
1690
|
+
}
|
|
1691
|
+
if ("startedat" in record2) {
|
|
1692
|
+
const source = record2;
|
|
1693
|
+
const copy = { ...source };
|
|
1694
|
+
delete copy.bytes;
|
|
1695
|
+
return { ...copy, bytesexpired: true };
|
|
1696
|
+
}
|
|
1697
|
+
return record2;
|
|
1698
|
+
}
|
|
967
1699
|
function expirecapturebytes(record2) {
|
|
968
1700
|
const { bytes, ...metadata } = record2;
|
|
969
1701
|
void bytes;
|
|
970
1702
|
return { ...metadata, bytesexpired: true };
|
|
971
1703
|
}
|
|
1704
|
+
function expirecallbody(record2) {
|
|
1705
|
+
const { body, ...metadata } = record2;
|
|
1706
|
+
void body;
|
|
1707
|
+
return { ...metadata, bodyexpired: true };
|
|
1708
|
+
}
|
|
972
1709
|
function randomid() {
|
|
973
1710
|
return crypto.randomUUID();
|
|
974
1711
|
}
|
|
975
1712
|
|
|
976
1713
|
// policy.ts
|
|
977
|
-
var sensitiveactions = /* @__PURE__ */ new Set(["click", "type", "navigate", "select", "presskey", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "reload", "back", "forward", "writestorage", "setattribute", "removeattribute", "evaluate", "tabcreate", "tabactivate", "tabclose", "tabreload", "windowcreate", "windowclose", "windowresize", "downloadfile", "clickpoint", "shiftclick", "dismissdialog", "enterframe", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "openlink", "openprivate", "reloadcache", "stopnav", "followlink", "spanav", "rewritequery", "setfragment", "navlist", "navprofile", "handleauth", "printpdf", "prefetch", "preconnect", "deeplink", "reopentab", "pausenav", "navrate", "openclipboard", "batchopen", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "restorelayout", "reopenrun", "badgetab", "fillform", "filllabel", "fillplaceholder", "submitform", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcard", "fillcode", "consentpassword", "exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "streamdisk", "paginateextract", "resumeextract", "batchdownload", "pausedownload", "resumedownload", "interceptmime", "readclipboard", "writeclipboard", "copyscreen", "quarantinedownload", "scanvirus", "cleanupartifacts"]);
|
|
1714
|
+
var sensitiveactions = /* @__PURE__ */ new Set(["click", "type", "navigate", "select", "presskey", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "reload", "back", "forward", "writestorage", "setattribute", "removeattribute", "evaluate", "tabcreate", "tabactivate", "tabclose", "tabreload", "windowcreate", "windowclose", "windowresize", "downloadfile", "clickpoint", "shiftclick", "dismissdialog", "enterframe", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "openlink", "openprivate", "reloadcache", "stopnav", "followlink", "spanav", "rewritequery", "setfragment", "navlist", "navprofile", "handleauth", "printpdf", "prefetch", "preconnect", "deeplink", "reopentab", "pausenav", "navrate", "openclipboard", "batchopen", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "restorelayout", "reopenrun", "badgetab", "fillform", "filllabel", "fillplaceholder", "submitform", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcard", "fillcode", "consentpassword", "exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "streamdisk", "paginateextract", "resumeextract", "batchdownload", "pausedownload", "resumedownload", "interceptmime", "readclipboard", "writeclipboard", "copyscreen", "quarantinedownload", "scanvirus", "cleanupartifacts", "recordscreen", "captureaudio", "downloadimages", "callrest", "callgraphql"]);
|
|
978
1715
|
var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset", "movepointer", "clicktext", "clickaria", "clickname", "expanddetails", "pierceshadow", "retryaction"]);
|
|
979
|
-
var readactions = /* @__PURE__ */ new Set(["observe", "inspect", "extract", "wait", "waitfor", "waittext", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "highlight", "tablist", "windowlist", "tabsnapshot", "mapclicks", "verifyvisible", "verifyenabled", "resolvexpath", "a11ytree", "readvisible", "readertree", "detectlists", "detecttables", "readjson", "watchmutate", "waitquiet", "watchbanner", "detectinfinitescroll", "detectvirtual", "detectlazy", "readscrollpos", "readlang", "readoutline", "countpages", "listshadow", "listframes", "classifypage", "fingerprintsection", "diffsnapshots", "readselection", "watchfocus", "detectsticky", "detectscrolllock", "readopengraph", "detectlanguage", "deriveselector", "waitload", "waiturl", "spawait", "detecthttp", "readredirects", "readfinalurl", "trailaudit", "navintent", "checksafe", "querytabs", "watchtab", "findclones", "searchtabs", "listaudio", "snapshotsession", "savelayout", "attachmeta", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "readerrors", "skiphoneypot", "detectlogin", "detecttemplate", "handoffcaptcha", "scrapetable", "importcsv", "looprows", "transformvalues", "deduperows", "mergepages", "stamplerows", "previewgrid", "logprovenance", "verifydownload", "exportnetlog", "namecaptures", "shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet"]);
|
|
1716
|
+
var readactions = /* @__PURE__ */ new Set(["observe", "inspect", "extract", "wait", "waitfor", "waittext", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "highlight", "tablist", "windowlist", "tabsnapshot", "mapclicks", "verifyvisible", "verifyenabled", "resolvexpath", "a11ytree", "readvisible", "readertree", "detectlists", "detecttables", "readjson", "watchmutate", "waitquiet", "watchbanner", "detectinfinitescroll", "detectvirtual", "detectlazy", "readscrollpos", "readlang", "readoutline", "countpages", "listshadow", "listframes", "classifypage", "fingerprintsection", "diffsnapshots", "readselection", "watchfocus", "detectsticky", "detectscrolllock", "readopengraph", "detectlanguage", "deriveselector", "waitload", "waiturl", "spawait", "detecthttp", "readredirects", "readfinalurl", "trailaudit", "navintent", "checksafe", "querytabs", "watchtab", "findclones", "searchtabs", "listaudio", "snapshotsession", "savelayout", "attachmeta", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "readerrors", "skiphoneypot", "detectlogin", "detecttemplate", "handoffcaptcha", "scrapetable", "importcsv", "looprows", "transformvalues", "deduperows", "mergepages", "stamplerows", "previewgrid", "logprovenance", "verifydownload", "exportnetlog", "namecaptures", "shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet", "capturepdf", "captureframe", "readmedia", "readassets", "probestream", "timelapse", "shotcanvas", "convertimage", "makethumbs", "fetchurl", "parsejson", "parsehtml"]);
|
|
980
1717
|
var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
|
|
981
1718
|
var watchactions = /* @__PURE__ */ new Set(["watchmutate", "watchbanner", "watchfocus", "watchtab"]);
|
|
982
|
-
var targetactions = /* @__PURE__ */ new Set(["inspect", "focus", "click", "type", "scroll", "select", "hover", "clickdeep", "rightclick", "doubleclick", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "highlight", "setattribute", "removeattribute", "waitfor", "shiftclick", "typetime", "appendtext", "setvalue", "typeedit", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "expanddetails", "verifyvisible", "verifyenabled", "pierceshadow", "deriveselector", "fingerprintsection", "submitform", "retryform", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcode", "consentpassword", "scrapetable", "paginateextract", "shotelement"]);
|
|
1719
|
+
var targetactions = /* @__PURE__ */ new Set(["inspect", "focus", "click", "type", "scroll", "select", "hover", "clickdeep", "rightclick", "doubleclick", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "highlight", "setattribute", "removeattribute", "waitfor", "shiftclick", "typetime", "appendtext", "setvalue", "typeedit", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "expanddetails", "verifyvisible", "verifyenabled", "pierceshadow", "deriveselector", "fingerprintsection", "submitform", "retryform", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcode", "consentpassword", "scrapetable", "paginateextract", "shotelement", "captureframe", "shotcanvas"]);
|
|
983
1720
|
var valueactions = /* @__PURE__ */ new Set(["presskey", "drag", "drop", "upload", "readattribute", "removeattribute", "waittext", "evaluate", "zoomset", "tabactivate", "tabclose", "tabreload", "windowclose", "windowresize", "tabcreate", "windowcreate", "downloadfile", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "chooseradio", "setslider", "setdate", "setcolor", "followlink", "setfragment", "handleauth", "navintent", "openclipboard", "checksafe", "reopentab", "spanav", "duplicatetab", "pintab", "mutetab", "movetab", "movetabwindow", "searchtabs", "badgetab", "attachmeta", "focuswindow", "maximizewindow", "minimizewindow", "restorewindow", "incognitowindow", "asksubmit", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcode", "consentpassword", "pausedownload", "resumedownload", "verifydownload", "writeclipboard", "quarantinedownload", "scanvirus"]);
|
|
984
1721
|
var tabscommandactions = /* @__PURE__ */ new Set(["querytabs", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "watchtab", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "savelayout", "restorelayout", "findclones", "searchtabs", "badgetab", "attachmeta", "listaudio", "reopenrun", "snapshotsession"]);
|
|
985
1722
|
var formactions = /* @__PURE__ */ new Set(["fillform", "filllabel", "fillplaceholder", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "submitform", "readerrors", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "handoffcaptcha", "fillcard", "fillcode", "consentpassword", "skiphoneypot", "detectlogin", "detecttemplate"]);
|
|
@@ -987,6 +1724,9 @@ var datasetactions = /* @__PURE__ */ new Set(["scrapetable", "exportcsv", "expor
|
|
|
987
1724
|
var exportactions = /* @__PURE__ */ new Set(["exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "streamdisk"]);
|
|
988
1725
|
var filesactions = /* @__PURE__ */ new Set(["batchdownload", "pausedownload", "resumedownload", "verifydownload", "interceptmime", "exportnetlog", "readclipboard", "writeclipboard", "copyscreen", "quarantinedownload", "scanvirus", "namecaptures", "cleanupartifacts"]);
|
|
989
1726
|
var captureactions = /* @__PURE__ */ new Set(["shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet"]);
|
|
1727
|
+
var mediaactions = /* @__PURE__ */ new Set(["capturepdf", "recordscreen", "captureaudio", "captureframe", "downloadimages", "shotcanvas", "probestream", "readmedia", "readassets", "timelapse", "convertimage", "makethumbs"]);
|
|
1728
|
+
var httpactions = /* @__PURE__ */ new Set(["fetchurl", "parsejson", "parsehtml", "callrest", "callgraphql"]);
|
|
1729
|
+
var credentialheaders = /* @__PURE__ */ new Set(["authorization", "proxy-authorization", "cookie", "cookie2", "set-cookie", "api-key", "x-api-key", "x-auth-token", "x-session-token", "proxy-authorization"]);
|
|
990
1730
|
var fieldkinds = ["text", "email", "phone", "date", "number", "select", "check", "radio", "file", "password", "card", "code"];
|
|
991
1731
|
var layoutmutationactions = /* @__PURE__ */ new Set(["grouptabs", "colorgroup", "collapsegroup", "savelayout", "restorelayout"]);
|
|
992
1732
|
var groupcolors = ["grey", "blue", "red", "yellow", "green", "pink", "purple", "cyan", "orange"];
|
|
@@ -1688,6 +2428,305 @@ function validatetabsgrammar(step, options) {
|
|
|
1688
2428
|
if (kind === "reopenrun" && !isnonempty(options.run)) return { allowed: false, reason: "A reviewed run id is required in options to reopen its tabs." };
|
|
1689
2429
|
return { allowed: true };
|
|
1690
2430
|
}
|
|
2431
|
+
function ismediakind(kind) {
|
|
2432
|
+
return mediaactions.has(kind);
|
|
2433
|
+
}
|
|
2434
|
+
function ishttpkind(kind) {
|
|
2435
|
+
return httpactions.has(kind);
|
|
2436
|
+
}
|
|
2437
|
+
function origincheck(session, url) {
|
|
2438
|
+
let parsed;
|
|
2439
|
+
try {
|
|
2440
|
+
parsed = new URL(url);
|
|
2441
|
+
} catch {
|
|
2442
|
+
return { allowed: false, reason: "The outbound request needs a valid url before it can be reviewed." };
|
|
2443
|
+
}
|
|
2444
|
+
if (parsed.protocol !== "https:") return { allowed: false, reason: "Outbound requests use HTTPS urls only." };
|
|
2445
|
+
if (parsed.username || parsed.password) return { allowed: false, reason: "Endpoint credentials are not allowed in the url." };
|
|
2446
|
+
if (!origingranted(session, parsed.origin)) return { allowed: false, reason: `The outbound request to ${parsed.origin} stays outside the session origin grants.` };
|
|
2447
|
+
return { allowed: true };
|
|
2448
|
+
}
|
|
2449
|
+
function credentialheadername(name) {
|
|
2450
|
+
return credentialheaders.has(name.trim().toLowerCase());
|
|
2451
|
+
}
|
|
2452
|
+
function fetchconsentrefgranted(step) {
|
|
2453
|
+
let options = {};
|
|
2454
|
+
try {
|
|
2455
|
+
options = parseoptions(step);
|
|
2456
|
+
} catch {
|
|
2457
|
+
options = {};
|
|
2458
|
+
}
|
|
2459
|
+
const request = options.fetch;
|
|
2460
|
+
const headers = request && typeof request === "object" && !Array.isArray(request) ? request.headers : void 0;
|
|
2461
|
+
const names = headers && typeof headers === "object" && !Array.isArray(headers) ? Object.keys(headers) : [];
|
|
2462
|
+
if (names.length === 0) return { allowed: true };
|
|
2463
|
+
const empty = names.some((name) => !name.trim());
|
|
2464
|
+
if (empty) return { allowed: false, reason: "Header allowlists with empty names are refused." };
|
|
2465
|
+
const credential = names.find((name) => credentialheadername(name));
|
|
2466
|
+
if (credential !== void 0 && !isnonempty(options.consentref)) return { allowed: false, reason: `The credential bearing header ${credential} needs the explicit reviewed consent that names it before it is sent.` };
|
|
2467
|
+
if (!isnonempty(options.consentref)) return { allowed: false, reason: `The ${names.length} reviewed custom header${names.length === 1 ? "" : "s"} need a reviewed consent ref in options before any send.` };
|
|
2468
|
+
return { allowed: true };
|
|
2469
|
+
}
|
|
2470
|
+
function fetchbudgetallowed(timeout, retries, backoff, wait) {
|
|
2471
|
+
for (const [label, value] of [["timeout", timeout], ["retries", retries], ["backoff", backoff]]) {
|
|
2472
|
+
if (value !== void 0 && (typeof value !== "number" || !Number.isFinite(value) || value < 0)) return { allowed: false, reason: `The reviewed fetch ${label} must be zero or a positive number with no code ceiling.` };
|
|
2473
|
+
}
|
|
2474
|
+
if (wait !== void 0 && (typeof wait !== "number" || !Number.isFinite(wait) || wait < 0)) return { allowed: false, reason: "The reviewed fetch wait budget must be zero or a positive number of milliseconds." };
|
|
2475
|
+
if (wait === void 0 || timeout === void 0) return { allowed: true };
|
|
2476
|
+
const attempts = Math.max(1, Math.floor(retries ?? 0) + 1);
|
|
2477
|
+
const waits = (backoff ?? 0) * (attempts * (attempts - 1)) / 2;
|
|
2478
|
+
const worstcase = timeout * attempts + waits;
|
|
2479
|
+
if (worstcase > wait) return { allowed: false, reason: `The fetch worst case of ${worstcase} milliseconds exceeds the reviewed wait budget of ${wait} milliseconds; review a wider budget or fewer retries.` };
|
|
2480
|
+
return { allowed: true };
|
|
2481
|
+
}
|
|
2482
|
+
function outboundtarget(step) {
|
|
2483
|
+
let options = {};
|
|
2484
|
+
try {
|
|
2485
|
+
options = parseoptions(step);
|
|
2486
|
+
} catch {
|
|
2487
|
+
options = {};
|
|
2488
|
+
}
|
|
2489
|
+
const request = options.fetch;
|
|
2490
|
+
if (request && typeof request === "object" && !Array.isArray(request)) {
|
|
2491
|
+
const url = request.url;
|
|
2492
|
+
if (typeof url === "string" && url.trim()) return url.trim();
|
|
2493
|
+
}
|
|
2494
|
+
return void 0;
|
|
2495
|
+
}
|
|
2496
|
+
function validpath(path) {
|
|
2497
|
+
return path.split(".").every((segment) => /^[A-Za-z0-9_-]+$/.test(segment));
|
|
2498
|
+
}
|
|
2499
|
+
function validatehttpgrammar(step, options) {
|
|
2500
|
+
const kind = step.kind;
|
|
2501
|
+
if (kind === "fetchurl") {
|
|
2502
|
+
const request = options.fetch;
|
|
2503
|
+
if (!request || typeof request !== "object" || Array.isArray(request)) return { allowed: false, reason: "A reviewed fetch request with a url is required in options.fetch." };
|
|
2504
|
+
const fetchrequest = request;
|
|
2505
|
+
if (typeof fetchrequest.url !== "string" || !fetchrequest.url.trim()) return { allowed: false, reason: "The reviewed fetch request needs a non-empty url." };
|
|
2506
|
+
if (fetchrequest.method !== void 0 && (typeof fetchrequest.method !== "string" || !["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"].includes(fetchrequest.method.trim().toUpperCase()))) return { allowed: false, reason: "The reviewed fetch method must be a known HTTP verb." };
|
|
2507
|
+
if (fetchrequest.headers !== void 0) {
|
|
2508
|
+
if (!fetchrequest.headers || typeof fetchrequest.headers !== "object" || Array.isArray(fetchrequest.headers)) return { allowed: false, reason: "The reviewed header allowlist must be an object of custom headers." };
|
|
2509
|
+
for (const name of Object.keys(fetchrequest.headers)) {
|
|
2510
|
+
if (!name.trim()) return { allowed: false, reason: "Header allowlists with empty names are refused." };
|
|
2511
|
+
if (typeof fetchrequest.headers[name] !== "string") return { allowed: false, reason: `The reviewed header ${name} needs a string value.` };
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
if (fetchrequest.body !== void 0 && typeof fetchrequest.body !== "string") return { allowed: false, reason: "The reviewed fetch body must be a string." };
|
|
2515
|
+
if (fetchrequest.mode !== void 0 && fetchrequest.mode !== "cors" && fetchrequest.mode !== "no-cors" && fetchrequest.mode !== "same-origin") return { allowed: false, reason: "The reviewed fetch mode must be cors, no-cors or same-origin." };
|
|
2516
|
+
const consentgate = fetchconsentrefgranted(step);
|
|
2517
|
+
if (!consentgate.allowed) return consentgate;
|
|
2518
|
+
const policycheck = validatefetchoptions(options.fetchoptions);
|
|
2519
|
+
if (!policycheck.allowed) return policycheck;
|
|
2520
|
+
const fetchpolicy = fetchoptionsvalues(options.fetchoptions);
|
|
2521
|
+
const budget = fetchbudgetallowed(fetchpolicy.timeout, fetchpolicy.retries, fetchpolicy.backoff, fetchnumeric(options, "wait"));
|
|
2522
|
+
if (!budget.allowed) return budget;
|
|
2523
|
+
if (options.stream !== void 0) {
|
|
2524
|
+
if (!options.stream || typeof options.stream !== "object" || Array.isArray(options.stream)) return { allowed: false, reason: "The reviewed stream window must be an object with an optional byte budget." };
|
|
2525
|
+
const streambudget = options.stream.budget;
|
|
2526
|
+
if (streambudget !== void 0 && (typeof streambudget !== "number" || !Number.isFinite(streambudget) || streambudget < 0)) return { allowed: false, reason: "The reviewed stream byte budget must be zero or a positive number of bytes with no code ceiling." };
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
if (kind === "parsejson") {
|
|
2530
|
+
if (!isnonempty(options.call)) return { allowed: false, reason: "A reviewed stored call id is required in options.call before the body parses." };
|
|
2531
|
+
const fields = options.fields;
|
|
2532
|
+
if (!Array.isArray(fields) || fields.length === 0) return { allowed: false, reason: "A reviewed non-empty list of json path rules is required in options.fields." };
|
|
2533
|
+
for (const item of fields) {
|
|
2534
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return { allowed: false, reason: "Every json path rule must be an object." };
|
|
2535
|
+
const rule = item;
|
|
2536
|
+
if (!isnonempty(rule.name)) return { allowed: false, reason: "Every json path rule needs a non-empty field name." };
|
|
2537
|
+
if (typeof rule.path !== "string" || !rule.path.trim() || !validpath(rule.path.trim())) return { allowed: false, reason: `The json path of ${rule.name} must be a dotted path of non-empty segments.` };
|
|
2538
|
+
if (rule.kind !== void 0 && rule.kind !== "text" && rule.kind !== "number" && rule.kind !== "boolean" && rule.kind !== "json") return { allowed: false, reason: `The json path kind of ${rule.name} must be text, number, boolean or json.` };
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
if (kind === "parsehtml") {
|
|
2542
|
+
if (!isnonempty(options.call)) return { allowed: false, reason: "A reviewed stored call id is required in options.call before the markup parses." };
|
|
2543
|
+
const queries = options.queries;
|
|
2544
|
+
if (!Array.isArray(queries) || queries.length === 0) return { allowed: false, reason: "A reviewed non-empty list of html queries is required in options.queries." };
|
|
2545
|
+
for (const item of queries) {
|
|
2546
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return { allowed: false, reason: "Every html query must be an object." };
|
|
2547
|
+
const query = item;
|
|
2548
|
+
if (!isnonempty(query.selector)) return { allowed: false, reason: "Every html query needs a selector from the reviewed selector grammar." };
|
|
2549
|
+
if (query.attribute !== void 0 && !isnonempty(query.attribute)) return { allowed: false, reason: "The reviewed html query attribute must be a non-empty attribute name." };
|
|
2550
|
+
if (query.multi !== void 0 && typeof query.multi !== "boolean") return { allowed: false, reason: "The reviewed html query multi flag must be a boolean." };
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
if (kind === "callrest" || kind === "callgraphql") {
|
|
2554
|
+
if (!isnonempty(options.endpoint)) return { allowed: false, reason: "A reviewed typed endpoint name is required in options.endpoint." };
|
|
2555
|
+
if (kind === "callrest") {
|
|
2556
|
+
if (options.payload !== void 0 && (!options.payload || typeof options.payload !== "object" || Array.isArray(options.payload))) return { allowed: false, reason: "The reviewed rest payload must be an object of reviewed values." };
|
|
2557
|
+
if (options.method !== void 0 && (typeof options.method !== "string" || !["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"].includes(options.method.trim().toUpperCase()))) return { allowed: false, reason: "The reviewed endpoint method override must be a known HTTP verb." };
|
|
2558
|
+
if (options.success !== void 0 && (!Array.isArray(options.success) || !options.success.every((code) => typeof code === "number" && Number.isInteger(code)))) return { allowed: false, reason: "The reviewed success status list must be a list of integer status codes." };
|
|
2559
|
+
}
|
|
2560
|
+
if (kind === "callgraphql") {
|
|
2561
|
+
const request = options.graphql;
|
|
2562
|
+
if (!request || typeof request !== "object" || Array.isArray(request)) return { allowed: false, reason: "A reviewed graphql request with an operation is required in options.graphql." };
|
|
2563
|
+
const graphql = request;
|
|
2564
|
+
if (typeof graphql.query !== "string" || !graphql.query.trim()) return { allowed: false, reason: "The reviewed graphql operation text must be a non-empty string." };
|
|
2565
|
+
if (graphql.operationkind !== "query" && graphql.operationkind !== "mutation") return { allowed: false, reason: "The reviewed graphql operation kind must be query or mutation; unknown operation kinds are refused." };
|
|
2566
|
+
if (graphql.variables !== void 0 && (!graphql.variables || typeof graphql.variables !== "object" || Array.isArray(graphql.variables))) return { allowed: false, reason: "The reviewed graphql variables must be an object of reviewed values." };
|
|
2567
|
+
if (graphql.operationname !== void 0 && !isnonempty(graphql.operationname)) return { allowed: false, reason: "The reviewed graphql operation name must be a non-empty string." };
|
|
2568
|
+
}
|
|
2569
|
+
if (options.apikeys !== void 0 && (!Array.isArray(options.apikeys) || !options.apikeys.every((name) => isnonempty(name)))) return { allowed: false, reason: "The reviewed api key reference list must be a list of non-empty stored names." };
|
|
2570
|
+
const policycheck = validatefetchoptions(options.fetchoptions);
|
|
2571
|
+
if (!policycheck.allowed) return policycheck;
|
|
2572
|
+
const fetchpolicy = fetchoptionsvalues(options.fetchoptions);
|
|
2573
|
+
const budget = fetchbudgetallowed(fetchpolicy.timeout, fetchpolicy.retries, fetchpolicy.backoff, fetchnumeric(options, "wait"));
|
|
2574
|
+
if (!budget.allowed) return budget;
|
|
2575
|
+
}
|
|
2576
|
+
return { allowed: true };
|
|
2577
|
+
}
|
|
2578
|
+
function validatefetchoptions(value) {
|
|
2579
|
+
if (value === void 0) return { allowed: true };
|
|
2580
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return { allowed: false, reason: "The reviewed fetch options must be an object with timeout, retries, backoff and follow." };
|
|
2581
|
+
const options = value;
|
|
2582
|
+
for (const key of ["timeout", "backoff"]) {
|
|
2583
|
+
if (options[key] !== void 0 && (typeof options[key] !== "number" || !Number.isFinite(options[key]) || options[key] < 0)) return { allowed: false, reason: `The reviewed fetch ${key} must be zero or a positive number with no code ceiling.` };
|
|
2584
|
+
}
|
|
2585
|
+
for (const key of ["retries", "follow"]) {
|
|
2586
|
+
if (options[key] !== void 0 && (typeof options[key] !== "number" || !Number.isInteger(options[key]) || options[key] < 0)) return { allowed: false, reason: `The reviewed fetch ${key} must be zero or a positive integer with no code ceiling.` };
|
|
2587
|
+
}
|
|
2588
|
+
return { allowed: true };
|
|
2589
|
+
}
|
|
2590
|
+
function fetchoptionsvalues(value) {
|
|
2591
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
2592
|
+
const options = value;
|
|
2593
|
+
return { timeout: fetchnumeric(options, "timeout"), retries: fetchnumeric(options, "retries"), backoff: fetchnumeric(options, "backoff") };
|
|
2594
|
+
}
|
|
2595
|
+
function fetchnumeric(options, key) {
|
|
2596
|
+
const value = options[key];
|
|
2597
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2598
|
+
}
|
|
2599
|
+
function isrecordingkind(kind) {
|
|
2600
|
+
return kind === "recordscreen" || kind === "captureaudio";
|
|
2601
|
+
}
|
|
2602
|
+
function mediagate(session, tabid, origin, now) {
|
|
2603
|
+
if (!session || session.stoppedat) return { allowed: false, reason: "No active browser session exists for the media capture." };
|
|
2604
|
+
if (session.expiresat <= now) return { allowed: false, reason: "The browser session has expired and cannot capture media." };
|
|
2605
|
+
if (session.pausedat) return { allowed: false, reason: "The browser session is paused and cannot capture media." };
|
|
2606
|
+
if (session.tabid !== tabid) return { allowed: false, reason: `The media capture needs the active tab grant of session tab ${session.tabid} and refuses tab ${tabid}.` };
|
|
2607
|
+
if (!origingranted(session, origin)) return { allowed: false, reason: `The media capture of ${origin} needs the session origin grants first.` };
|
|
2608
|
+
return { allowed: true };
|
|
2609
|
+
}
|
|
2610
|
+
function recordingconsentgranted(step) {
|
|
2611
|
+
let options = {};
|
|
2612
|
+
try {
|
|
2613
|
+
options = parseoptions(step);
|
|
2614
|
+
} catch {
|
|
2615
|
+
options = {};
|
|
2616
|
+
}
|
|
2617
|
+
const consentref = options.consentref;
|
|
2618
|
+
if (typeof consentref !== "string" || !consentref.trim()) return { allowed: false, reason: "A recording of user activity requires a reviewed consent ref in options before it starts." };
|
|
2619
|
+
return { allowed: true };
|
|
2620
|
+
}
|
|
2621
|
+
function lapsebudgetallowed(interval, duration, wait) {
|
|
2622
|
+
if (!(interval > 0)) return { allowed: false, reason: "The reviewed lapse interval must be a positive number of milliseconds." };
|
|
2623
|
+
if (!(duration > 0)) return { allowed: false, reason: "The reviewed lapse duration must be a positive number of milliseconds." };
|
|
2624
|
+
if (wait !== void 0 && !(wait >= 0)) return { allowed: false, reason: "The reviewed wait budget must be zero or a positive number of milliseconds." };
|
|
2625
|
+
if (wait !== void 0 && duration > wait) return { allowed: false, reason: `The lapse duration of ${duration} milliseconds exceeds the reviewed wait budget of ${wait} milliseconds; review a wider budget or a shorter duration.` };
|
|
2626
|
+
return { allowed: true };
|
|
2627
|
+
}
|
|
2628
|
+
function validatemediagrammar(step, options) {
|
|
2629
|
+
const kind = step.kind;
|
|
2630
|
+
if (kind === "capturepdf") {
|
|
2631
|
+
const pdf = options.pdf;
|
|
2632
|
+
if (pdf !== void 0) {
|
|
2633
|
+
if (!pdf || typeof pdf !== "object" || Array.isArray(pdf)) return { allowed: false, reason: "The reviewed pdf options must be an object in options.pdf." };
|
|
2634
|
+
const pdfoptions = pdf;
|
|
2635
|
+
if (pdfoptions.paperwidth !== void 0 && (typeof pdfoptions.paperwidth !== "number" || !Number.isFinite(pdfoptions.paperwidth) || pdfoptions.paperwidth <= 0)) return { allowed: false, reason: "The reviewed pdf paper width must be a positive number of inches with no code cap." };
|
|
2636
|
+
if (pdfoptions.paperheight !== void 0 && (typeof pdfoptions.paperheight !== "number" || !Number.isFinite(pdfoptions.paperheight) || pdfoptions.paperheight <= 0)) return { allowed: false, reason: "The reviewed pdf paper height must be a positive number of inches with no code cap." };
|
|
2637
|
+
if (pdfoptions.margins !== void 0) {
|
|
2638
|
+
const margins = pdfoptions.margins;
|
|
2639
|
+
if (!margins || typeof margins !== "object" || Array.isArray(margins)) return { allowed: false, reason: "The reviewed pdf margins must be an object with top, right, bottom and left inches." };
|
|
2640
|
+
for (const side of ["top", "right", "bottom", "left"]) {
|
|
2641
|
+
const value = margins[side];
|
|
2642
|
+
if (value === void 0) continue;
|
|
2643
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return { allowed: false, reason: `The reviewed pdf ${side} margin must be zero or a positive number of inches; negative margins are refused.` };
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
if (pdfoptions.scale !== void 0 && (typeof pdfoptions.scale !== "number" || !Number.isFinite(pdfoptions.scale) || pdfoptions.scale <= 0)) return { allowed: false, reason: "The reviewed pdf scale must be a positive number with no code cap." };
|
|
2647
|
+
if (pdfoptions.landscape !== void 0 && typeof pdfoptions.landscape !== "boolean") return { allowed: false, reason: "The reviewed pdf landscape flag must be a boolean." };
|
|
2648
|
+
if (pdfoptions.paginate !== void 0 && typeof pdfoptions.paginate !== "boolean") return { allowed: false, reason: "The reviewed pdf paginate flag must be a boolean." };
|
|
2649
|
+
}
|
|
2650
|
+
if (options.breakpoints !== void 0 && (!Array.isArray(options.breakpoints) || options.breakpoints.length === 0 || !options.breakpoints.every((item) => isnonempty(item)))) return { allowed: false, reason: "The reviewed pdf break points must be a non-empty list of selectors when present." };
|
|
2651
|
+
if (options.exporttarget !== void 0 && options.exporttarget !== "memory" && options.exporttarget !== "download") return { allowed: false, reason: "The reviewed pdf export target must be memory or download; pdf documents do not route to the clipboard." };
|
|
2652
|
+
if (options.name !== void 0 && !isnonempty(options.name)) return { allowed: false, reason: "The reviewed pdf artifact name must be a non-empty string." };
|
|
2653
|
+
}
|
|
2654
|
+
if (kind === "recordscreen" || kind === "captureaudio") {
|
|
2655
|
+
const recording = options.recording;
|
|
2656
|
+
if (recording !== void 0) {
|
|
2657
|
+
if (!recording || typeof recording !== "object" || Array.isArray(recording)) return { allowed: false, reason: "The reviewed recording options must be an object in options.recording." };
|
|
2658
|
+
const recordoptions = recording;
|
|
2659
|
+
if (recordoptions.scope !== void 0 && recordoptions.scope !== "tab" && recordoptions.scope !== "run") return { allowed: false, reason: "The reviewed recording scope must be tab or run." };
|
|
2660
|
+
if (recordoptions.fps !== void 0 && (typeof recordoptions.fps !== "number" || !Number.isFinite(recordoptions.fps) || recordoptions.fps <= 0)) return { allowed: false, reason: "The reviewed recording fps must be a positive number with no code ceiling." };
|
|
2661
|
+
if (recordoptions.bitrate !== void 0 && (typeof recordoptions.bitrate !== "number" || !Number.isFinite(recordoptions.bitrate) || recordoptions.bitrate <= 0)) return { allowed: false, reason: "The reviewed recording bitrate must be a positive number with no code ceiling." };
|
|
2662
|
+
if (recordoptions.audio !== void 0 && typeof recordoptions.audio !== "boolean") return { allowed: false, reason: "The reviewed recording audio flag must be a boolean." };
|
|
2663
|
+
}
|
|
2664
|
+
if (options.duration !== void 0 && (typeof options.duration !== "number" || !Number.isFinite(options.duration) || options.duration <= 0)) return { allowed: false, reason: "The reviewed recording duration must be a positive number of milliseconds with no code ceiling." };
|
|
2665
|
+
const consent = recordingconsentgranted(step);
|
|
2666
|
+
if (!consent.allowed) return consent;
|
|
2667
|
+
}
|
|
2668
|
+
if (kind === "captureframe") {
|
|
2669
|
+
if (options.timestamp !== void 0 && (typeof options.timestamp !== "number" || !Number.isFinite(options.timestamp) || options.timestamp < 0)) return { allowed: false, reason: "The reviewed frame timestamp must be zero or a positive number of seconds." };
|
|
2670
|
+
if (options.poster !== void 0 && typeof options.poster !== "boolean") return { allowed: false, reason: "The reviewed poster flag must be a boolean." };
|
|
2671
|
+
const capturecheck = validatecaptureoptions(options.capture);
|
|
2672
|
+
if (!capturecheck.allowed) return capturecheck;
|
|
2673
|
+
}
|
|
2674
|
+
if (kind === "downloadimages") {
|
|
2675
|
+
const filter = options.imagefilter;
|
|
2676
|
+
if (!filter || typeof filter !== "object" || Array.isArray(filter)) return { allowed: false, reason: "A reviewed imagefilter is required in options before any image downloads." };
|
|
2677
|
+
const imagefilter = filter;
|
|
2678
|
+
if (imagefilter.selector !== void 0 && !isnonempty(imagefilter.selector)) return { allowed: false, reason: "The reviewed imagefilter selector must be a non-empty selector from the reviewed selector grammar." };
|
|
2679
|
+
if (imagefilter.minwidth !== void 0 && (typeof imagefilter.minwidth !== "number" || !Number.isFinite(imagefilter.minwidth) || imagefilter.minwidth < 0)) return { allowed: false, reason: "The reviewed imagefilter minimum width must be zero or a positive number of pixels." };
|
|
2680
|
+
if (imagefilter.minheight !== void 0 && (typeof imagefilter.minheight !== "number" || !Number.isFinite(imagefilter.minheight) || imagefilter.minheight < 0)) return { allowed: false, reason: "The reviewed imagefilter minimum height must be zero or a positive number of pixels." };
|
|
2681
|
+
if (imagefilter.formats !== void 0 && (!Array.isArray(imagefilter.formats) || imagefilter.formats.length === 0 || !imagefilter.formats.every((item) => isnonempty(item)))) return { allowed: false, reason: "The reviewed imagefilter format list must be a non-empty list of mime or extension patterns when present." };
|
|
2682
|
+
if (options.naming !== void 0) {
|
|
2683
|
+
const namingcheck = validatecapturenaming(options.naming);
|
|
2684
|
+
if (!namingcheck.allowed) return namingcheck;
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
if (kind === "shotcanvas") {
|
|
2688
|
+
const capturecheck = validatecaptureoptions(options.capture);
|
|
2689
|
+
if (!capturecheck.allowed) return capturecheck;
|
|
2690
|
+
}
|
|
2691
|
+
if (kind === "probestream" && options.selector !== void 0 && !isnonempty(options.selector)) return { allowed: false, reason: "The reviewed stream probe scope selector must be a non-empty string." };
|
|
2692
|
+
if (kind === "timelapse") {
|
|
2693
|
+
const lapse = options.lapse;
|
|
2694
|
+
if (!lapse || typeof lapse !== "object" || Array.isArray(lapse)) return { allowed: false, reason: "A reviewed lapse plan with interval, duration and format is required in options." };
|
|
2695
|
+
const plan = lapse;
|
|
2696
|
+
if (typeof plan.interval !== "number" || !Number.isFinite(plan.interval) || plan.interval <= 0) return { allowed: false, reason: "The reviewed lapse interval must be a positive number of milliseconds with no code ceiling." };
|
|
2697
|
+
if (typeof plan.duration !== "number" || !Number.isFinite(plan.duration) || plan.duration <= 0) return { allowed: false, reason: "The reviewed lapse duration must be a positive number of milliseconds with no code ceiling." };
|
|
2698
|
+
if (plan.format !== void 0 && plan.format !== "png" && plan.format !== "jpeg" && plan.format !== "webp") return { allowed: false, reason: "The reviewed lapse format must be png, jpeg or webp." };
|
|
2699
|
+
const budget = lapsebudgetallowed(plan.interval, plan.duration, typeof options.wait === "number" ? options.wait : void 0);
|
|
2700
|
+
if (!budget.allowed) return budget;
|
|
2701
|
+
const capturecheck = validatecaptureoptions(options.capture);
|
|
2702
|
+
if (!capturecheck.allowed) return capturecheck;
|
|
2703
|
+
}
|
|
2704
|
+
if (kind === "convertimage" || kind === "makethumbs") {
|
|
2705
|
+
const single = options.capture;
|
|
2706
|
+
const list = options.captures;
|
|
2707
|
+
const hasone = isnonempty(single);
|
|
2708
|
+
const haslist = Array.isArray(list) && list.length > 0 && list.every((item) => isnonempty(item));
|
|
2709
|
+
if (!hasone && !haslist) return { allowed: false, reason: "A reviewed capture id or a reviewed non-empty capture id list is required in options." };
|
|
2710
|
+
if (hasone && haslist) return { allowed: false, reason: "The reviewed step needs one capture id or a capture id list, not both." };
|
|
2711
|
+
}
|
|
2712
|
+
if (kind === "convertimage") {
|
|
2713
|
+
const convert = options.convert;
|
|
2714
|
+
if (!convert || typeof convert !== "object" || Array.isArray(convert)) return { allowed: false, reason: "A reviewed convert directive with a target format is required in options." };
|
|
2715
|
+
const directive = convert;
|
|
2716
|
+
if (directive.target !== "png" && directive.target !== "jpeg" && directive.target !== "webp") return { allowed: false, reason: "The reviewed conversion target must be png, jpeg or webp." };
|
|
2717
|
+
if (directive.source !== void 0 && directive.source !== "png" && directive.source !== "jpeg" && directive.source !== "webp") return { allowed: false, reason: "The reviewed conversion source must be png, jpeg or webp." };
|
|
2718
|
+
if (directive.quality !== void 0 && (typeof directive.quality !== "number" || !Number.isFinite(directive.quality) || directive.quality < 0 || directive.quality > 100)) return { allowed: false, reason: "The reviewed conversion quality must stay between zero and one hundred with no code cap inside that range." };
|
|
2719
|
+
}
|
|
2720
|
+
if (kind === "makethumbs") {
|
|
2721
|
+
const thumb = options.thumb;
|
|
2722
|
+
if (!thumb || typeof thumb !== "object" || Array.isArray(thumb)) return { allowed: false, reason: "A reviewed thumb directive with size, fit and suffix is required in options." };
|
|
2723
|
+
const directive = thumb;
|
|
2724
|
+
if (typeof directive.size !== "number" || !Number.isFinite(directive.size) || directive.size <= 0) return { allowed: false, reason: "The reviewed thumbnail size must be a positive number of pixels with no fixed set." };
|
|
2725
|
+
if (directive.fit !== "cover" && directive.fit !== "contain") return { allowed: false, reason: "The reviewed thumbnail fit must be cover or contain." };
|
|
2726
|
+
if (!isnonempty(directive.suffix)) return { allowed: false, reason: "The reviewed thumbnail naming suffix must be a non-empty string." };
|
|
2727
|
+
}
|
|
2728
|
+
return { allowed: true };
|
|
2729
|
+
}
|
|
1691
2730
|
function validatestep(step, origin) {
|
|
1692
2731
|
if (!allowedactions.has(step.kind)) return { allowed: false, reason: "Unsupported action kind." };
|
|
1693
2732
|
if (!step.summary.trim()) return { allowed: false, reason: "A human-readable action summary is required." };
|
|
@@ -1909,6 +2948,14 @@ function validatestep(step, origin) {
|
|
|
1909
2948
|
const capturecheck = validatecapturegrammar(step, options);
|
|
1910
2949
|
if (!capturecheck.allowed) return capturecheck;
|
|
1911
2950
|
}
|
|
2951
|
+
if (ismediakind(step.kind)) {
|
|
2952
|
+
const mediacheck = validatemediagrammar(step, options);
|
|
2953
|
+
if (!mediacheck.allowed) return mediacheck;
|
|
2954
|
+
}
|
|
2955
|
+
if (ishttpkind(step.kind)) {
|
|
2956
|
+
const httpcheck = validatehttpgrammar(step, options);
|
|
2957
|
+
if (!httpcheck.allowed) return httpcheck;
|
|
2958
|
+
}
|
|
1912
2959
|
if (step.kind === "tabcreate") {
|
|
1913
2960
|
if (options.background !== void 0 && typeof options.background !== "boolean") return { allowed: false, reason: "The reviewed background flag must be a boolean." };
|
|
1914
2961
|
if (options.window !== void 0 && (typeof options.window !== "number" || !Number.isInteger(options.window) || options.window < 0)) return { allowed: false, reason: "The reviewed target window id must be a non-negative integer." };
|
|
@@ -1980,6 +3027,25 @@ function canexecute(input) {
|
|
|
1980
3027
|
const target = captureoptions.capture?.exporttarget;
|
|
1981
3028
|
if (target !== void 0 && target !== "memory" && target !== "download" && target !== "clipboard") return { allowed: false, reason: "The capture export target must be memory, download or clipboard." };
|
|
1982
3029
|
}
|
|
3030
|
+
if (ismediakind(input.step.kind)) {
|
|
3031
|
+
const mediagatecheck = mediagate(input.session, input.tabid, input.origin, now);
|
|
3032
|
+
if (!mediagatecheck.allowed) return mediagatecheck;
|
|
3033
|
+
}
|
|
3034
|
+
if (isrecordingkind(input.step.kind)) {
|
|
3035
|
+
const recordinggate = recordingconsentgranted(input.step);
|
|
3036
|
+
if (!recordinggate.allowed) return recordinggate;
|
|
3037
|
+
}
|
|
3038
|
+
if (ishttpkind(input.step.kind)) {
|
|
3039
|
+
const target = outboundtarget(input.step);
|
|
3040
|
+
if (target !== void 0) {
|
|
3041
|
+
const outboundgate = origincheck(input.session, target);
|
|
3042
|
+
if (!outboundgate.allowed) return outboundgate;
|
|
3043
|
+
}
|
|
3044
|
+
if (input.step.kind === "fetchurl" || input.step.kind === "callrest" || input.step.kind === "callgraphql") {
|
|
3045
|
+
const consentgate = fetchconsentrefgranted(input.step);
|
|
3046
|
+
if (!consentgate.allowed) return consentgate;
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
1983
3049
|
if (input.step.kind === "openlink" || input.step.kind === "openprivate" || input.step.kind === "batchopen" || input.step.kind === "prefetch" || input.step.kind === "deeplink" || input.step.kind === "reopentab") {
|
|
1984
3050
|
let options = {};
|
|
1985
3051
|
try {
|
|
@@ -2001,7 +3067,7 @@ function canexecute(input) {
|
|
|
2001
3067
|
}
|
|
2002
3068
|
|
|
2003
3069
|
// version.ts
|
|
2004
|
-
var packageversion = "1.1.
|
|
3070
|
+
var packageversion = "1.1.42";
|
|
2005
3071
|
|
|
2006
3072
|
// types.ts
|
|
2007
3073
|
var protocolversion = packageversion;
|
|
@@ -2015,9 +3081,10 @@ function text(value, field) {
|
|
|
2015
3081
|
if (typeof value !== "string" || !value.trim()) throw new Error(`${field} must be a non-empty string.`);
|
|
2016
3082
|
return value.trim();
|
|
2017
3083
|
}
|
|
2018
|
-
function parseproposal(value, origin) {
|
|
3084
|
+
function parseproposal(value, origin, grants) {
|
|
2019
3085
|
const root = record(value);
|
|
2020
3086
|
if (root.version !== protocolversion) throw new Error("Unsupported protocol version.");
|
|
3087
|
+
const covered = grants !== void 0 && grants.length > 0 ? grants : [origin];
|
|
2021
3088
|
const planinput = record(root.plan);
|
|
2022
3089
|
const stepsinput = planinput.steps;
|
|
2023
3090
|
if (!Array.isArray(stepsinput) || stepsinput.length === 0) throw new Error("A plan needs at least one step.");
|
|
@@ -2035,6 +3102,17 @@ function parseproposal(value, origin) {
|
|
|
2035
3102
|
};
|
|
2036
3103
|
const evaluation = validatestep(step, origin);
|
|
2037
3104
|
if (!evaluation.allowed) throw new Error(evaluation.reason);
|
|
3105
|
+
const target = outboundtarget(step);
|
|
3106
|
+
if (target !== void 0) {
|
|
3107
|
+
const granted = covered.some((pattern) => {
|
|
3108
|
+
try {
|
|
3109
|
+
return new URL(target).origin === new URL(pattern).origin;
|
|
3110
|
+
} catch {
|
|
3111
|
+
return false;
|
|
3112
|
+
}
|
|
3113
|
+
});
|
|
3114
|
+
if (!granted) throw new Error(`The fetch request to ${target} targets an origin outside the grants.`);
|
|
3115
|
+
}
|
|
2038
3116
|
return step;
|
|
2039
3117
|
});
|
|
2040
3118
|
for (const step of steps) {
|
|
@@ -2065,7 +3143,7 @@ function requestbody(input) {
|
|
|
2065
3143
|
return JSON.stringify({ version: protocolversion, objective: input.objective, session: input.session, observation: input.observation, capabilities: input.capabilities });
|
|
2066
3144
|
}
|
|
2067
3145
|
function outcomeresponse(input) {
|
|
2068
|
-
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, outcome: input.outcome, ...input.resolvedtarget ? { resolvedtarget: input.resolvedtarget } : {}, ...input.capture ? { capture: input.capture } : {} });
|
|
3146
|
+
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, outcome: input.outcome, ...input.resolvedtarget ? { resolvedtarget: input.resolvedtarget } : {}, ...input.capture ? { capture: input.capture } : {}, ...input.media ? { media: input.media } : {}, ...input.transport ? { transport: input.transport } : {} });
|
|
2069
3147
|
}
|
|
2070
3148
|
function mapresponse(input) {
|
|
2071
3149
|
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, map: input.map });
|
|
@@ -2145,12 +3223,28 @@ function quarantinereport(input) {
|
|
|
2145
3223
|
function capturereport(input) {
|
|
2146
3224
|
return { version: protocolversion, records: input.records, pairs: input.pairs };
|
|
2147
3225
|
}
|
|
3226
|
+
function mediareport(input) {
|
|
3227
|
+
return { version: protocolversion, records: input.records, images: input.images };
|
|
3228
|
+
}
|
|
3229
|
+
function callsreport(input) {
|
|
3230
|
+
const calls = input.calls.map((call) => {
|
|
3231
|
+
const { body, ...metadata } = call;
|
|
3232
|
+
void body;
|
|
3233
|
+
return metadata;
|
|
3234
|
+
});
|
|
3235
|
+
return { version: protocolversion, calls };
|
|
3236
|
+
}
|
|
2148
3237
|
export {
|
|
2149
3238
|
annotationplanof,
|
|
3239
|
+
assetentries,
|
|
2150
3240
|
blendrows,
|
|
2151
3241
|
buildname,
|
|
3242
|
+
buildpdf,
|
|
2152
3243
|
buildsheet,
|
|
2153
3244
|
buildstitchplan,
|
|
3245
|
+
callgraphql,
|
|
3246
|
+
callrest,
|
|
3247
|
+
callsreport,
|
|
2154
3248
|
canexecute,
|
|
2155
3249
|
captureelement,
|
|
2156
3250
|
captureformats,
|
|
@@ -2162,38 +3256,68 @@ export {
|
|
|
2162
3256
|
capturestitched,
|
|
2163
3257
|
capturetargets,
|
|
2164
3258
|
capturevisible,
|
|
3259
|
+
convertdirectiveof,
|
|
2165
3260
|
croprect,
|
|
2166
3261
|
crossesviewport,
|
|
2167
3262
|
datasetresponse,
|
|
3263
|
+
dedupeimages,
|
|
2168
3264
|
actionrisk as deriveactionrisk,
|
|
2169
3265
|
diffresponse,
|
|
2170
3266
|
downloadreport,
|
|
2171
3267
|
errorreportresponse,
|
|
2172
3268
|
eventresponse,
|
|
2173
3269
|
extractionreport,
|
|
3270
|
+
fetchoptionsof,
|
|
3271
|
+
fetchrequestof,
|
|
3272
|
+
finishrecording,
|
|
2174
3273
|
fixedheadermatch,
|
|
2175
3274
|
formreportresponse,
|
|
3275
|
+
frameinterval,
|
|
2176
3276
|
generatedvalueallowed,
|
|
3277
|
+
graphqlopenvelope,
|
|
3278
|
+
graphqlrequestof,
|
|
2177
3279
|
heldkeysreport,
|
|
2178
3280
|
hostpattern,
|
|
3281
|
+
htmlqueriesof,
|
|
3282
|
+
httpkinds,
|
|
3283
|
+
imagefilterof,
|
|
3284
|
+
imagematches,
|
|
3285
|
+
imagenames,
|
|
2179
3286
|
isformkind,
|
|
2180
3287
|
iswatchkind,
|
|
3288
|
+
jsonpathrulesof,
|
|
3289
|
+
lapseframes,
|
|
3290
|
+
lapseplanof,
|
|
2181
3291
|
layoutreport,
|
|
2182
3292
|
mapresponse,
|
|
3293
|
+
mediaentries,
|
|
3294
|
+
mediakinds,
|
|
3295
|
+
mediareport,
|
|
2183
3296
|
navstateresponse,
|
|
2184
3297
|
netlogreport,
|
|
3298
|
+
newrecording,
|
|
2185
3299
|
normalizeendpoint,
|
|
2186
3300
|
observationmodeof,
|
|
2187
3301
|
observationresponse,
|
|
2188
3302
|
outcomeresponse,
|
|
2189
3303
|
pairstates,
|
|
3304
|
+
parsehtmlbody,
|
|
2190
3305
|
parseproposal,
|
|
2191
3306
|
passwordconsentgranted,
|
|
3307
|
+
payloadvalid,
|
|
3308
|
+
payloadwithdefaults,
|
|
3309
|
+
pdfoptionsof,
|
|
3310
|
+
pdfpagesize,
|
|
3311
|
+
pdfsegments,
|
|
3312
|
+
pdftextlayout,
|
|
2192
3313
|
profilegrantgranted,
|
|
2193
3314
|
protocolversion,
|
|
2194
3315
|
provenancereport,
|
|
2195
3316
|
quarantinereport,
|
|
2196
3317
|
randomid,
|
|
3318
|
+
readpath,
|
|
3319
|
+
readstream,
|
|
3320
|
+
recordingoptionsof,
|
|
2197
3321
|
regionsteps,
|
|
2198
3322
|
requestbody,
|
|
2199
3323
|
resolutionverdict,
|
|
@@ -2201,12 +3325,20 @@ export {
|
|
|
2201
3325
|
scaledrect,
|
|
2202
3326
|
seamweights,
|
|
2203
3327
|
selectorresponse,
|
|
3328
|
+
sendfetch,
|
|
2204
3329
|
sessionmemory,
|
|
2205
3330
|
signalsreport,
|
|
3331
|
+
statusclassof,
|
|
3332
|
+
streamsummaries,
|
|
3333
|
+
streamwindowof,
|
|
2206
3334
|
submitreviewgranted,
|
|
2207
3335
|
tabreportresponse,
|
|
3336
|
+
templateurl,
|
|
3337
|
+
thumbdirectiveof,
|
|
3338
|
+
thumbgeometry,
|
|
2208
3339
|
trailreport,
|
|
2209
3340
|
transformgrammar,
|
|
3341
|
+
unwrapgraphql,
|
|
2210
3342
|
validatefieldmatch,
|
|
2211
3343
|
validateformrecord,
|
|
2212
3344
|
validatestep,
|