@wenathlan/extension 1.1.41 → 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 +5 -4
- package/dist/httpclient.d.ts +158 -0
- package/dist/httpclient.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +591 -5
- package/dist/index.js.map +4 -4
- package/dist/memory.d.ts +32 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/policy.d.ts +25 -0
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +21 -4
- package/dist/protocol.d.ts.map +1 -1
- package/dist/types.d.ts +131 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +884 -9
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/manifest.json +1 -1
- package/extension/dist/pagebridge.js +12 -3
- package/extension/dist/pagebridge.js.map +2 -2
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +10 -2
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sidepanel.html +1 -1
- package/extension/dist/sidepanel.js +149 -2
- package/extension/dist/sidepanel.js.map +3 -3
- package/extension/dist/style.css +2 -0
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -196,6 +196,292 @@ 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
|
+
|
|
199
485
|
// media.ts
|
|
200
486
|
var mediakinds = ["capturepdf", "recordscreen", "captureaudio", "captureframe", "downloadimages", "shotcanvas", "probestream", "readmedia", "readassets", "timelapse", "convertimage", "makethumbs"];
|
|
201
487
|
var defaultpaperwidth = 8.5;
|
|
@@ -1313,6 +1599,79 @@ var sessionmemory = class {
|
|
|
1313
1599
|
async getrecordingconsents() {
|
|
1314
1600
|
return await this.adapter.get("recordingconsents") ?? [];
|
|
1315
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
|
+
}
|
|
1316
1675
|
};
|
|
1317
1676
|
function mediakindof(record2) {
|
|
1318
1677
|
if ("pages" in record2) return "pdf";
|
|
@@ -1342,14 +1701,19 @@ function expirecapturebytes(record2) {
|
|
|
1342
1701
|
void bytes;
|
|
1343
1702
|
return { ...metadata, bytesexpired: true };
|
|
1344
1703
|
}
|
|
1704
|
+
function expirecallbody(record2) {
|
|
1705
|
+
const { body, ...metadata } = record2;
|
|
1706
|
+
void body;
|
|
1707
|
+
return { ...metadata, bodyexpired: true };
|
|
1708
|
+
}
|
|
1345
1709
|
function randomid() {
|
|
1346
1710
|
return crypto.randomUUID();
|
|
1347
1711
|
}
|
|
1348
1712
|
|
|
1349
1713
|
// policy.ts
|
|
1350
|
-
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"]);
|
|
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"]);
|
|
1351
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"]);
|
|
1352
|
-
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"]);
|
|
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"]);
|
|
1353
1717
|
var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
|
|
1354
1718
|
var watchactions = /* @__PURE__ */ new Set(["watchmutate", "watchbanner", "watchfocus", "watchtab"]);
|
|
1355
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"]);
|
|
@@ -1361,6 +1725,8 @@ var exportactions = /* @__PURE__ */ new Set(["exportcsv", "exportjson", "exporte
|
|
|
1361
1725
|
var filesactions = /* @__PURE__ */ new Set(["batchdownload", "pausedownload", "resumedownload", "verifydownload", "interceptmime", "exportnetlog", "readclipboard", "writeclipboard", "copyscreen", "quarantinedownload", "scanvirus", "namecaptures", "cleanupartifacts"]);
|
|
1362
1726
|
var captureactions = /* @__PURE__ */ new Set(["shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet"]);
|
|
1363
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"]);
|
|
1364
1730
|
var fieldkinds = ["text", "email", "phone", "date", "number", "select", "check", "radio", "file", "password", "card", "code"];
|
|
1365
1731
|
var layoutmutationactions = /* @__PURE__ */ new Set(["grouptabs", "colorgroup", "collapsegroup", "savelayout", "restorelayout"]);
|
|
1366
1732
|
var groupcolors = ["grey", "blue", "red", "yellow", "green", "pink", "purple", "cyan", "orange"];
|
|
@@ -2065,6 +2431,171 @@ function validatetabsgrammar(step, options) {
|
|
|
2065
2431
|
function ismediakind(kind) {
|
|
2066
2432
|
return mediaactions.has(kind);
|
|
2067
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
|
+
}
|
|
2068
2599
|
function isrecordingkind(kind) {
|
|
2069
2600
|
return kind === "recordscreen" || kind === "captureaudio";
|
|
2070
2601
|
}
|
|
@@ -2421,6 +2952,10 @@ function validatestep(step, origin) {
|
|
|
2421
2952
|
const mediacheck = validatemediagrammar(step, options);
|
|
2422
2953
|
if (!mediacheck.allowed) return mediacheck;
|
|
2423
2954
|
}
|
|
2955
|
+
if (ishttpkind(step.kind)) {
|
|
2956
|
+
const httpcheck = validatehttpgrammar(step, options);
|
|
2957
|
+
if (!httpcheck.allowed) return httpcheck;
|
|
2958
|
+
}
|
|
2424
2959
|
if (step.kind === "tabcreate") {
|
|
2425
2960
|
if (options.background !== void 0 && typeof options.background !== "boolean") return { allowed: false, reason: "The reviewed background flag must be a boolean." };
|
|
2426
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." };
|
|
@@ -2500,6 +3035,17 @@ function canexecute(input) {
|
|
|
2500
3035
|
const recordinggate = recordingconsentgranted(input.step);
|
|
2501
3036
|
if (!recordinggate.allowed) return recordinggate;
|
|
2502
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
|
+
}
|
|
2503
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") {
|
|
2504
3050
|
let options = {};
|
|
2505
3051
|
try {
|
|
@@ -2521,7 +3067,7 @@ function canexecute(input) {
|
|
|
2521
3067
|
}
|
|
2522
3068
|
|
|
2523
3069
|
// version.ts
|
|
2524
|
-
var packageversion = "1.1.
|
|
3070
|
+
var packageversion = "1.1.42";
|
|
2525
3071
|
|
|
2526
3072
|
// types.ts
|
|
2527
3073
|
var protocolversion = packageversion;
|
|
@@ -2535,9 +3081,10 @@ function text(value, field) {
|
|
|
2535
3081
|
if (typeof value !== "string" || !value.trim()) throw new Error(`${field} must be a non-empty string.`);
|
|
2536
3082
|
return value.trim();
|
|
2537
3083
|
}
|
|
2538
|
-
function parseproposal(value, origin) {
|
|
3084
|
+
function parseproposal(value, origin, grants) {
|
|
2539
3085
|
const root = record(value);
|
|
2540
3086
|
if (root.version !== protocolversion) throw new Error("Unsupported protocol version.");
|
|
3087
|
+
const covered = grants !== void 0 && grants.length > 0 ? grants : [origin];
|
|
2541
3088
|
const planinput = record(root.plan);
|
|
2542
3089
|
const stepsinput = planinput.steps;
|
|
2543
3090
|
if (!Array.isArray(stepsinput) || stepsinput.length === 0) throw new Error("A plan needs at least one step.");
|
|
@@ -2555,6 +3102,17 @@ function parseproposal(value, origin) {
|
|
|
2555
3102
|
};
|
|
2556
3103
|
const evaluation = validatestep(step, origin);
|
|
2557
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
|
+
}
|
|
2558
3116
|
return step;
|
|
2559
3117
|
});
|
|
2560
3118
|
for (const step of steps) {
|
|
@@ -2585,7 +3143,7 @@ function requestbody(input) {
|
|
|
2585
3143
|
return JSON.stringify({ version: protocolversion, objective: input.objective, session: input.session, observation: input.observation, capabilities: input.capabilities });
|
|
2586
3144
|
}
|
|
2587
3145
|
function outcomeresponse(input) {
|
|
2588
|
-
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 } : {} });
|
|
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 } : {} });
|
|
2589
3147
|
}
|
|
2590
3148
|
function mapresponse(input) {
|
|
2591
3149
|
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, map: input.map });
|
|
@@ -2668,6 +3226,14 @@ function capturereport(input) {
|
|
|
2668
3226
|
function mediareport(input) {
|
|
2669
3227
|
return { version: protocolversion, records: input.records, images: input.images };
|
|
2670
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
|
+
}
|
|
2671
3237
|
export {
|
|
2672
3238
|
annotationplanof,
|
|
2673
3239
|
assetentries,
|
|
@@ -2676,6 +3242,9 @@ export {
|
|
|
2676
3242
|
buildpdf,
|
|
2677
3243
|
buildsheet,
|
|
2678
3244
|
buildstitchplan,
|
|
3245
|
+
callgraphql,
|
|
3246
|
+
callrest,
|
|
3247
|
+
callsreport,
|
|
2679
3248
|
canexecute,
|
|
2680
3249
|
captureelement,
|
|
2681
3250
|
captureformats,
|
|
@@ -2698,18 +3267,25 @@ export {
|
|
|
2698
3267
|
errorreportresponse,
|
|
2699
3268
|
eventresponse,
|
|
2700
3269
|
extractionreport,
|
|
3270
|
+
fetchoptionsof,
|
|
3271
|
+
fetchrequestof,
|
|
2701
3272
|
finishrecording,
|
|
2702
3273
|
fixedheadermatch,
|
|
2703
3274
|
formreportresponse,
|
|
2704
3275
|
frameinterval,
|
|
2705
3276
|
generatedvalueallowed,
|
|
3277
|
+
graphqlopenvelope,
|
|
3278
|
+
graphqlrequestof,
|
|
2706
3279
|
heldkeysreport,
|
|
2707
3280
|
hostpattern,
|
|
3281
|
+
htmlqueriesof,
|
|
3282
|
+
httpkinds,
|
|
2708
3283
|
imagefilterof,
|
|
2709
3284
|
imagematches,
|
|
2710
3285
|
imagenames,
|
|
2711
3286
|
isformkind,
|
|
2712
3287
|
iswatchkind,
|
|
3288
|
+
jsonpathrulesof,
|
|
2713
3289
|
lapseframes,
|
|
2714
3290
|
lapseplanof,
|
|
2715
3291
|
layoutreport,
|
|
@@ -2725,8 +3301,11 @@ export {
|
|
|
2725
3301
|
observationresponse,
|
|
2726
3302
|
outcomeresponse,
|
|
2727
3303
|
pairstates,
|
|
3304
|
+
parsehtmlbody,
|
|
2728
3305
|
parseproposal,
|
|
2729
3306
|
passwordconsentgranted,
|
|
3307
|
+
payloadvalid,
|
|
3308
|
+
payloadwithdefaults,
|
|
2730
3309
|
pdfoptionsof,
|
|
2731
3310
|
pdfpagesize,
|
|
2732
3311
|
pdfsegments,
|
|
@@ -2736,6 +3315,8 @@ export {
|
|
|
2736
3315
|
provenancereport,
|
|
2737
3316
|
quarantinereport,
|
|
2738
3317
|
randomid,
|
|
3318
|
+
readpath,
|
|
3319
|
+
readstream,
|
|
2739
3320
|
recordingoptionsof,
|
|
2740
3321
|
regionsteps,
|
|
2741
3322
|
requestbody,
|
|
@@ -2744,15 +3325,20 @@ export {
|
|
|
2744
3325
|
scaledrect,
|
|
2745
3326
|
seamweights,
|
|
2746
3327
|
selectorresponse,
|
|
3328
|
+
sendfetch,
|
|
2747
3329
|
sessionmemory,
|
|
2748
3330
|
signalsreport,
|
|
3331
|
+
statusclassof,
|
|
2749
3332
|
streamsummaries,
|
|
3333
|
+
streamwindowof,
|
|
2750
3334
|
submitreviewgranted,
|
|
2751
3335
|
tabreportresponse,
|
|
3336
|
+
templateurl,
|
|
2752
3337
|
thumbdirectiveof,
|
|
2753
3338
|
thumbgeometry,
|
|
2754
3339
|
trailreport,
|
|
2755
3340
|
transformgrammar,
|
|
3341
|
+
unwrapgraphql,
|
|
2756
3342
|
validatefieldmatch,
|
|
2757
3343
|
validateformrecord,
|
|
2758
3344
|
validatestep,
|