@wenathlan/extension 1.1.51 → 1.1.52
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 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +657 -3
- package/dist/index.js.map +4 -4
- package/dist/memory.d.ts +38 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/policy.d.ts +13 -0
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +62 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/trigger.d.ts +151 -0
- package/dist/trigger.d.ts.map +1 -0
- package/dist/types.d.ts +98 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +1068 -5
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/manifest.json +1 -1
- package/extension/dist/pagebridge.js +1 -1
- package/extension/dist/pagebridge.js.map +2 -2
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +24 -1
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sidepanel.html +1 -1
- package/extension/dist/sidepanel.js +159 -1
- package/extension/dist/sidepanel.js.map +2 -2
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -2211,6 +2211,77 @@ var sessionmemory = class {
|
|
|
2211
2211
|
}
|
|
2212
2212
|
return history2;
|
|
2213
2213
|
}
|
|
2214
|
+
/** Stores one armed trigger rule with its workflow reference; re-arming the same id replaces the rule while the fire history survives. */
|
|
2215
|
+
async addtriggerule(rule) {
|
|
2216
|
+
const rules = (await this.gettriggerules()).filter((entry) => entry.id !== rule.id);
|
|
2217
|
+
await this.adapter.set("triggerules", [rule, ...rules]);
|
|
2218
|
+
}
|
|
2219
|
+
/** Returns every armed trigger rule, newest first. */
|
|
2220
|
+
async gettriggerules() {
|
|
2221
|
+
return await this.adapter.get("triggerules") ?? [];
|
|
2222
|
+
}
|
|
2223
|
+
/** Returns one armed trigger rule by its id. */
|
|
2224
|
+
async gettriggerule(id) {
|
|
2225
|
+
return (await this.gettriggerules()).find((rule) => rule.id === id);
|
|
2226
|
+
}
|
|
2227
|
+
/** Replaces one stored rule after an enable, disable, pause, resume, cooldown or fire bookkeeping change. */
|
|
2228
|
+
async settriggerule(rule) {
|
|
2229
|
+
const rules = await this.gettriggerules();
|
|
2230
|
+
await this.adapter.set("triggerules", rules.map((entry) => entry.id === rule.id ? rule : entry));
|
|
2231
|
+
}
|
|
2232
|
+
/** Replaces every stored rule at once so the session pause and resume suspend and release the whole rule set atomically. */
|
|
2233
|
+
async settriggerules(rules) {
|
|
2234
|
+
return this.adapter.set("triggerules", rules);
|
|
2235
|
+
}
|
|
2236
|
+
/** Removes one armed rule when the user disarms it; the fire history survives for the audit trail. */
|
|
2237
|
+
async removetriggerule(id) {
|
|
2238
|
+
await this.adapter.set("triggerules", (await this.gettriggerules()).filter((rule) => rule.id !== id));
|
|
2239
|
+
}
|
|
2240
|
+
/** Lists every armed rule joined with the name of its composed workflow so the trigger list shows what each rule launches. */
|
|
2241
|
+
async listtriggers() {
|
|
2242
|
+
const rules = await this.gettriggerules();
|
|
2243
|
+
const names = new Map((await this.listworkflows()).map((record2) => [record2.id, record2.name]));
|
|
2244
|
+
return rules.map((rule) => ({ rule, ...names.has(rule.workflowid) ? { workflowname: names.get(rule.workflowid) } : {} }));
|
|
2245
|
+
}
|
|
2246
|
+
/** Records one trigger fire with the reviewed retention window; an absent window keeps every fire record while the rule counters always survive. */
|
|
2247
|
+
async addtriggerfire(fire) {
|
|
2248
|
+
const fires = await this.listtriggerfires();
|
|
2249
|
+
const combined = [fire, ...fires];
|
|
2250
|
+
const retention = (await this.getsettings())?.triggerretention;
|
|
2251
|
+
await this.adapter.set("triggerfires", retention === void 0 ? combined : combined.slice(0, retention));
|
|
2252
|
+
}
|
|
2253
|
+
/** Returns every stored trigger fire record, newest first, optionally filtered to one rule. */
|
|
2254
|
+
async listtriggerfires(ruleid) {
|
|
2255
|
+
const fires = await this.adapter.get("triggerfires") ?? [];
|
|
2256
|
+
return ruleid === void 0 ? fires : fires.filter((fire) => fire.ruleid === ruleid);
|
|
2257
|
+
}
|
|
2258
|
+
/** Stores the pending trigger queue: fires that arrived while the target run was busy or the session paused; the resume drains them through the same gates. */
|
|
2259
|
+
async settriggerqueue(queue) {
|
|
2260
|
+
return this.adapter.set("triggerqueue", queue);
|
|
2261
|
+
}
|
|
2262
|
+
/** Returns the pending trigger queue, oldest first. */
|
|
2263
|
+
async gettriggerqueue() {
|
|
2264
|
+
return await this.adapter.get("triggerqueue") ?? [];
|
|
2265
|
+
}
|
|
2266
|
+
/** Stores one verified webhook payload of a rule; the executor verifies the shared secret and the schema before anything persists. */
|
|
2267
|
+
async addwebhookpayload(ruleid, payload, at) {
|
|
2268
|
+
const stored = await this.adapter.get("webhookpayloads") ?? [];
|
|
2269
|
+
await this.adapter.set("webhookpayloads", [{ ruleid, payload, at }, ...stored]);
|
|
2270
|
+
}
|
|
2271
|
+
/** Returns the stored webhook payloads of one rule, newest first; only secret verified deliveries ever reach this store. */
|
|
2272
|
+
async listwebhookpayloads(ruleid) {
|
|
2273
|
+
const stored = await this.adapter.get("webhookpayloads") ?? [];
|
|
2274
|
+
return stored.filter((entry) => entry.ruleid === ruleid);
|
|
2275
|
+
}
|
|
2276
|
+
/** Stores one manual run preview with its step list so the panel renders it before confirmation. */
|
|
2277
|
+
async addmanualrun(preview) {
|
|
2278
|
+
const runs = (await this.listmanualruns()).filter((entry) => entry.id !== preview.id);
|
|
2279
|
+
await this.adapter.set("manualruns", [preview, ...runs]);
|
|
2280
|
+
}
|
|
2281
|
+
/** Returns every stored manual run preview with its confirmation outcome, newest first. */
|
|
2282
|
+
async listmanualruns() {
|
|
2283
|
+
return await this.adapter.get("manualruns") ?? [];
|
|
2284
|
+
}
|
|
2214
2285
|
};
|
|
2215
2286
|
function mediakindof(record2) {
|
|
2216
2287
|
if ("pages" in record2) return "pdf";
|
|
@@ -4385,6 +4456,394 @@ function dryrunworkflow(input) {
|
|
|
4385
4456
|
return { run: { ...run, state: "done", cursor: input.record.steps.length, endedat: input.now }, scopes, log };
|
|
4386
4457
|
}
|
|
4387
4458
|
|
|
4459
|
+
// trigger.ts
|
|
4460
|
+
var triggerkinds = ["visitrule", "urlrule", "menurule", "keyrule", "buttonrule", "cronrule", "intervalrule", "urllistrule", "webhookrule", "eventrule"];
|
|
4461
|
+
var triggerfamilies = ["visit", "url", "menu", "key", "button", "cron", "interval", "urllist", "webhook", "event"];
|
|
4462
|
+
var triggereventcatalog = ["mutate", "focus", "banner", "console", "error", "navigate"];
|
|
4463
|
+
var defaulttriggercooldown = 1e4;
|
|
4464
|
+
function triggerfamilyof(kind) {
|
|
4465
|
+
const index = triggerkinds.indexOf(kind);
|
|
4466
|
+
return index >= 0 ? triggerfamilies[index] : void 0;
|
|
4467
|
+
}
|
|
4468
|
+
function triggerlabel(value) {
|
|
4469
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
4470
|
+
}
|
|
4471
|
+
function positivewindow(value) {
|
|
4472
|
+
if (value === void 0) return void 0;
|
|
4473
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
4474
|
+
}
|
|
4475
|
+
function jitterwindow(value) {
|
|
4476
|
+
if (value === void 0) return void 0;
|
|
4477
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
4478
|
+
}
|
|
4479
|
+
function httpsorigin(value) {
|
|
4480
|
+
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
4481
|
+
try {
|
|
4482
|
+
const parsed = new URL(value.trim());
|
|
4483
|
+
if (parsed.protocol !== "https:") return void 0;
|
|
4484
|
+
return parsed.origin;
|
|
4485
|
+
} catch {
|
|
4486
|
+
return void 0;
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4489
|
+
function webhookfieldof(value) {
|
|
4490
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
4491
|
+
const candidate = value;
|
|
4492
|
+
if (typeof candidate.name !== "string" || !/^[a-z][a-z0-9]*$/i.test(candidate.name)) return void 0;
|
|
4493
|
+
if (candidate.kind !== "string" && candidate.kind !== "number" && candidate.kind !== "boolean") return void 0;
|
|
4494
|
+
if (candidate.required !== void 0 && typeof candidate.required !== "boolean") return void 0;
|
|
4495
|
+
return { name: candidate.name, kind: candidate.kind, ...candidate.required === true ? { required: true } : {} };
|
|
4496
|
+
}
|
|
4497
|
+
function triggerpayloadof(family, value) {
|
|
4498
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
4499
|
+
const candidate = value;
|
|
4500
|
+
if (family === "visit") {
|
|
4501
|
+
if (!Array.isArray(candidate.origins) || candidate.origins.length === 0) return void 0;
|
|
4502
|
+
const origins = candidate.origins.map((origin) => httpsorigin(origin));
|
|
4503
|
+
if (origins.some((origin) => origin === void 0)) return void 0;
|
|
4504
|
+
return { origins: [...new Set(origins)] };
|
|
4505
|
+
}
|
|
4506
|
+
if (family === "url") {
|
|
4507
|
+
if (typeof candidate.pattern !== "string" || !candidate.pattern.trim()) return void 0;
|
|
4508
|
+
if (httpsorigin(candidate.pattern) === void 0) return void 0;
|
|
4509
|
+
return { pattern: candidate.pattern.trim() };
|
|
4510
|
+
}
|
|
4511
|
+
if (family === "menu") {
|
|
4512
|
+
const title = triggerlabel(candidate.title);
|
|
4513
|
+
if (!title) return void 0;
|
|
4514
|
+
return { title };
|
|
4515
|
+
}
|
|
4516
|
+
if (family === "key") {
|
|
4517
|
+
if (typeof candidate.command !== "string" || !/^[a-z][a-z0-9-]*$/.test(candidate.command)) return void 0;
|
|
4518
|
+
if (candidate.key !== void 0 && (typeof candidate.key !== "string" || !candidate.key.trim())) return void 0;
|
|
4519
|
+
return { command: candidate.command, ...candidate.key !== void 0 ? { key: candidate.key } : {} };
|
|
4520
|
+
}
|
|
4521
|
+
if (family === "button") return {};
|
|
4522
|
+
if (family === "cron") {
|
|
4523
|
+
if (typeof candidate.cron !== "string" || !candidate.cron.trim()) return void 0;
|
|
4524
|
+
if (cronparse(candidate.cron) === void 0) return void 0;
|
|
4525
|
+
if (candidate.timezone !== void 0 && (typeof candidate.timezone !== "string" || !timezonevalid(candidate.timezone))) return void 0;
|
|
4526
|
+
return { cron: candidate.cron.trim(), ...candidate.timezone !== void 0 ? { timezone: candidate.timezone } : {} };
|
|
4527
|
+
}
|
|
4528
|
+
if (family === "interval") {
|
|
4529
|
+
const period = positivewindow(candidate.period);
|
|
4530
|
+
if (period === void 0) return void 0;
|
|
4531
|
+
const jitter = jitterwindow(candidate.jitter);
|
|
4532
|
+
if (candidate.jitter !== void 0 && jitter === void 0) return void 0;
|
|
4533
|
+
return { period, ...jitter !== void 0 ? { jitter } : {} };
|
|
4534
|
+
}
|
|
4535
|
+
if (family === "urllist") {
|
|
4536
|
+
if (!Array.isArray(candidate.urls) || candidate.urls.length === 0) return void 0;
|
|
4537
|
+
const urls = candidate.urls.map((url) => httpsorigin(url) === void 0 ? void 0 : url.trim());
|
|
4538
|
+
if (urls.some((url) => url === void 0)) return void 0;
|
|
4539
|
+
return { urls };
|
|
4540
|
+
}
|
|
4541
|
+
if (family === "webhook") {
|
|
4542
|
+
if (typeof candidate.secret !== "string" || !webhooksecretok(candidate.secret)) return void 0;
|
|
4543
|
+
if (!Array.isArray(candidate.schema) || candidate.schema.length === 0) return void 0;
|
|
4544
|
+
const schema = candidate.schema.map((field) => webhookfieldof(field));
|
|
4545
|
+
if (schema.some((field) => field === void 0)) return void 0;
|
|
4546
|
+
const names = schema.map((field) => field.name);
|
|
4547
|
+
if (new Set(names).size !== names.length) return void 0;
|
|
4548
|
+
return { secret: candidate.secret, schema };
|
|
4549
|
+
}
|
|
4550
|
+
const events = candidate.events;
|
|
4551
|
+
if (!Array.isArray(events) || events.length === 0) return void 0;
|
|
4552
|
+
if (!events.every((name) => typeof name === "string" && triggereventcatalog.includes(name))) return void 0;
|
|
4553
|
+
return { events: [...new Set(events)] };
|
|
4554
|
+
}
|
|
4555
|
+
function webhooksecretok(secret) {
|
|
4556
|
+
if (secret.length < 24) return false;
|
|
4557
|
+
if (/^(.)\1+$/.test(secret)) return false;
|
|
4558
|
+
return /[a-z]/i.test(secret) && /\d/.test(secret);
|
|
4559
|
+
}
|
|
4560
|
+
function timezonevalid(timezone) {
|
|
4561
|
+
try {
|
|
4562
|
+
new Intl.DateTimeFormat("en-US", { timeZone: timezone });
|
|
4563
|
+
return true;
|
|
4564
|
+
} catch {
|
|
4565
|
+
return false;
|
|
4566
|
+
}
|
|
4567
|
+
}
|
|
4568
|
+
function armrule(input) {
|
|
4569
|
+
if (typeof input.workflowid !== "string" || !input.workflowid.trim()) return void 0;
|
|
4570
|
+
const payload = triggerpayloadof(input.family, input.payload);
|
|
4571
|
+
if (!payload) return void 0;
|
|
4572
|
+
if (input.cooldown !== void 0 && (typeof input.cooldown !== "number" || !Number.isFinite(input.cooldown) || input.cooldown <= 0)) return void 0;
|
|
4573
|
+
const cooldown = input.cooldown ?? (input.family === "webhook" || input.family === "event" ? defaulttriggercooldown : 0);
|
|
4574
|
+
const label = input.label ?? `The ${input.family} rule of ${input.workflowid}`;
|
|
4575
|
+
return { id: input.id ?? crypto.randomUUID(), kind: input.family, workflowid: input.workflowid, label, ...payload, cooldown, state: { enabled: true, cooldown }, stats: { fires: 0, launches: 0, suppressions: 0 }, createdat: input.now };
|
|
4576
|
+
}
|
|
4577
|
+
function updaterule(rule, patch) {
|
|
4578
|
+
return { ...rule, ...patch.state !== void 0 ? { state: { ...rule.state, ...patch.state } } : {}, ...patch.stats !== void 0 ? { stats: { ...rule.stats, ...patch.stats } } : {} };
|
|
4579
|
+
}
|
|
4580
|
+
function matchurl(pattern, url) {
|
|
4581
|
+
let parsedpattern;
|
|
4582
|
+
let parsedurl;
|
|
4583
|
+
try {
|
|
4584
|
+
parsedpattern = new URL(pattern);
|
|
4585
|
+
parsedurl = new URL(url);
|
|
4586
|
+
} catch {
|
|
4587
|
+
return false;
|
|
4588
|
+
}
|
|
4589
|
+
if (parsedpattern.protocol !== parsedurl.protocol) return false;
|
|
4590
|
+
if (parsedpattern.hostname !== parsedurl.hostname) return false;
|
|
4591
|
+
if (parsedpattern.port !== "" && parsedpattern.port !== parsedurl.port) return false;
|
|
4592
|
+
return globmatch(`${parsedpattern.pathname}${parsedpattern.search}`, `${parsedurl.pathname}${parsedurl.search}`);
|
|
4593
|
+
}
|
|
4594
|
+
function globmatch(pattern, text2) {
|
|
4595
|
+
const escaped = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4596
|
+
const expression = new RegExp(["^", pattern.split("**").map((segment) => segment.split("*").map(escaped).join("[^/]*")).join(".*"), "$"].join(""));
|
|
4597
|
+
return expression.test(text2);
|
|
4598
|
+
}
|
|
4599
|
+
function visitmatch(origins, url) {
|
|
4600
|
+
let origin = "";
|
|
4601
|
+
try {
|
|
4602
|
+
origin = new URL(url).origin;
|
|
4603
|
+
} catch {
|
|
4604
|
+
return false;
|
|
4605
|
+
}
|
|
4606
|
+
return origins.includes(origin);
|
|
4607
|
+
}
|
|
4608
|
+
function cronparse(expression) {
|
|
4609
|
+
const fields = expression.trim().split(/\s+/);
|
|
4610
|
+
if (fields.length !== 5) return void 0;
|
|
4611
|
+
const minutes = cronfield(fields[0] ?? "", 0, 59);
|
|
4612
|
+
const hours = cronfield(fields[1] ?? "", 0, 23);
|
|
4613
|
+
const daysofmonth = cronfield(fields[2] ?? "", 1, 31);
|
|
4614
|
+
const months = cronfield(fields[3] ?? "", 1, 12, monthnames);
|
|
4615
|
+
const daysofweek = cronfield(fields[4] ?? "", 0, 7, weekdaynames, true);
|
|
4616
|
+
if (!minutes || !hours || !daysofmonth || !months || !daysofweek) return void 0;
|
|
4617
|
+
return { minutes, hours, daysofmonth, months, daysofweek: [...new Set(daysofweek.map((day) => day % 7))].sort((left, right) => left - right) };
|
|
4618
|
+
}
|
|
4619
|
+
var weekdaynames = { sun: 0, mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6 };
|
|
4620
|
+
var monthnames = { jan: 1, feb: 2, mar: 3, apr: 4, may: 5, jun: 6, jul: 7, aug: 8, sep: 9, oct: 10, nov: 11, dec: 12 };
|
|
4621
|
+
function cronfield(field, min, max, names, sundayseven = false) {
|
|
4622
|
+
const values = /* @__PURE__ */ new Set();
|
|
4623
|
+
for (const part of field.split(",")) {
|
|
4624
|
+
if (!part) return void 0;
|
|
4625
|
+
const [range, stepstring] = part.split("/");
|
|
4626
|
+
const step = stepstring === void 0 ? 1 : Number(stepstring);
|
|
4627
|
+
if (!Number.isInteger(step) || step < 1) return void 0;
|
|
4628
|
+
let low = min;
|
|
4629
|
+
let high = max;
|
|
4630
|
+
if (range !== void 0 && range !== "*") {
|
|
4631
|
+
const bounds = range.split("-");
|
|
4632
|
+
if (bounds.length > 2) return void 0;
|
|
4633
|
+
const lowvalue = cronvalue(bounds[0] ?? "", min, max, names);
|
|
4634
|
+
if (lowvalue === void 0) return void 0;
|
|
4635
|
+
low = lowvalue;
|
|
4636
|
+
high = lowvalue;
|
|
4637
|
+
if (bounds.length === 2) {
|
|
4638
|
+
const highvalue = cronvalue(bounds[1] ?? "", min, max, names);
|
|
4639
|
+
if (highvalue === void 0 || highvalue < lowvalue) return void 0;
|
|
4640
|
+
high = highvalue;
|
|
4641
|
+
}
|
|
4642
|
+
}
|
|
4643
|
+
for (let value = low; value <= high; value += step) values.add(value);
|
|
4644
|
+
}
|
|
4645
|
+
const list = [...values];
|
|
4646
|
+
if (list.some((value) => value < min || value > max)) return void 0;
|
|
4647
|
+
if (sundayseven && values.has(7)) {
|
|
4648
|
+
values.delete(7);
|
|
4649
|
+
values.add(0);
|
|
4650
|
+
}
|
|
4651
|
+
return [...values].sort((left, right) => left - right);
|
|
4652
|
+
}
|
|
4653
|
+
function cronvalue(value, min, max, names) {
|
|
4654
|
+
const candidate = names?.[value.toLowerCase()];
|
|
4655
|
+
if (candidate !== void 0) return candidate;
|
|
4656
|
+
if (!/^\d+$/.test(value)) return void 0;
|
|
4657
|
+
const parsed = Number(value);
|
|
4658
|
+
if (parsed < min || parsed > max) return void 0;
|
|
4659
|
+
return parsed;
|
|
4660
|
+
}
|
|
4661
|
+
function calendarparts(at, timezone) {
|
|
4662
|
+
if (timezone === void 0) {
|
|
4663
|
+
const date = new Date(at);
|
|
4664
|
+
return { minute: date.getUTCMinutes(), hour: date.getUTCHours(), day: date.getUTCDate(), month: date.getUTCMonth() + 1, weekday: date.getUTCDay() };
|
|
4665
|
+
}
|
|
4666
|
+
const parts = new Intl.DateTimeFormat("en-US", { timeZone: timezone, hourCycle: "h23", minute: "numeric", hour: "numeric", day: "numeric", month: "short", weekday: "short" }).formatToParts(new Date(at));
|
|
4667
|
+
const pick = (type) => parts.find((part) => part.type === type)?.value ?? "";
|
|
4668
|
+
const weekday = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].indexOf(pick("weekday"));
|
|
4669
|
+
const month = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"].indexOf(pick("month")) + 1;
|
|
4670
|
+
return { minute: Number(pick("minute")), hour: Number(pick("hour")), day: Number(pick("day")), month, weekday };
|
|
4671
|
+
}
|
|
4672
|
+
function crondaymatch(schedule, parts) {
|
|
4673
|
+
if (!schedule.months.includes(parts.month)) return false;
|
|
4674
|
+
const domfull = schedule.daysofmonth.length === 31;
|
|
4675
|
+
const dowfull = schedule.daysofweek.length === 7;
|
|
4676
|
+
const dommatch = schedule.daysofmonth.includes(parts.day);
|
|
4677
|
+
const dowmatch = schedule.daysofweek.includes(parts.weekday);
|
|
4678
|
+
if (!domfull && !dowfull) return dommatch || dowmatch;
|
|
4679
|
+
if (!domfull) return dommatch;
|
|
4680
|
+
if (!dowfull) return dowmatch;
|
|
4681
|
+
return true;
|
|
4682
|
+
}
|
|
4683
|
+
function cronnext(expression, from, timezone) {
|
|
4684
|
+
const schedule = cronparse(expression);
|
|
4685
|
+
if (!schedule) return void 0;
|
|
4686
|
+
const minute = 6e4;
|
|
4687
|
+
const hour = 60 * minute;
|
|
4688
|
+
const day = 24 * hour;
|
|
4689
|
+
let candidate = Math.floor(from / minute) * minute + minute;
|
|
4690
|
+
const horizon = from + 4 * 366 * day;
|
|
4691
|
+
while (candidate <= horizon) {
|
|
4692
|
+
const parts = calendarparts(candidate, timezone);
|
|
4693
|
+
if (!crondaymatch(schedule, parts)) {
|
|
4694
|
+
candidate += day - parts.hour * hour - parts.minute * minute;
|
|
4695
|
+
continue;
|
|
4696
|
+
}
|
|
4697
|
+
if (!schedule.hours.includes(parts.hour)) {
|
|
4698
|
+
const later = schedule.hours.find((value) => value > parts.hour);
|
|
4699
|
+
candidate += later === void 0 ? day - parts.hour * hour - parts.minute * minute : (later - parts.hour) * hour - parts.minute * minute;
|
|
4700
|
+
continue;
|
|
4701
|
+
}
|
|
4702
|
+
if (!schedule.minutes.includes(parts.minute)) {
|
|
4703
|
+
const later = schedule.minutes.find((value) => value > parts.minute);
|
|
4704
|
+
candidate += later === void 0 ? (60 - parts.minute) * minute : (later - parts.minute) * minute;
|
|
4705
|
+
continue;
|
|
4706
|
+
}
|
|
4707
|
+
return candidate;
|
|
4708
|
+
}
|
|
4709
|
+
return void 0;
|
|
4710
|
+
}
|
|
4711
|
+
function schedulecron(rule, from) {
|
|
4712
|
+
return cronnext(rule.cron, from, rule.timezone);
|
|
4713
|
+
}
|
|
4714
|
+
function scheduleinterval(rule, lastfire, armedat, seed) {
|
|
4715
|
+
const base = (lastfire ?? armedat) + rule.period;
|
|
4716
|
+
const jitter = rule.jitter ?? 0;
|
|
4717
|
+
if (jitter <= 0) return base;
|
|
4718
|
+
return Math.max(0, Math.round(base - jitter / 2 + seededrandom(seed) * jitter));
|
|
4719
|
+
}
|
|
4720
|
+
function listdue(rules, now) {
|
|
4721
|
+
return rules.flatMap((rule) => {
|
|
4722
|
+
if (rule.state.nextfireat === void 0 || rule.state.nextfireat > now) return [];
|
|
4723
|
+
if (!rule.state.enabled || rule.state.pausedat !== void 0) return [];
|
|
4724
|
+
return [{ rule, overdueby: now - rule.state.nextfireat }];
|
|
4725
|
+
});
|
|
4726
|
+
}
|
|
4727
|
+
function applycooldown(rule, now) {
|
|
4728
|
+
const lastfireat = rule.state.lastfireat;
|
|
4729
|
+
if (lastfireat === void 0 || rule.state.cooldown <= 0) return { suppressed: false, remaining: 0 };
|
|
4730
|
+
const remaining = lastfireat + rule.state.cooldown - now;
|
|
4731
|
+
return { suppressed: remaining > 0, remaining: Math.max(0, remaining) };
|
|
4732
|
+
}
|
|
4733
|
+
function evaluatetrigger(input) {
|
|
4734
|
+
const rule = input.rule;
|
|
4735
|
+
if (!rule.state.enabled) return { fired: false, suppressed: "disabled" };
|
|
4736
|
+
if (rule.state.pausedat !== void 0) return { fired: false, suppressed: "paused" };
|
|
4737
|
+
if (!input.workflowreviewed) return { fired: false, suppressed: "unreviewed" };
|
|
4738
|
+
if (input.runactive) return { fired: false, suppressed: "dedupe" };
|
|
4739
|
+
const cooldown = applycooldown(rule, input.now);
|
|
4740
|
+
if (cooldown.suppressed) return { fired: false, suppressed: "cooldown", remaining: cooldown.remaining };
|
|
4741
|
+
const fire = { id: crypto.randomUUID(), ruleid: rule.id, at: input.now, cause: input.cause, ...input.url !== void 0 ? { url: input.url } : {}, ...input.title !== void 0 ? { title: input.title } : {}, ...input.payload !== void 0 ? { payload: input.payload } : {} };
|
|
4742
|
+
return { fired: true, fire };
|
|
4743
|
+
}
|
|
4744
|
+
function queuefire(queue, fire) {
|
|
4745
|
+
if (queue.some((pending) => pending.ruleid === fire.ruleid)) return { queue, queued: false, deduped: true };
|
|
4746
|
+
return { queue: [...queue, fire], queued: true, deduped: false };
|
|
4747
|
+
}
|
|
4748
|
+
async function drainqueue(queue, launch) {
|
|
4749
|
+
let remaining = [...queue];
|
|
4750
|
+
let launched = 0;
|
|
4751
|
+
while (remaining.length > 0) {
|
|
4752
|
+
const fire = remaining[0];
|
|
4753
|
+
try {
|
|
4754
|
+
await launch(fire);
|
|
4755
|
+
} catch {
|
|
4756
|
+
return { launched, remaining };
|
|
4757
|
+
}
|
|
4758
|
+
remaining = remaining.slice(1);
|
|
4759
|
+
launched += 1;
|
|
4760
|
+
}
|
|
4761
|
+
return { launched, remaining };
|
|
4762
|
+
}
|
|
4763
|
+
function verifywebhook(input) {
|
|
4764
|
+
if (typeof input.rule.secret !== "string" || !input.rule.secret) return { verified: false, reason: "The webhook rule carries no reviewed secret." };
|
|
4765
|
+
if (!secrectsmatch(input.secret, input.rule.secret)) return { verified: false, reason: "The webhook secret does not match the reviewed secret of the rule." };
|
|
4766
|
+
const payload = input.payload;
|
|
4767
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return { verified: false, reason: "The webhook payload must be a JSON object." };
|
|
4768
|
+
const candidate = payload;
|
|
4769
|
+
for (const field of input.rule.schema ?? []) {
|
|
4770
|
+
const value = candidate[field.name];
|
|
4771
|
+
if (value === void 0) {
|
|
4772
|
+
if (field.required === true) return { verified: false, reason: `The required webhook field ${field.name} is missing.` };
|
|
4773
|
+
continue;
|
|
4774
|
+
}
|
|
4775
|
+
if (typeof value !== field.kind) return { verified: false, reason: `The webhook field ${field.name} is not a ${field.kind}.` };
|
|
4776
|
+
}
|
|
4777
|
+
return { verified: true };
|
|
4778
|
+
}
|
|
4779
|
+
function secrectsmatch(left, right) {
|
|
4780
|
+
if (left.length !== right.length) return false;
|
|
4781
|
+
let same = true;
|
|
4782
|
+
for (let index = 0; index < left.length; index += 1) if (left.charCodeAt(index) !== right.charCodeAt(index)) same = false;
|
|
4783
|
+
return same;
|
|
4784
|
+
}
|
|
4785
|
+
function eventrulematches(rule, event) {
|
|
4786
|
+
return (rule.events ?? []).includes(event);
|
|
4787
|
+
}
|
|
4788
|
+
function observeevents(rules, event) {
|
|
4789
|
+
if (!triggereventcatalog.includes(event)) return [];
|
|
4790
|
+
return rules.filter((rule) => rule.kind === "event" && rule.state.enabled && rule.state.pausedat === void 0 && eventrulematches(rule, event));
|
|
4791
|
+
}
|
|
4792
|
+
function pauseall(rules, now) {
|
|
4793
|
+
return rules.map((rule) => rule.state.enabled && rule.state.pausedat === void 0 ? updaterule(rule, { state: { pausedat: now } }) : rule);
|
|
4794
|
+
}
|
|
4795
|
+
function resumeall(rules) {
|
|
4796
|
+
return rules.map((rule) => {
|
|
4797
|
+
if (rule.state.pausedat === void 0) return rule;
|
|
4798
|
+
const state = { ...rule.state };
|
|
4799
|
+
delete state.pausedat;
|
|
4800
|
+
return { ...rule, state };
|
|
4801
|
+
});
|
|
4802
|
+
}
|
|
4803
|
+
function manualpreview(record2, now) {
|
|
4804
|
+
return { id: crypto.randomUUID(), workflowid: record2.id, preview: record2.steps.map((step) => ({ stepid: step.id, kind: step.kind, label: step.label, ...step.block !== void 0 ? { block: step.block } : {}, ...controlsummary(step) !== void 0 ? { control: controlsummary(step) } : {} })), at: now };
|
|
4805
|
+
}
|
|
4806
|
+
function confirmmanualrun(preview, confirmed, now) {
|
|
4807
|
+
return { ...preview, confirmed, decidedat: now };
|
|
4808
|
+
}
|
|
4809
|
+
function triggersummary(rule) {
|
|
4810
|
+
return {
|
|
4811
|
+
kind: rule.kind,
|
|
4812
|
+
workflowid: rule.workflowid,
|
|
4813
|
+
label: rule.label,
|
|
4814
|
+
...rule.origins !== void 0 ? { origins: rule.origins } : {},
|
|
4815
|
+
...rule.pattern !== void 0 ? { pattern: rule.pattern } : {},
|
|
4816
|
+
...rule.title !== void 0 ? { title: rule.title } : {},
|
|
4817
|
+
...rule.command !== void 0 ? { command: rule.command } : {},
|
|
4818
|
+
...rule.key !== void 0 ? { key: rule.key } : {},
|
|
4819
|
+
...rule.cron !== void 0 ? { cron: rule.cron, ...rule.timezone !== void 0 ? { timezone: rule.timezone } : {} } : {},
|
|
4820
|
+
...rule.period !== void 0 ? { period: rule.period, ...rule.jitter !== void 0 ? { jitter: rule.jitter } : {} } : {},
|
|
4821
|
+
...rule.urls !== void 0 ? { urls: rule.urls } : {},
|
|
4822
|
+
...rule.events !== void 0 ? { events: rule.events } : {},
|
|
4823
|
+
...rule.schema !== void 0 ? { fields: rule.schema.length } : {},
|
|
4824
|
+
cooldown: rule.state.cooldown,
|
|
4825
|
+
enabled: rule.state.enabled,
|
|
4826
|
+
...rule.state.nextfireat !== void 0 ? { nextfireat: rule.state.nextfireat } : {}
|
|
4827
|
+
};
|
|
4828
|
+
}
|
|
4829
|
+
function ruleorigins(rule) {
|
|
4830
|
+
const origins = /* @__PURE__ */ new Set();
|
|
4831
|
+
for (const origin of rule.origins ?? []) origins.add(origin);
|
|
4832
|
+
if (rule.pattern !== void 0) {
|
|
4833
|
+
const origin = httpsorigin(rule.pattern);
|
|
4834
|
+
if (origin !== void 0) origins.add(origin);
|
|
4835
|
+
}
|
|
4836
|
+
for (const url of rule.urls ?? []) {
|
|
4837
|
+
const origin = httpsorigin(url);
|
|
4838
|
+
if (origin !== void 0) origins.add(origin);
|
|
4839
|
+
}
|
|
4840
|
+
return [...origins];
|
|
4841
|
+
}
|
|
4842
|
+
function ruleoriginsgranted(rule, workfloworigins) {
|
|
4843
|
+
const granted = new Set(workfloworigins);
|
|
4844
|
+
return ruleorigins(rule).every((origin) => granted.has(origin));
|
|
4845
|
+
}
|
|
4846
|
+
|
|
4388
4847
|
// netauth.ts
|
|
4389
4848
|
function oauthflowof(value) {
|
|
4390
4849
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
@@ -4618,7 +5077,7 @@ function consolediff(input) {
|
|
|
4618
5077
|
}
|
|
4619
5078
|
|
|
4620
5079
|
// policy.ts
|
|
4621
|
-
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", "sendmessage", "blockrequest", "mockresponse", "rewriteheaders", "setcookies", "clearcookies", "authflow", "saveapikey", "routeproxy", "postform", "postfiles", "attachcdp", "detachcdp", "cdpcmd", "overridescript", "heapshot", "profilecpu", "capturesourcemaps", "emulatedevice", "emulatenetwork", "emulatelocate", "setuseragent", "overridepermission", "restoresession", "exportsessions", "importsessions", "runworkflow"]);
|
|
5080
|
+
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", "sendmessage", "blockrequest", "mockresponse", "rewriteheaders", "setcookies", "clearcookies", "authflow", "saveapikey", "routeproxy", "postform", "postfiles", "attachcdp", "detachcdp", "cdpcmd", "overridescript", "heapshot", "profilecpu", "capturesourcemaps", "emulatedevice", "emulatenetwork", "emulatelocate", "setuseragent", "overridepermission", "restoresession", "exportsessions", "importsessions", "runworkflow", "visitrule", "urlrule", "menurule", "keyrule", "buttonrule", "cronrule", "intervalrule", "urllistrule", "webhookrule", "eventrule"]);
|
|
4622
5081
|
var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset", "movepointer", "clicktext", "clickaria", "clickname", "expanddetails", "pierceshadow", "retryaction", "capturebodies", "setbreakpoint", "stepcode", "watchexpr", "loop", "repeatuntil", "whileloop", "foreach", "parallel", "trycatch"]);
|
|
4623
5082
|
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", "opensocket", "waitmessage", "watchrequests", "readheaders", "mapapi", "subscribesse", "longpoll", "extractapi", "readcookies", "watchconsole", "watcherrors", "watchtasks", "watchcdp", "measureflow", "trackmemory", "watchshifts", "traceload", "annotatetrace", "replaytrace", "blackboxscripts", "persiststate", "capturesession", "namedsessions", "diffsessions", "searchsessions", "composeworkflow", "savetemplate", "dryrun", "delay", "waitelement", "compute", "extractvars", "condition", "branch"]);
|
|
4624
5083
|
var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
|
|
@@ -4642,6 +5101,7 @@ var profileractions = /* @__PURE__ */ new Set(["measureflow", "heapshot", "track
|
|
|
4642
5101
|
var emulationactions = /* @__PURE__ */ new Set(["emulatedevice", "emulatenetwork", "emulatelocate", "setuseragent", "overridepermission", "blackboxscripts"]);
|
|
4643
5102
|
var sessionactions = /* @__PURE__ */ new Set(["persiststate", "capturesession", "restoresession", "namedsessions", "diffsessions", "searchsessions", "exportsessions", "importsessions"]);
|
|
4644
5103
|
var workflowactions = /* @__PURE__ */ new Set(["composeworkflow", "savetemplate", "runworkflow", "dryrun", "delay", "waitelement", "compute", "extractvars", "condition", "branch", "loop", "repeatuntil", "whileloop", "foreach", "parallel", "trycatch"]);
|
|
5104
|
+
var triggeractions = /* @__PURE__ */ new Set(["visitrule", "urlrule", "menurule", "keyrule", "buttonrule", "cronrule", "intervalrule", "urllistrule", "webhookrule", "eventrule"]);
|
|
4645
5105
|
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"]);
|
|
4646
5106
|
var fieldkinds = ["text", "email", "phone", "date", "number", "select", "check", "radio", "file", "password", "card", "code"];
|
|
4647
5107
|
var layoutmutationactions = /* @__PURE__ */ new Set(["grouptabs", "colorgroup", "collapsegroup", "savelayout", "restorelayout"]);
|
|
@@ -4663,6 +5123,9 @@ function issessionkind(kind) {
|
|
|
4663
5123
|
function isworkflowkind(kind) {
|
|
4664
5124
|
return workflowactions.has(kind);
|
|
4665
5125
|
}
|
|
5126
|
+
function istriggeraction(kind) {
|
|
5127
|
+
return triggeractions.has(kind);
|
|
5128
|
+
}
|
|
4666
5129
|
function isdebugkind(kind) {
|
|
4667
5130
|
return debugactions.has(kind);
|
|
4668
5131
|
}
|
|
@@ -6408,6 +6871,79 @@ function workflowgate(input) {
|
|
|
6408
6871
|
}
|
|
6409
6872
|
return { allowed: true };
|
|
6410
6873
|
}
|
|
6874
|
+
function validatetriggergrammar(step, options) {
|
|
6875
|
+
const family = triggerfamilyof(step.kind);
|
|
6876
|
+
if (family === void 0) return { allowed: false, reason: "The trigger step is not a reviewed trigger kind." };
|
|
6877
|
+
if (typeof options.workflowid !== "string" || !options.workflowid.trim()) return { allowed: false, reason: "Every trigger rule needs the reviewed id of the composed workflow it launches." };
|
|
6878
|
+
if (options.reviewed !== true) return { allowed: false, reason: "Every trigger rule needs the explicit arm review with its match fields and bound workflow shown before it arms." };
|
|
6879
|
+
if (options.label !== void 0 && (typeof options.label !== "string" || !options.label.trim())) return { allowed: false, reason: "The reviewed trigger label must be a non-empty string." };
|
|
6880
|
+
if (options.cooldown !== void 0 && (typeof options.cooldown !== "number" || !Number.isFinite(options.cooldown) || options.cooldown <= 0)) return { allowed: false, reason: "The reviewed cooldown window must be a positive number of milliseconds with no code ceiling; the webhook and event families keep the documented default when the review configures none." };
|
|
6881
|
+
const payload = options.rule;
|
|
6882
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return { allowed: false, reason: `The ${step.kind} step needs its reviewed rule payload in options.` };
|
|
6883
|
+
if (triggerpayloadof(family, payload) === void 0) {
|
|
6884
|
+
if (family === "visit") return { allowed: false, reason: "The visit rule needs a non-empty reviewed list of HTTPS origins it fires on." };
|
|
6885
|
+
if (family === "url") return { allowed: false, reason: "The url rule needs a reviewed HTTPS glob url pattern; `*` spans one path segment and `**` spans across segments." };
|
|
6886
|
+
if (family === "menu") return { allowed: false, reason: "The menu rule needs a reviewed non-empty context menu entry title." };
|
|
6887
|
+
if (family === "key") return { allowed: false, reason: "The keyboard shortcut rule needs a reviewed lowercase command name and an optional suggested key binding." };
|
|
6888
|
+
if (family === "cron") return { allowed: false, reason: "The cron rule needs a reviewed five field cron expression of minutes, hours, days, months and weekdays with named weekdays and months and an optional resolvable timezone; unparseable schedules are refused." };
|
|
6889
|
+
if (family === "interval") return { allowed: false, reason: "The interval rule needs a reviewed positive period in milliseconds with an optional zero or positive jitter window." };
|
|
6890
|
+
if (family === "urllist") return { allowed: false, reason: "The url list rule needs a reviewed non-empty list of HTTPS urls its workflow runs across." };
|
|
6891
|
+
if (family === "webhook") return { allowed: false, reason: `The webhook rule needs a reviewed shared secret of at least twenty four characters mixing letters and digits and a non-empty payload schema of named string, number or boolean fields.` };
|
|
6892
|
+
if (family === "event") return { allowed: false, reason: `The page event rule needs a reviewed non-empty list of event names of the observed event catalog: ${triggereventcatalog.join(", ")}.` };
|
|
6893
|
+
return { allowed: false, reason: "The trigger rule payload does not follow its family grammar." };
|
|
6894
|
+
}
|
|
6895
|
+
if (family === "cron") {
|
|
6896
|
+
const candidate = payload;
|
|
6897
|
+
if (typeof candidate.cron === "string" && cronparse(candidate.cron) === void 0) return { allowed: false, reason: "The cron expression does not parse as a five field schedule and is refused." };
|
|
6898
|
+
}
|
|
6899
|
+
if (family === "webhook") {
|
|
6900
|
+
const candidate = payload;
|
|
6901
|
+
if (typeof candidate.secret === "string" && !webhooksecretok(candidate.secret)) return { allowed: false, reason: "The webhook shared secret must hold at least twenty four characters mixing letters and digits; the entropy floor is a floor, never a cap." };
|
|
6902
|
+
}
|
|
6903
|
+
const armed = armrule({ family, workflowid: options.workflowid, ...typeof options.label === "string" && options.label.trim() ? { label: options.label } : {}, payload, ...typeof options.cooldown === "number" ? { cooldown: options.cooldown } : {}, now: 0 });
|
|
6904
|
+
if (armed === void 0) return { allowed: false, reason: "The trigger rule payload does not arm as a reviewed rule." };
|
|
6905
|
+
return { allowed: true };
|
|
6906
|
+
}
|
|
6907
|
+
function triggergate(input) {
|
|
6908
|
+
const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now: input.now, action: "arm the trigger rule" });
|
|
6909
|
+
if (!gate.allowed) return gate;
|
|
6910
|
+
if (!input.plan || input.plan.state !== "approved") return { allowed: false, reason: "Trigger rules need the approved plan review before they arm." };
|
|
6911
|
+
let triggeroptions = {};
|
|
6912
|
+
try {
|
|
6913
|
+
triggeroptions = parseoptions(input.step);
|
|
6914
|
+
} catch {
|
|
6915
|
+
triggeroptions = {};
|
|
6916
|
+
}
|
|
6917
|
+
if (triggeroptions.reviewed !== true) return { allowed: false, reason: "Every trigger rule needs the explicit arm review with its match fields and bound workflow shown before it arms." };
|
|
6918
|
+
return { allowed: true };
|
|
6919
|
+
}
|
|
6920
|
+
function triggerorigins(step) {
|
|
6921
|
+
let triggeroptions = {};
|
|
6922
|
+
try {
|
|
6923
|
+
triggeroptions = parseoptions(step);
|
|
6924
|
+
} catch {
|
|
6925
|
+
return [];
|
|
6926
|
+
}
|
|
6927
|
+
const family = triggerfamilyof(step.kind);
|
|
6928
|
+
if (family === void 0) return [];
|
|
6929
|
+
const armed = armrule({ family, workflowid: typeof triggeroptions.workflowid === "string" ? triggeroptions.workflowid : "", payload: triggeroptions.rule, ...typeof triggeroptions.cooldown === "number" ? { cooldown: triggeroptions.cooldown } : {}, now: 0 });
|
|
6930
|
+
if (armed === void 0) return [];
|
|
6931
|
+
const origins = [];
|
|
6932
|
+
for (const origin of armed.origins ?? []) origins.push(origin);
|
|
6933
|
+
if (armed.pattern !== void 0) {
|
|
6934
|
+
try {
|
|
6935
|
+
origins.push(new URL(armed.pattern).origin);
|
|
6936
|
+
} catch {
|
|
6937
|
+
}
|
|
6938
|
+
}
|
|
6939
|
+
for (const url of armed.urls ?? []) {
|
|
6940
|
+
try {
|
|
6941
|
+
origins.push(new URL(url).origin);
|
|
6942
|
+
} catch {
|
|
6943
|
+
}
|
|
6944
|
+
}
|
|
6945
|
+
return [...new Set(origins)];
|
|
6946
|
+
}
|
|
6411
6947
|
function dryrunprojection(step) {
|
|
6412
6948
|
if (iscontrolflowkind(step.kind)) {
|
|
6413
6949
|
for (const child of controlsteps(step)) {
|
|
@@ -7004,6 +7540,10 @@ function validatestep(step, origin) {
|
|
|
7004
7540
|
const workflowcheck = validateworkflowgrammar(step, options);
|
|
7005
7541
|
if (!workflowcheck.allowed) return workflowcheck;
|
|
7006
7542
|
}
|
|
7543
|
+
if (istriggeraction(step.kind)) {
|
|
7544
|
+
const triggercheck = validatetriggergrammar(step, options);
|
|
7545
|
+
if (!triggercheck.allowed) return triggercheck;
|
|
7546
|
+
}
|
|
7007
7547
|
if (step.kind === "tabcreate") {
|
|
7008
7548
|
if (options.background !== void 0 && typeof options.background !== "boolean") return { allowed: false, reason: "The reviewed background flag must be a boolean." };
|
|
7009
7549
|
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." };
|
|
@@ -7198,6 +7738,10 @@ function canexecute(input) {
|
|
|
7198
7738
|
const workflowgatecheck = workflowgate({ session: input.session, plan: input.plan, step: input.step, tabid: input.tabid, origin: input.origin, now });
|
|
7199
7739
|
if (!workflowgatecheck.allowed) return workflowgatecheck;
|
|
7200
7740
|
}
|
|
7741
|
+
if (istriggeraction(input.step.kind)) {
|
|
7742
|
+
const triggergatecheck = triggergate({ session: input.session, plan: input.plan, step: input.step, tabid: input.tabid, origin: input.origin, now });
|
|
7743
|
+
if (!triggergatecheck.allowed) return triggergatecheck;
|
|
7744
|
+
}
|
|
7201
7745
|
if (iscontrolkind(input.step.kind)) {
|
|
7202
7746
|
const controlgate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now, action: "control the network" });
|
|
7203
7747
|
if (!controlgate.allowed) return controlgate;
|
|
@@ -7460,9 +8004,15 @@ function recordworkflow(progress, planid, stepid, entry, now) {
|
|
|
7460
8004
|
const outcome = { stepid, ok: true, summary: `${entry.detail}${counts.length > 0 ? ` with ${counts}` : ""}.`, details: { workflow: entry }, at: now };
|
|
7461
8005
|
return recordoutcome(base, planid, outcome, now);
|
|
7462
8006
|
}
|
|
8007
|
+
function recordtrigger(progress, planid, stepid, entry, now) {
|
|
8008
|
+
const base = progress && progress.planid === planid ? progress : emptyprogress(planid, now);
|
|
8009
|
+
const counts = `${entry.fires !== void 0 ? `${entry.fires} fire${entry.fires === 1 ? "" : "s"}, ` : ""}${entry.launches !== void 0 ? `${entry.launches} launch${entry.launches === 1 ? "" : "es"}, ` : ""}${entry.suppressions !== void 0 ? `${entry.suppressions} suppression${entry.suppressions === 1 ? "" : "s"}, ` : ""}${entry.queued !== void 0 ? `${entry.queued} queued fire${entry.queued === 1 ? "" : "s"}, ` : ""}`.replace(/, $/, "");
|
|
8010
|
+
const outcome = { stepid, ok: true, summary: `${entry.detail}${counts.length > 0 ? ` with ${counts}` : ""}.`, details: { trigger: entry }, at: now };
|
|
8011
|
+
return recordoutcome(base, planid, outcome, now);
|
|
8012
|
+
}
|
|
7463
8013
|
|
|
7464
8014
|
// version.ts
|
|
7465
|
-
var packageversion = "1.1.
|
|
8015
|
+
var packageversion = "1.1.52";
|
|
7466
8016
|
|
|
7467
8017
|
// types.ts
|
|
7468
8018
|
var protocolversion = packageversion;
|
|
@@ -7701,6 +8251,25 @@ function parseproposal(value, origin, grants) {
|
|
|
7701
8251
|
}
|
|
7702
8252
|
if (step.kind === "runworkflow" && workflowoptions.reviewed !== true) throw new Error("Workflow runs without the explicit run review of the expanded step list are refused.");
|
|
7703
8253
|
}
|
|
8254
|
+
if (istriggeraction(step.kind)) {
|
|
8255
|
+
let triggeroptions = {};
|
|
8256
|
+
try {
|
|
8257
|
+
triggeroptions = parseoptions(step);
|
|
8258
|
+
} catch {
|
|
8259
|
+
triggeroptions = {};
|
|
8260
|
+
}
|
|
8261
|
+
if (triggeroptions.reviewed !== true) throw new Error("Trigger rules without the explicit arm review of their match fields and bound workflow are refused.");
|
|
8262
|
+
for (const ruleorigin of triggerorigins(step)) {
|
|
8263
|
+
const granted = covered.some((pattern) => {
|
|
8264
|
+
try {
|
|
8265
|
+
return new URL(ruleorigin).origin === new URL(pattern).origin;
|
|
8266
|
+
} catch {
|
|
8267
|
+
return false;
|
|
8268
|
+
}
|
|
8269
|
+
});
|
|
8270
|
+
if (!granted) throw new Error(`The trigger on ${ruleorigin} stays outside the grants.`);
|
|
8271
|
+
}
|
|
8272
|
+
}
|
|
7704
8273
|
const evaluation = validatestep(step, origin);
|
|
7705
8274
|
if (!evaluation.allowed) throw new Error(evaluation.reason);
|
|
7706
8275
|
const target = outboundtarget(step);
|
|
@@ -7780,7 +8349,7 @@ function requestbody(input) {
|
|
|
7780
8349
|
return JSON.stringify({ version: protocolversion, objective: input.objective, session: input.session, observation: input.observation, capabilities: input.capabilities });
|
|
7781
8350
|
}
|
|
7782
8351
|
function outcomeresponse(input) {
|
|
7783
|
-
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 } : {}, ...input.network ? { network: input.network } : {}, ...input.control ? { control: input.control } : {}, ...input.timeline ? { timeline: input.timeline } : {}, ...input.cdp ? { cdp: input.cdp } : {}, ...input.profile ? { profile: input.profile } : {}, ...input.emulation ? { emulation: input.emulation } : {}, ...input.session ? { session: input.session } : {}, ...input.workflow ? { workflow: { runid: input.workflow.runid, state: input.workflow.state, ...input.workflow.dryrun === true ? { dryrun: true } : {}, produced: input.workflow.produced, consumed: input.workflow.consumed, ...input.workflow.timeout !== void 0 ? { timeout: input.workflow.timeout } : {}, ...input.workflow.retry !== void 0 ? { retry: input.workflow.retry } : {} } } : {} });
|
|
8352
|
+
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 } : {}, ...input.network ? { network: input.network } : {}, ...input.control ? { control: input.control } : {}, ...input.timeline ? { timeline: input.timeline } : {}, ...input.cdp ? { cdp: input.cdp } : {}, ...input.profile ? { profile: input.profile } : {}, ...input.emulation ? { emulation: input.emulation } : {}, ...input.session ? { session: input.session } : {}, ...input.workflow ? { workflow: { runid: input.workflow.runid, state: input.workflow.state, ...input.workflow.dryrun === true ? { dryrun: true } : {}, produced: input.workflow.produced, consumed: input.workflow.consumed, ...input.workflow.timeout !== void 0 ? { timeout: input.workflow.timeout } : {}, ...input.workflow.retry !== void 0 ? { retry: input.workflow.retry } : {} } } : {}, ...input.trigger ? { trigger: { ruleid: input.trigger.ruleid, kind: input.trigger.kind, enabled: input.trigger.enabled, ...input.trigger.nextfireat !== void 0 ? { nextfireat: input.trigger.nextfireat } : {} } } : {} });
|
|
7784
8353
|
}
|
|
7785
8354
|
function mapresponse(input) {
|
|
7786
8355
|
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, map: input.map });
|
|
@@ -7917,6 +8486,45 @@ function sessionreport(input) {
|
|
|
7917
8486
|
function workflowreport(input) {
|
|
7918
8487
|
return { version: protocolversion, workflows: input.workflows, runs: input.runs, templates: input.templates, log: input.log ?? [], scopes: input.scopes ?? [], provenance: input.provenance ?? [], control: input.control ?? [] };
|
|
7919
8488
|
}
|
|
8489
|
+
function triggerlist(input) {
|
|
8490
|
+
const names = new Map(input.workflows.map((record2) => [record2.id, record2.name]));
|
|
8491
|
+
const rules = input.rules.map((rule) => {
|
|
8492
|
+
const workflowname = names.get(rule.workflowid);
|
|
8493
|
+
return {
|
|
8494
|
+
id: rule.id,
|
|
8495
|
+
kind: rule.kind,
|
|
8496
|
+
workflowid: rule.workflowid,
|
|
8497
|
+
...workflowname !== void 0 ? { workflowname } : {},
|
|
8498
|
+
label: rule.label,
|
|
8499
|
+
enabled: rule.state.enabled,
|
|
8500
|
+
...rule.state.pausedat !== void 0 ? { paused: true } : {},
|
|
8501
|
+
cooldown: rule.state.cooldown,
|
|
8502
|
+
...rule.state.lastfireat !== void 0 ? { lastfireat: rule.state.lastfireat } : {},
|
|
8503
|
+
...rule.state.nextfireat !== void 0 ? { nextfireat: rule.state.nextfireat } : {},
|
|
8504
|
+
fires: rule.stats.fires,
|
|
8505
|
+
launches: rule.stats.launches,
|
|
8506
|
+
suppressions: rule.stats.suppressions,
|
|
8507
|
+
summary: triggersummaryof(rule)
|
|
8508
|
+
};
|
|
8509
|
+
});
|
|
8510
|
+
return { version: protocolversion, rules, queued: (input.queue ?? []).length };
|
|
8511
|
+
}
|
|
8512
|
+
function triggersummaryof(rule) {
|
|
8513
|
+
const summary = { kind: rule.kind, workflowid: rule.workflowid };
|
|
8514
|
+
if (rule.origins !== void 0) summary.origins = rule.origins;
|
|
8515
|
+
if (rule.pattern !== void 0) summary.pattern = rule.pattern;
|
|
8516
|
+
if (rule.title !== void 0) summary.title = rule.title;
|
|
8517
|
+
if (rule.command !== void 0) summary.command = rule.command;
|
|
8518
|
+
if (rule.key !== void 0) summary.key = rule.key;
|
|
8519
|
+
if (rule.cron !== void 0) summary.cron = rule.cron;
|
|
8520
|
+
if (rule.timezone !== void 0) summary.timezone = rule.timezone;
|
|
8521
|
+
if (rule.period !== void 0) summary.period = rule.period;
|
|
8522
|
+
if (rule.jitter !== void 0) summary.jitter = rule.jitter;
|
|
8523
|
+
if (rule.urls !== void 0) summary.urls = rule.urls;
|
|
8524
|
+
if (rule.events !== void 0) summary.events = rule.events;
|
|
8525
|
+
if (rule.schema !== void 0) summary.fields = rule.schema.length;
|
|
8526
|
+
return summary;
|
|
8527
|
+
}
|
|
7920
8528
|
|
|
7921
8529
|
// capture.ts
|
|
7922
8530
|
var capturekinds = ["shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet"];
|
|
@@ -9968,6 +10576,8 @@ async function executewatchstep(step, session, plan, tabid2, origin) {
|
|
|
9968
10576
|
const record2 = entry;
|
|
9969
10577
|
await memory.addmutationevent({ ...record2, sessionid: session.id });
|
|
9970
10578
|
}
|
|
10579
|
+
void evaluatepagetriggers("mutate").catch(() => {
|
|
10580
|
+
});
|
|
9971
10581
|
}
|
|
9972
10582
|
if (step.kind === "watchfocus") {
|
|
9973
10583
|
for (const entry of detailarray(output.details, "events")) {
|
|
@@ -9975,6 +10585,8 @@ async function executewatchstep(step, session, plan, tabid2, origin) {
|
|
|
9975
10585
|
const record2 = entry;
|
|
9976
10586
|
await memory.addfocusevent({ ...record2, sessionid: session.id });
|
|
9977
10587
|
}
|
|
10588
|
+
void evaluatepagetriggers("focus").catch(() => {
|
|
10589
|
+
});
|
|
9978
10590
|
}
|
|
9979
10591
|
if (step.kind === "watchbanner") {
|
|
9980
10592
|
for (const entry of detailarray(output.details, "banners")) {
|
|
@@ -9982,6 +10594,8 @@ async function executewatchstep(step, session, plan, tabid2, origin) {
|
|
|
9982
10594
|
const record2 = entry;
|
|
9983
10595
|
await memory.addbanner({ ...record2, sessionid: session.id });
|
|
9984
10596
|
}
|
|
10597
|
+
void evaluatepagetriggers("banner").catch(() => {
|
|
10598
|
+
});
|
|
9985
10599
|
}
|
|
9986
10600
|
await audit("watch", `Watch ${watchid} closed after its reviewed lifetime of ${lifetime} milliseconds.`, { sessionid: session.id, planid: plan.id, stepid: step.id });
|
|
9987
10601
|
return { output, watch };
|
|
@@ -10109,6 +10723,8 @@ async function tracktabupdate(tabid2, changeinfo) {
|
|
|
10109
10723
|
}
|
|
10110
10724
|
chrome.tabs.onUpdated.addListener((tabid2, changeinfo) => {
|
|
10111
10725
|
void tracktabupdate(tabid2, changeinfo);
|
|
10726
|
+
if (typeof changeinfo.url === "string" && changeinfo.url.startsWith("https://")) void evaluatenavigationtriggers(changeinfo.url).catch(() => {
|
|
10727
|
+
});
|
|
10112
10728
|
});
|
|
10113
10729
|
chrome.tabs.onActivated.addListener((activeinfo) => {
|
|
10114
10730
|
void recordtabwatchevent("activated", activeinfo.tabId);
|
|
@@ -12791,6 +13407,10 @@ async function executetimelinestep(step, session, plan, tabid2, origin) {
|
|
|
12791
13407
|
const watcherstate = activetimelinewatchers.get(watchid);
|
|
12792
13408
|
activetimelinewatchers.delete(watchid);
|
|
12793
13409
|
await memory.closewatch(watchid, Date.now());
|
|
13410
|
+
if (step.kind === "watchconsole" || step.kind === "watcherrors") {
|
|
13411
|
+
void evaluatepagetriggers(step.kind === "watchconsole" ? "console" : "error").catch(() => {
|
|
13412
|
+
});
|
|
13413
|
+
}
|
|
12794
13414
|
await audit("timeline", `Watcher ${watchid} detached cleanly after its reviewed window of ${watchwindow} milliseconds.`, extra);
|
|
12795
13415
|
if (watcherstate?.cancelled) {
|
|
12796
13416
|
await audit("timeline", `Watcher ${watchid} cancelled on run cancel or the killswitch; the captured window is discarded.`, extra);
|
|
@@ -14355,6 +14975,187 @@ async function storetimeoutabort(run, step, message, budget) {
|
|
|
14355
14975
|
await audit("workflow", `The run ${run.id} exceeded its reviewed budget of ${budget} milliseconds and was cancelled with the cancelled error class.`, { planid: step.id });
|
|
14356
14976
|
return { run: aborted, log: [entry] };
|
|
14357
14977
|
}
|
|
14978
|
+
async function executetriggerstep(step, session, plan, tabid2, origin) {
|
|
14979
|
+
const options = stepoptions2(step);
|
|
14980
|
+
const family = triggerfamilyof(step.kind);
|
|
14981
|
+
if (!family) throw new Error(`The ${step.kind} step is not a reviewed trigger kind.`);
|
|
14982
|
+
const workflowid = typeof options.workflowid === "string" ? options.workflowid : "";
|
|
14983
|
+
const record2 = await memory.getworkflowrecord(workflowid);
|
|
14984
|
+
if (!record2) throw new Error(`No composed workflow matches ${workflowid || "the reviewed id"}; trigger rules bind to workflows already composed and reviewed.`);
|
|
14985
|
+
const armed = armrule({ family, workflowid, ...typeof options.label === "string" && options.label.trim() ? { label: options.label } : {}, payload: options.rule, ...typeof options.cooldown === "number" ? { cooldown: options.cooldown } : {}, now: Date.now() });
|
|
14986
|
+
if (!armed) throw new Error(`The ${step.kind} payload does not arm as a reviewed rule.`);
|
|
14987
|
+
if (!ruleoriginsgranted(armed, record2.origins)) throw new Error("The trigger touches origins outside the workflow grant list; review the rule or the workflow origins.");
|
|
14988
|
+
for (const matchorigin of armed.origins ?? []) {
|
|
14989
|
+
if (!origingranted(session, matchorigin)) throw new Error(`The trigger origin ${matchorigin} falls outside the session grants.`);
|
|
14990
|
+
}
|
|
14991
|
+
if (armed.pattern !== void 0) {
|
|
14992
|
+
let patternorigin2 = "";
|
|
14993
|
+
try {
|
|
14994
|
+
patternorigin2 = new URL(armed.pattern).origin;
|
|
14995
|
+
} catch {
|
|
14996
|
+
patternorigin2 = "";
|
|
14997
|
+
}
|
|
14998
|
+
if (patternorigin2 && !origingranted(session, patternorigin2)) throw new Error(`The trigger origin ${patternorigin2} falls outside the session grants.`);
|
|
14999
|
+
}
|
|
15000
|
+
for (const url of armed.urls ?? []) {
|
|
15001
|
+
let listorigin = "";
|
|
15002
|
+
try {
|
|
15003
|
+
listorigin = new URL(url).origin;
|
|
15004
|
+
} catch {
|
|
15005
|
+
listorigin = "";
|
|
15006
|
+
}
|
|
15007
|
+
if (listorigin && !origingranted(session, listorigin)) throw new Error(`The trigger origin ${listorigin} falls outside the session grants.`);
|
|
15008
|
+
}
|
|
15009
|
+
const nextfireat = family === "cron" && armed.cron !== void 0 ? schedulecron({ cron: armed.cron, ...armed.timezone !== void 0 ? { timezone: armed.timezone } : {} }, Date.now()) : family === "interval" && armed.period !== void 0 ? scheduleinterval({ period: armed.period, ...armed.jitter !== void 0 ? { jitter: armed.jitter } : {} }, void 0, Date.now(), hashseed(armed.id)) : void 0;
|
|
15010
|
+
const rule = { ...armed, ...nextfireat !== void 0 ? { state: { ...armed.state, nextfireat } } : {} };
|
|
15011
|
+
await memory.addtriggerule(rule);
|
|
15012
|
+
await registermenurule(rule);
|
|
15013
|
+
await scheduletriggerwakes();
|
|
15014
|
+
await memory.setprogress(recordtrigger(await memory.getprogress(), plan.id, step.id, { family: "arm", detail: `Armed the ${family} rule for the workflow ${record2.name}`, ruleid: rule.id, ...nextfireat !== void 0 ? { nextfireat } : {} }, Date.now()));
|
|
15015
|
+
await audit("trigger", `Armed the ${family} rule ${rule.id} for the workflow ${record2.name} version ${record2.version} behind the explicit arm review; the rule starts ${rule.state.enabled ? "enabled" : "disabled"} with a cooldown of ${rule.state.cooldown} millisecond${rule.state.cooldown === 1 ? "" : "s"}${nextfireat !== void 0 ? ` and the next fire scheduled` : ""}.`, { sessionid: session.id, planid: plan.id, stepid: step.id });
|
|
15016
|
+
void origin;
|
|
15017
|
+
void tabid2;
|
|
15018
|
+
return { ok: true, summary: `Armed the ${family} rule for the workflow ${record2.name}${nextfireat !== void 0 ? " with the next fire scheduled" : ""}.`, details: { trigger: { ruleid: rule.id, kind: family, enabled: rule.state.enabled, ...nextfireat !== void 0 ? { nextfireat } : {} } } };
|
|
15019
|
+
}
|
|
15020
|
+
async function runofworkflowactive(workflowid) {
|
|
15021
|
+
if ([...activeworkflowruns.keys()].length === 0) return false;
|
|
15022
|
+
for (const run of await memory.listworkflowruns()) {
|
|
15023
|
+
if (run.workflowid === workflowid && run.state === "running") return true;
|
|
15024
|
+
}
|
|
15025
|
+
return false;
|
|
15026
|
+
}
|
|
15027
|
+
async function launchtriggerrun(fire, rule) {
|
|
15028
|
+
const session = await memory.getsession();
|
|
15029
|
+
const plan = await memory.getplan();
|
|
15030
|
+
const now = Date.now();
|
|
15031
|
+
if (!session || session.stoppedat || session.pausedat || session.expiresat <= now) return { launched: false, reason: "The trigger launch needs a live, unpaused browser session." };
|
|
15032
|
+
if (!plan || plan.state !== "approved") return { launched: false, reason: "The trigger launch needs the approved plan review." };
|
|
15033
|
+
const record2 = await memory.getworkflowrecord(rule.workflowid);
|
|
15034
|
+
if (!record2) return { launched: false, reason: "The workflow of the trigger rule is no longer composed; evaluation skips the rule." };
|
|
15035
|
+
for (const workfloworigin of record2.origins) {
|
|
15036
|
+
if (!origingranted(session, workfloworigin)) return { launched: false, reason: `The workflow origin ${workfloworigin} falls outside the session grants.` };
|
|
15037
|
+
}
|
|
15038
|
+
const variables = { triggercause: fire.cause, triggerrule: rule.id };
|
|
15039
|
+
if (fire.url !== void 0) variables.triggerurl = fire.url;
|
|
15040
|
+
if (fire.title !== void 0) variables.triggertitle = fire.title;
|
|
15041
|
+
if (fire.payload !== void 0) variables.triggerpayload = JSON.stringify(fire.payload);
|
|
15042
|
+
const runstep2 = { id: `trigger${fire.id}`, kind: "runworkflow", summary: `The ${rule.kind} trigger run of ${record2.name}`, risk: "sensitive", options: JSON.stringify({ workflowid: rule.workflowid, reviewed: true, variables }) };
|
|
15043
|
+
const output = await executeworkflowstep(runstep2, session, plan, session.tabid, session.origin).catch((error) => ({ ok: false, summary: error instanceof Error ? error.message : String(error), details: {} }));
|
|
15044
|
+
const launched = Boolean(output.ok);
|
|
15045
|
+
const runid = output.details?.runid;
|
|
15046
|
+
await memory.settriggerule(updaterule(rule, { state: { lastfireat: fire.at, ...nextfireof(rule, fire.at) !== void 0 ? { nextfireat: nextfireof(rule, fire.at) } : {} }, stats: { launches: rule.stats.launches + (launched ? 1 : 0) } }));
|
|
15047
|
+
await memory.addtriggerfire(fire);
|
|
15048
|
+
await audit("trigger", `The ${rule.kind} rule ${rule.id} fired on ${fire.cause}${fire.url !== void 0 ? ` at ${fire.url}` : ""} and ${launched ? `launched the run ${runid ?? ""} of the workflow ${record2.name}` : `refused the launch: ${output.ok === false ? output.summary : "the run ended"}`}; the arm review, the live session, the approved plan and the origin gates all re-passed.`, { sessionid: session.id, planid: plan.id });
|
|
15049
|
+
if (launched) await notifytriggerfired(fire, rule, runid);
|
|
15050
|
+
await refreshbadge();
|
|
15051
|
+
return { launched, ...runid !== void 0 ? { runid } : {}, ...launched ? {} : { reason: output.ok === false ? output.summary : "the run ended" } };
|
|
15052
|
+
}
|
|
15053
|
+
function nextfireof(rule, after) {
|
|
15054
|
+
if (rule.kind === "cron" && rule.cron !== void 0) return schedulecron({ cron: rule.cron, ...rule.timezone !== void 0 ? { timezone: rule.timezone } : {} }, after);
|
|
15055
|
+
if (rule.kind === "interval" && rule.period !== void 0) return scheduleinterval({ period: rule.period, ...rule.jitter !== void 0 ? { jitter: rule.jitter } : {} }, after, rule.createdat, hashseed(rule.id));
|
|
15056
|
+
return void 0;
|
|
15057
|
+
}
|
|
15058
|
+
async function firetrigger(rule, cause, url, title, payload) {
|
|
15059
|
+
const now = Date.now();
|
|
15060
|
+
const record2 = await memory.getworkflowrecord(rule.workflowid);
|
|
15061
|
+
const runactive = await runofworkflowactive(rule.workflowid);
|
|
15062
|
+
const decision = evaluatetrigger({ rule, now, cause, ...url !== void 0 ? { url } : {}, ...title !== void 0 ? { title } : {}, ...payload !== void 0 ? { payload } : {}, runactive: false, workflowreviewed: record2 !== void 0 });
|
|
15063
|
+
if (!decision.fired || decision.fire === void 0) {
|
|
15064
|
+
await memory.settriggerule(updaterule(rule, { stats: { suppressions: rule.stats.suppressions + 1 } }));
|
|
15065
|
+
await audit("trigger", `The ${rule.kind} rule ${rule.id} suppressed a ${cause} fire inside the ${decision.suppressed ?? "review"} gate${decision.remaining !== void 0 ? ` with ${decision.remaining} millisecond${decision.remaining === 1 ? "" : "s"} of cooldown left` : ""}.`, {});
|
|
15066
|
+
return { fired: false, ...decision.suppressed !== void 0 ? { suppressed: decision.suppressed } : {} };
|
|
15067
|
+
}
|
|
15068
|
+
if (runactive) {
|
|
15069
|
+
const queued = queuefire(await memory.gettriggerqueue(), decision.fire);
|
|
15070
|
+
await memory.settriggerqueue(queued.queue);
|
|
15071
|
+
await memory.settriggerule(updaterule(rule, { stats: { suppressions: rule.stats.suppressions + 1 } }));
|
|
15072
|
+
await audit("trigger", `The ${rule.kind} rule ${rule.id} fired on ${cause} while its workflow run was busy${queued.deduped ? " and the queue kept the pending fire already waiting" : " and the fire queued for the run to settle"}.`, {});
|
|
15073
|
+
await refreshbadge();
|
|
15074
|
+
return { fired: true, queued: true };
|
|
15075
|
+
}
|
|
15076
|
+
await memory.settriggerule(updaterule(rule, { stats: { fires: rule.stats.fires + 1 } }));
|
|
15077
|
+
const launch = await launchtriggerrun(decision.fire, rule);
|
|
15078
|
+
return { fired: true, ...launch.launched ? {} : { suppressed: launch.reason } };
|
|
15079
|
+
}
|
|
15080
|
+
async function draintriggerqueue() {
|
|
15081
|
+
const queue = await memory.gettriggerqueue();
|
|
15082
|
+
if (queue.length === 0) return { launched: 0, remaining: 0 };
|
|
15083
|
+
const result = await drainqueue(queue, async (fire) => {
|
|
15084
|
+
const rule = await memory.gettriggerule(fire.ruleid);
|
|
15085
|
+
if (!rule) return;
|
|
15086
|
+
await memory.settriggerule(updaterule(rule, { stats: { fires: rule.stats.fires + 1 } }));
|
|
15087
|
+
await launchtriggerrun(fire, rule);
|
|
15088
|
+
});
|
|
15089
|
+
await memory.settriggerqueue(result.remaining);
|
|
15090
|
+
await audit("trigger", `The trigger queue drained ${result.launched} queued fire${result.launched === 1 ? "" : "s"} with ${result.remaining} still waiting.`, {});
|
|
15091
|
+
await refreshbadge();
|
|
15092
|
+
return { launched: result.launched, remaining: result.remaining.length };
|
|
15093
|
+
}
|
|
15094
|
+
async function evaluatenavigationtriggers(url, title) {
|
|
15095
|
+
if (!url.startsWith("https://")) return;
|
|
15096
|
+
const session = await memory.getsession();
|
|
15097
|
+
if (!session || session.stoppedat) return;
|
|
15098
|
+
for (const { rule } of await memory.listtriggers()) {
|
|
15099
|
+
if (!rule.state.enabled || rule.state.pausedat !== void 0) continue;
|
|
15100
|
+
if (rule.kind === "visit" && rule.origins !== void 0 && visitmatch(rule.origins, url)) await firetrigger(rule, "visit", url, title);
|
|
15101
|
+
if (rule.kind === "url" && rule.pattern !== void 0 && matchurl(rule.pattern, url)) await firetrigger(rule, "url", url, title);
|
|
15102
|
+
}
|
|
15103
|
+
await evaluatepagetriggers("navigate", url, title);
|
|
15104
|
+
}
|
|
15105
|
+
async function evaluatepagetriggers(event, url, title) {
|
|
15106
|
+
const session = await memory.getsession();
|
|
15107
|
+
if (!session || session.stoppedat || session.pausedat) return;
|
|
15108
|
+
for (const rule of observeevents(await memory.gettriggerules(), event)) {
|
|
15109
|
+
await firetrigger(rule, "event", url, title, { event });
|
|
15110
|
+
}
|
|
15111
|
+
}
|
|
15112
|
+
async function evaluatelistedtriggers() {
|
|
15113
|
+
const rules = await memory.gettriggerules();
|
|
15114
|
+
const due = listdue(rules, Date.now());
|
|
15115
|
+
for (const entry of due) {
|
|
15116
|
+
await firetrigger(entry.rule, "schedule");
|
|
15117
|
+
}
|
|
15118
|
+
await scheduletriggerwakes();
|
|
15119
|
+
return { due: due.length };
|
|
15120
|
+
}
|
|
15121
|
+
async function registermenurule(rule) {
|
|
15122
|
+
if (rule.kind !== "menu" || rule.title === void 0) return;
|
|
15123
|
+
const menus = chrome.contextMenus;
|
|
15124
|
+
if (typeof menus?.create !== "function") {
|
|
15125
|
+
await audit("trigger", `The menu rule ${rule.id} stays armed without its context menu entry because the browser exposes no context menus api under the current permission set; the manual run path stays available in the panel.`, {});
|
|
15126
|
+
return;
|
|
15127
|
+
}
|
|
15128
|
+
try {
|
|
15129
|
+
menus.create({ id: `devthinktrigger${rule.id}`, title: rule.title, contexts: ["page", "selection", "link"] });
|
|
15130
|
+
} catch {
|
|
15131
|
+
}
|
|
15132
|
+
}
|
|
15133
|
+
async function registermenurules() {
|
|
15134
|
+
for (const { rule } of await memory.listtriggers()) await registermenurule(rule);
|
|
15135
|
+
}
|
|
15136
|
+
async function scheduletriggerwakes() {
|
|
15137
|
+
const alarms = chrome.alarms;
|
|
15138
|
+
if (typeof alarms?.create !== "function") return;
|
|
15139
|
+
try {
|
|
15140
|
+
const rules = await memory.gettriggerules();
|
|
15141
|
+
const scheduled = rules.filter((rule) => rule.state.enabled && rule.state.pausedat === void 0 && rule.state.nextfireat !== void 0);
|
|
15142
|
+
if (scheduled.length === 0) return;
|
|
15143
|
+
const next = Math.min(...scheduled.map((rule) => rule.state.nextfireat));
|
|
15144
|
+
const oneminute = 6e4;
|
|
15145
|
+
alarms.create("devthinktriggerwake", { when: Math.max(Date.now() + oneminute, next) });
|
|
15146
|
+
} catch {
|
|
15147
|
+
}
|
|
15148
|
+
}
|
|
15149
|
+
async function notifytriggerfired(fire, rule, runid) {
|
|
15150
|
+
const notifications = chrome.notifications;
|
|
15151
|
+
if (typeof notifications?.create === "function") {
|
|
15152
|
+
try {
|
|
15153
|
+
notifications.create(`devthinktrigger${fire.id}`, { type: "basic", iconUrl: "icon128.png", title: "Devthink trigger fired", message: `The ${rule.kind} rule fired on ${fire.cause}${runid !== void 0 ? ` and launched run ${runid}` : ""}.` });
|
|
15154
|
+
} catch {
|
|
15155
|
+
}
|
|
15156
|
+
}
|
|
15157
|
+
void runid;
|
|
15158
|
+
}
|
|
14358
15159
|
async function auditcontroldecision(runid, decision, sessionid, planid) {
|
|
14359
15160
|
if (decision.kind === "branch" && decision.branch !== void 0) await audit("workflow", `The branch step ${decision.stepid} of the run ${runid} chose the path ${decision.branch.path}: ${decision.branch.reason}`, { sessionid, planid });
|
|
14360
15161
|
else if (decision.kind === "loop" && decision.loops !== void 0) await audit("workflow", `The loop step ${decision.stepid} of the run ${runid} recorded ${decision.loops.length} iteration counter${decision.loops.length === 1 ? "" : "s"} with the paths ${decision.loops.map((counter) => counter.path).join(", ")}.`, { sessionid, planid });
|
|
@@ -14448,6 +15249,9 @@ async function executeaction(step, session, plan, tabid2, origin, settings, verd
|
|
|
14448
15249
|
} else if (isworkflowkind(step.kind)) {
|
|
14449
15250
|
if (!session || !plan || plan.state !== "approved") throw new Error("Workflow kinds refuse to run outside an approved session plan.");
|
|
14450
15251
|
output = await executeworkflowstep(step, session, plan, tabid2, origin);
|
|
15252
|
+
} else if (istriggeraction(step.kind)) {
|
|
15253
|
+
if (!session || !plan || plan.state !== "approved") throw new Error("Trigger kinds refuse to run outside an approved session plan.");
|
|
15254
|
+
output = await executetriggerstep(step, session, plan, tabid2, origin);
|
|
14451
15255
|
} else {
|
|
14452
15256
|
if (step.target && freshcheckkinds.has(step.kind)) {
|
|
14453
15257
|
const fresh = await snapshot(tabid2);
|
|
@@ -14548,7 +15352,9 @@ async function pausesession() {
|
|
|
14548
15352
|
if (session.pausedat) throw new Error("The browser session is already paused.");
|
|
14549
15353
|
const paused = { ...session, pausedat: Date.now() };
|
|
14550
15354
|
await memory.setsession(paused);
|
|
15355
|
+
await memory.settriggerules(pauseall(await memory.gettriggerules(), Date.now()));
|
|
14551
15356
|
await audit("pause", "The user paused the browser session; no action or preview can run.", { sessionid: session.id });
|
|
15357
|
+
await audit("trigger", "The session pause suspended every armed trigger rule; fires that arrive while paused queue for the resume drain.", { sessionid: session.id });
|
|
14552
15358
|
return paused;
|
|
14553
15359
|
}
|
|
14554
15360
|
async function resumesession() {
|
|
@@ -14558,7 +15364,11 @@ async function resumesession() {
|
|
|
14558
15364
|
if (!session.pausedat) throw new Error("The browser session is not paused.");
|
|
14559
15365
|
const resumed = { id: session.id, tabid: session.tabid, origin: session.origin, startedat: session.startedat, expiresat: session.expiresat, ...session.grants ? { grants: session.grants } : {} };
|
|
14560
15366
|
await memory.setsession(resumed);
|
|
15367
|
+
await memory.settriggerules(resumeall(await memory.gettriggerules()));
|
|
14561
15368
|
await audit("resume", "The user resumed the browser session; reviewed actions can run again.", { sessionid: session.id });
|
|
15369
|
+
await audit("trigger", "The session resume released the suspended trigger rules and drained the queued fires through the same gates.", { sessionid: session.id });
|
|
15370
|
+
void draintriggerqueue().catch(() => {
|
|
15371
|
+
});
|
|
14562
15372
|
return resumed;
|
|
14563
15373
|
}
|
|
14564
15374
|
async function grantcapability(permission) {
|
|
@@ -14696,7 +15506,7 @@ async function handlerequest(message, sender) {
|
|
|
14696
15506
|
const livetab = session ? await chrome.tabs.get(session.tabid).catch(() => void 0) : void 0;
|
|
14697
15507
|
const waitprofile = session ? waitprofiles.find((record2) => record2.origin === session.origin) : void 0;
|
|
14698
15508
|
const livestate = { phase: livetab?.status === "loading" ? "loading" : "complete", ...navrecords[0] ? { finalurl: navrecords[0].finalurl, redirects: navrecords[0].chain } : {} };
|
|
14699
|
-
return { config: await memory.getconfig(), session, plan, progress: plan && progress?.planid === plan.id ? progress : void 0, diagnostic: await memory.getdiagnostic(), audit: await memory.getaudit(), capabilities: await refreshcapabilities(), outcomes: await memory.getoutcomes(), holds: heldkeysreport({ tabid: session?.tabid ?? 0, holds }), dialogs: await memory.getdialogs(), retries: await memory.getretries(), ...signals ? { signals: signalsreport({ signals }) } : { signals: signalsreport({}) }, banners: await memory.getbanners(), mutationevents: await memory.getmutationevents(), focusevents: await memory.getfocusevents(), diffs: await memory.getdiffs(), selectors: await memory.getselectors(), ...a11y ? { a11y } : {}, ...reader ? { reader } : {}, ...map ? { map } : {}, trail: trailreport({ ...session ? { sessionid: session.id } : {}, trail }), navrecords, ratestates, safeties, curated, waitprofiles, auths, navcontrol, navqueues, artifacts, navstate: livestate, ...waitprofile ? { waitprofile } : {}, offline: !navigator.onLine, tabs, windows, layouts: layoutreport({ layouts }), tabgroups, tabmetas, badges, snapshots, closedtabs, tabwatchevents, clones, tasktabgauge: taskgauge, ...controltab ? { controltab } : {}, tabreport: report, profiles, tickets, wizards: wizardreport({ ...session ? { sessionid: session.id } : {}, wizards, picks }), picks, errorreports, captchas, detections, ...codeentry !== void 0 ? { codeentry: true } : {}, datasets, imports, extractsessions, streams, exports, provenances, taskrules, sheetendpoints: sheetgrants, downloads, netlogs, clipconsents, clips, quarantines, cleanuprules, cleanupruns, capturecounters, inventory, mimefilters, scanhooks, captures: capturemetadata, capturepairs, capturepolicy: runsettings?.capturepolicy ?? "manual", media: mediarecords, imagebatches, recordingconsents, recordingactive: [...activerecordings.values()].map((active) => ({ id: active.record.id, kind: active.record.kind, scope: active.record.scope, startedat: active.record.startedat, stopat: active.stopat })), recordingwindow: runsettings?.recordingwindow, calls, endpoints, fetchconsents, apikeys, callretention: runsettings?.callretention, fetchesactive: activefetches.size, exchanges, channels, subscriptions, apimap, messages: messagecount, webrequestgrant: runsettings?.webrequestgrant === true, bodyretention: runsettings?.bodyretention, timelineretention: runsettings?.timelineretention, timeline, consoleconsents: await memory.getconsoleconsents(), rotationtargets: await memory.getrotationtargets(), levelsummaries: await memory.getlevelsummaries(), cdpsessions: await memory.getcdpsessions(), cdpcommands: await memory.getcdpcommands(), cdpeventrules: await memory.getcdpeventrules(), breakpoints: await memory.getbreakpoints(), pauses: await memory.getpauses(), watchexpressions: await memory.getwatchexpressions(), scriptoverrides: await memory.getscriptoverrides(), debuggergrants: await memory.getdebuggergrants(), pauseretention: runsettings?.pauseretention, breakpointceiling: runsettings?.breakpointceiling, cdpattached: [...activecdpsessions.values()].filter((active) => active.session.detachedat === void 0).length, profileretention: runsettings?.profileretention, traceceiling: runsettings?.traceceiling, profile: profilereport({ flows: await memory.getflowmetrics(), heaps: await memory.getheaprecords(), samples: await memory.getgrowsamples(), trends: await memory.gettrends(), profiles: await memory.getcpuprofiles(), shifts: await memory.getshiftentries(), traces: await memory.gettracerecords(), sourcemaps: await memory.getsourcemaps(), consents: await memory.getsourcemapconsents() }), profileactive: activememorytrackers.size + activeprofiletargets.size, profiletargets: [...activeprofiletargets.values()].flatMap((entry) => entry.targets), socketsactive: activesockets.size, emulation: emulationreport({ ...plan && await loademulationstate(plan.id) !== void 0 ? { state: await loademulationstate(plan.id) } : {}, devices: await memory.getdevicepresets(), networks: await memory.getnetworkpresets(), locations: await memory.getlocationpresets(), agents: await memory.getagentpresets(), blackbox: await memory.getblackboxrules(), permissions: await memory.getpermissionoverrides(), consents: await memory.getlocationconsents() }), emulatedlayers: plan ? layernames(await loademulationstate(plan.id)) : [], emulationretention: runsettings?.emulationretention, traffic, tokens, authflows, activerules: [...activerules.values()].reduce((total, ruleset) => total + ruleset.blocks.filter((rule) => rule.revertedat === void 0).length + ruleset.mocks.filter((rule) => rule.revertedat === void 0).length + ruleset.rewrites.filter((rule) => rule.revertedat === void 0).length + (ruleset.proxy !== void 0 && ruleset.proxy.revertedat === void 0 ? 1 : 0), 0), sessionmemory: sessionreport({ records: sessionrecords, events: await memory.getsessionevents(), folders: await memory.getsessionfolders(), diffs: await memory.getsessiondiffs(), ...autosnapshot !== void 0 ? { auto: autosnapshot.interval } : {}, ...crashed ? { crashed: true } : {} }), autosnapshotstate: autosnapshot, sessionretention: runsettings?.sessionretention, workflow: workflowreport({ workflows: await memory.listworkflows(), runs: await memory.listworkflowruns(), templates: await memory.getsteptemplates(), ...newestworkflowrun !== void 0 ? { log: await memory.getrunlog(newestworkflowrun.id), scopes: await memory.getrunscopes(newestworkflowrun.id), provenance: await memory.getworkflowprovenance(newestworkflowrun.id), control: await memory.listcontroldecisions(newestworkflowrun.id) } : {} }), runlogretention: runsettings?.runlogretention, ...taskstate !== void 0 ? { taskstate } : {}, ...stitchprogress.size > 0 ? { stitchprogress: [...stitchprogress.values()] } : {} };
|
|
15509
|
+
return { config: await memory.getconfig(), session, plan, progress: plan && progress?.planid === plan.id ? progress : void 0, diagnostic: await memory.getdiagnostic(), audit: await memory.getaudit(), capabilities: await refreshcapabilities(), outcomes: await memory.getoutcomes(), holds: heldkeysreport({ tabid: session?.tabid ?? 0, holds }), dialogs: await memory.getdialogs(), retries: await memory.getretries(), ...signals ? { signals: signalsreport({ signals }) } : { signals: signalsreport({}) }, banners: await memory.getbanners(), mutationevents: await memory.getmutationevents(), focusevents: await memory.getfocusevents(), diffs: await memory.getdiffs(), selectors: await memory.getselectors(), ...a11y ? { a11y } : {}, ...reader ? { reader } : {}, ...map ? { map } : {}, trail: trailreport({ ...session ? { sessionid: session.id } : {}, trail }), navrecords, ratestates, safeties, curated, waitprofiles, auths, navcontrol, navqueues, artifacts, navstate: livestate, ...waitprofile ? { waitprofile } : {}, offline: !navigator.onLine, tabs, windows, layouts: layoutreport({ layouts }), tabgroups, tabmetas, badges, snapshots, closedtabs, tabwatchevents, clones, tasktabgauge: taskgauge, ...controltab ? { controltab } : {}, tabreport: report, profiles, tickets, wizards: wizardreport({ ...session ? { sessionid: session.id } : {}, wizards, picks }), picks, errorreports, captchas, detections, ...codeentry !== void 0 ? { codeentry: true } : {}, datasets, imports, extractsessions, streams, exports, provenances, taskrules, sheetendpoints: sheetgrants, downloads, netlogs, clipconsents, clips, quarantines, cleanuprules, cleanupruns, capturecounters, inventory, mimefilters, scanhooks, captures: capturemetadata, capturepairs, capturepolicy: runsettings?.capturepolicy ?? "manual", media: mediarecords, imagebatches, recordingconsents, recordingactive: [...activerecordings.values()].map((active) => ({ id: active.record.id, kind: active.record.kind, scope: active.record.scope, startedat: active.record.startedat, stopat: active.stopat })), recordingwindow: runsettings?.recordingwindow, calls, endpoints, fetchconsents, apikeys, callretention: runsettings?.callretention, fetchesactive: activefetches.size, exchanges, channels, subscriptions, apimap, messages: messagecount, webrequestgrant: runsettings?.webrequestgrant === true, bodyretention: runsettings?.bodyretention, timelineretention: runsettings?.timelineretention, timeline, consoleconsents: await memory.getconsoleconsents(), rotationtargets: await memory.getrotationtargets(), levelsummaries: await memory.getlevelsummaries(), cdpsessions: await memory.getcdpsessions(), cdpcommands: await memory.getcdpcommands(), cdpeventrules: await memory.getcdpeventrules(), breakpoints: await memory.getbreakpoints(), pauses: await memory.getpauses(), watchexpressions: await memory.getwatchexpressions(), scriptoverrides: await memory.getscriptoverrides(), debuggergrants: await memory.getdebuggergrants(), pauseretention: runsettings?.pauseretention, breakpointceiling: runsettings?.breakpointceiling, cdpattached: [...activecdpsessions.values()].filter((active) => active.session.detachedat === void 0).length, profileretention: runsettings?.profileretention, traceceiling: runsettings?.traceceiling, profile: profilereport({ flows: await memory.getflowmetrics(), heaps: await memory.getheaprecords(), samples: await memory.getgrowsamples(), trends: await memory.gettrends(), profiles: await memory.getcpuprofiles(), shifts: await memory.getshiftentries(), traces: await memory.gettracerecords(), sourcemaps: await memory.getsourcemaps(), consents: await memory.getsourcemapconsents() }), profileactive: activememorytrackers.size + activeprofiletargets.size, profiletargets: [...activeprofiletargets.values()].flatMap((entry) => entry.targets), socketsactive: activesockets.size, emulation: emulationreport({ ...plan && await loademulationstate(plan.id) !== void 0 ? { state: await loademulationstate(plan.id) } : {}, devices: await memory.getdevicepresets(), networks: await memory.getnetworkpresets(), locations: await memory.getlocationpresets(), agents: await memory.getagentpresets(), blackbox: await memory.getblackboxrules(), permissions: await memory.getpermissionoverrides(), consents: await memory.getlocationconsents() }), emulatedlayers: plan ? layernames(await loademulationstate(plan.id)) : [], emulationretention: runsettings?.emulationretention, traffic, tokens, authflows, activerules: [...activerules.values()].reduce((total, ruleset) => total + ruleset.blocks.filter((rule) => rule.revertedat === void 0).length + ruleset.mocks.filter((rule) => rule.revertedat === void 0).length + ruleset.rewrites.filter((rule) => rule.revertedat === void 0).length + (ruleset.proxy !== void 0 && ruleset.proxy.revertedat === void 0 ? 1 : 0), 0), sessionmemory: sessionreport({ records: sessionrecords, events: await memory.getsessionevents(), folders: await memory.getsessionfolders(), diffs: await memory.getsessiondiffs(), ...autosnapshot !== void 0 ? { auto: autosnapshot.interval } : {}, ...crashed ? { crashed: true } : {} }), autosnapshotstate: autosnapshot, sessionretention: runsettings?.sessionretention, trigger: triggerlist({ rules: await memory.gettriggerules(), workflows: await memory.listworkflows(), queue: await memory.gettriggerqueue() }), triggerretention: runsettings?.triggerretention, workflow: workflowreport({ workflows: await memory.listworkflows(), runs: await memory.listworkflowruns(), templates: await memory.getsteptemplates(), ...newestworkflowrun !== void 0 ? { log: await memory.getrunlog(newestworkflowrun.id), scopes: await memory.getrunscopes(newestworkflowrun.id), provenance: await memory.getworkflowprovenance(newestworkflowrun.id), control: await memory.listcontroldecisions(newestworkflowrun.id) } : {} }), runlogretention: runsettings?.runlogretention, ...taskstate !== void 0 ? { taskstate } : {}, ...stitchprogress.size > 0 ? { stitchprogress: [...stitchprogress.values()] } : {} };
|
|
14700
15510
|
}
|
|
14701
15511
|
case "capabilities":
|
|
14702
15512
|
return refreshcapabilities();
|
|
@@ -14743,7 +15553,8 @@ async function handlerequest(message, sender) {
|
|
|
14743
15553
|
const network = outcome.details?.network;
|
|
14744
15554
|
const timeline = outcome.details?.timeline;
|
|
14745
15555
|
const sessionblock = outcome.details?.session;
|
|
14746
|
-
|
|
15556
|
+
const triggerblock = outcome.details?.trigger;
|
|
15557
|
+
return JSON.parse(outcomeresponse({ outcome, plan, ...resolved ? { resolvedtarget: resolved } : {}, ...triggerblock !== void 0 && triggerblock.ruleid !== void 0 ? { trigger: { ruleid: triggerblock.ruleid, kind: triggerblock.kind ?? "", enabled: triggerblock.enabled ?? true, ...triggerblock.nextfireat !== void 0 ? { nextfireat: triggerblock.nextfireat } : {} } } : {}, ...capture ? { capture } : {}, ...media ? { media } : {}, ...network ? { network } : {}, ...timeline ? { timeline } : {}, ...sessionblock !== void 0 ? { session: { recordid: sessionblock.recordid ?? "", sections: sessionblock.sections ?? 0, ...sessionblock.matches !== void 0 ? { matches: sessionblock.matches } : {}, ...sessionblock.restored !== void 0 ? { restored: sessionblock.restored } : {}, ...sessionblock.skipped !== void 0 ? { skipped: sessionblock.skipped } : {}, ...sessionblock.cursor !== void 0 ? { cursor: sessionblock.cursor } : {}, ...sessionblock.bytes !== void 0 ? { bytes: sessionblock.bytes } : {} } } : {} }));
|
|
14747
15558
|
}
|
|
14748
15559
|
case "map": {
|
|
14749
15560
|
const plan = await memory.getplan();
|
|
@@ -16029,6 +16840,184 @@ async function handlerequest(message, sender) {
|
|
|
16029
16840
|
if (!stored) throw new Error(`No workflow run matches ${inputoutcome.runid ?? ""}.`);
|
|
16030
16841
|
return workflowoutcome({ run: stored.run, entries: stored.log, ...inputoutcome.stepid !== void 0 && inputoutcome.stepid !== "" ? { stepid: inputoutcome.stepid } : {} });
|
|
16031
16842
|
}
|
|
16843
|
+
case "triggerreview": {
|
|
16844
|
+
const triggers = await memory.listtriggers();
|
|
16845
|
+
const runs = await memory.listworkflowruns();
|
|
16846
|
+
const queue = await memory.gettriggerqueue();
|
|
16847
|
+
const rules = await Promise.all(triggers.map(async (entry) => {
|
|
16848
|
+
const record2 = await memory.getworkflowrecord(entry.rule.workflowid);
|
|
16849
|
+
return {
|
|
16850
|
+
id: entry.rule.id,
|
|
16851
|
+
kind: entry.rule.kind,
|
|
16852
|
+
workflowid: entry.rule.workflowid,
|
|
16853
|
+
...entry.workflowname !== void 0 ? { workflowname: entry.workflowname } : {},
|
|
16854
|
+
label: entry.rule.label,
|
|
16855
|
+
enabled: entry.rule.state.enabled,
|
|
16856
|
+
paused: entry.rule.state.pausedat !== void 0,
|
|
16857
|
+
cooldown: entry.rule.state.cooldown,
|
|
16858
|
+
...entry.rule.state.lastfireat !== void 0 ? { lastfireat: entry.rule.state.lastfireat } : {},
|
|
16859
|
+
...entry.rule.state.nextfireat !== void 0 ? { nextfireat: entry.rule.state.nextfireat } : {},
|
|
16860
|
+
fires: entry.rule.stats.fires,
|
|
16861
|
+
launches: entry.rule.stats.launches,
|
|
16862
|
+
suppressions: entry.rule.stats.suppressions,
|
|
16863
|
+
summary: triggersummary(entry.rule),
|
|
16864
|
+
steps: record2 ? record2.steps.map((inner) => ({ id: inner.id, kind: inner.kind, label: inner.label })) : [],
|
|
16865
|
+
...entry.rule.kind === "webhook" ? { deliveries: (await memory.listwebhookpayloads(entry.rule.id)).length } : {}
|
|
16866
|
+
};
|
|
16867
|
+
}));
|
|
16868
|
+
const active = runs.filter((run) => run.state === "running").length;
|
|
16869
|
+
return { rules, queued: queue.length, active, fires: (await memory.listtriggerfires()).slice(0, 50) };
|
|
16870
|
+
}
|
|
16871
|
+
case "toggletrigger": {
|
|
16872
|
+
const inputtoggle = message;
|
|
16873
|
+
const rule = await memory.gettriggerule(inputtoggle.ruleid ?? "");
|
|
16874
|
+
if (!rule) throw new Error(`No armed trigger rule matches ${inputtoggle.ruleid ?? ""}.`);
|
|
16875
|
+
if (typeof inputtoggle.enabled !== "boolean") throw new Error("The trigger toggle needs the reviewed enabled flag.");
|
|
16876
|
+
const updated = updaterule(rule, { state: { enabled: inputtoggle.enabled } });
|
|
16877
|
+
await memory.settriggerule(updated);
|
|
16878
|
+
await audit("trigger", `The user ${inputtoggle.enabled ? "enabled" : "disabled"} the ${rule.kind} rule ${rule.id} of the workflow ${rule.workflowid}; disabled rules never fire until reenabled.`, {});
|
|
16879
|
+
await scheduletriggerwakes();
|
|
16880
|
+
return { ruleid: rule.id, enabled: updated.state.enabled };
|
|
16881
|
+
}
|
|
16882
|
+
case "createtrigger": {
|
|
16883
|
+
const inputcreate = message;
|
|
16884
|
+
const session = await memory.getsession();
|
|
16885
|
+
const plan = await memory.getplan();
|
|
16886
|
+
if (!session || session.stoppedat || session.expiresat <= Date.now()) throw new Error("Arming a trigger rule needs an active browser session behind the consent gates.");
|
|
16887
|
+
if (!plan || plan.state !== "approved") throw new Error("Arming a trigger rule needs the approved plan review.");
|
|
16888
|
+
const family = triggerfamilyof(`${inputcreate.family ?? ""}rule`);
|
|
16889
|
+
if (!family) throw new Error("The trigger family is not a reviewed family.");
|
|
16890
|
+
const step = { id: `panel${Date.now()}`, kind: `${family}rule`, summary: `The reviewed ${family} rule of the panel`, risk: "sensitive", options: JSON.stringify({ workflowid: inputcreate.workflowid ?? "", reviewed: true, ...typeof inputcreate.label === "string" ? { label: inputcreate.label } : {}, ...inputcreate.cooldown !== void 0 ? { cooldown: inputcreate.cooldown } : {}, rule: inputcreate.payload ?? {} }) };
|
|
16891
|
+
const verdict = canexecute({ session, plan, step, tabid: session.tabid, origin: session.origin, now: Date.now() });
|
|
16892
|
+
if (!verdict.allowed) throw new Error(verdict.reason ?? "The trigger rule was refused by the consent gates.");
|
|
16893
|
+
return await executetriggerstep(step, session, plan, session.tabid, session.origin);
|
|
16894
|
+
}
|
|
16895
|
+
case "createvisitrule": {
|
|
16896
|
+
const inputvisit = message;
|
|
16897
|
+
const session = await memory.getsession();
|
|
16898
|
+
if (!session || session.stoppedat || session.expiresat <= Date.now()) throw new Error("Creating a visit rule needs an active browser session behind the consent gates.");
|
|
16899
|
+
const tab = await chrome.tabs.get(session.tabid).catch(() => void 0);
|
|
16900
|
+
const url = tab?.url ?? "";
|
|
16901
|
+
let origin = "";
|
|
16902
|
+
try {
|
|
16903
|
+
origin = new URL(url).origin;
|
|
16904
|
+
} catch {
|
|
16905
|
+
origin = "";
|
|
16906
|
+
}
|
|
16907
|
+
if (!origin.startsWith("https://")) throw new Error("The current page carries no HTTPS origin to bind a visit rule to.");
|
|
16908
|
+
return await handlerequest({ kind: "createtrigger", workflowid: inputvisit.workflowid ?? "", family: "visit", payload: { origins: [origin] }, label: `Visit ${origin}` }, {});
|
|
16909
|
+
}
|
|
16910
|
+
case "duplicatetrigger": {
|
|
16911
|
+
const inputduplicate = message;
|
|
16912
|
+
const rule = await memory.gettriggerule(inputduplicate.ruleid ?? "");
|
|
16913
|
+
if (!rule) throw new Error(`No armed trigger rule matches ${inputduplicate.ruleid ?? ""}.`);
|
|
16914
|
+
const record2 = await memory.getworkflowrecord(inputduplicate.workflowid ?? "");
|
|
16915
|
+
if (!record2) throw new Error(`No composed workflow matches ${inputduplicate.workflowid ?? ""}.`);
|
|
16916
|
+
const { state: rulestate, stats: rulestats, createdat: rulecreatedat, id: ruleid, workflowid: ruleworkflowid, label: rulelabel, cooldown: rulecooldown, ...familypayload } = rule;
|
|
16917
|
+
void rulestate;
|
|
16918
|
+
void rulestats;
|
|
16919
|
+
void rulecreatedat;
|
|
16920
|
+
void ruleid;
|
|
16921
|
+
void ruleworkflowid;
|
|
16922
|
+
void rulelabel;
|
|
16923
|
+
const armed = armrule({ family: rule.kind, workflowid: record2.id, label: `${rule.label} for ${record2.name}`, payload: familypayload, ...rulecooldown !== void 0 ? { cooldown: rulecooldown } : {}, now: Date.now() });
|
|
16924
|
+
if (!armed) throw new Error("The duplicated rule payload failed its family grammar.");
|
|
16925
|
+
const nextfireat = rule.kind === "cron" && rule.cron !== void 0 ? schedulecron({ cron: rule.cron, ...rule.timezone !== void 0 ? { timezone: rule.timezone } : {} }, Date.now()) : rule.kind === "interval" && rule.period !== void 0 ? scheduleinterval({ period: rule.period, ...rule.jitter !== void 0 ? { jitter: rule.jitter } : {} }, void 0, Date.now(), hashseed(armed.id)) : void 0;
|
|
16926
|
+
const duplicate = { ...armed, ...nextfireat !== void 0 ? { state: { ...armed.state, nextfireat } } : {} };
|
|
16927
|
+
if (!ruleoriginsgranted(duplicate, record2.origins)) throw new Error("The duplicated rule touches origins outside the workflow grant list of its new workflow.");
|
|
16928
|
+
await memory.addtriggerule(duplicate);
|
|
16929
|
+
await registermenurule(duplicate);
|
|
16930
|
+
await scheduletriggerwakes();
|
|
16931
|
+
await audit("trigger", `The user duplicated the ${rule.kind} rule ${rule.id} to the workflow ${record2.name} as the rule ${duplicate.id}; the duplicated rule arms enabled with zeroed counters.`, {});
|
|
16932
|
+
return { ruleid: duplicate.id, workflowid: duplicate.workflowid };
|
|
16933
|
+
}
|
|
16934
|
+
case "manualrun": {
|
|
16935
|
+
const inputmanual = message;
|
|
16936
|
+
const record2 = await memory.getworkflowrecord(inputmanual.workflowid ?? "");
|
|
16937
|
+
if (!record2) throw new Error(`No composed workflow matches ${inputmanual.workflowid ?? ""}.`);
|
|
16938
|
+
const preview = manualpreview(record2, Date.now());
|
|
16939
|
+
await memory.addmanualrun(preview);
|
|
16940
|
+
await audit("trigger", `The user opened the manual run preview of the workflow ${record2.name} with its ${preview.preview.length} expanded step${preview.preview.length === 1 ? "" : "s"}; nothing runs before the confirmation.`, {});
|
|
16941
|
+
return { manualrun: preview, workflowname: record2.name };
|
|
16942
|
+
}
|
|
16943
|
+
case "confirmmanualrun": {
|
|
16944
|
+
const inputconfirm = message;
|
|
16945
|
+
const stored = (await memory.listmanualruns()).find((entry) => entry.id === (inputconfirm.previewid ?? ""));
|
|
16946
|
+
if (!stored) throw new Error(`No manual run preview matches ${inputconfirm.previewid ?? ""}.`);
|
|
16947
|
+
if (typeof inputconfirm.confirmed !== "boolean") throw new Error("The manual run confirmation needs the reviewed confirmed flag.");
|
|
16948
|
+
const session = await memory.getsession();
|
|
16949
|
+
const plan = await memory.getplan();
|
|
16950
|
+
if (!session || session.stoppedat || session.pausedat || session.expiresat <= Date.now()) throw new Error("Confirming a manual run needs a live, unpaused browser session behind the consent gates.");
|
|
16951
|
+
if (!plan || plan.state !== "approved") throw new Error("Confirming a manual run needs the approved plan review.");
|
|
16952
|
+
const record2 = await memory.getworkflowrecord(stored.workflowid);
|
|
16953
|
+
if (!record2) throw new Error("The workflow of the manual run preview is no longer composed in the library.");
|
|
16954
|
+
const decided = confirmmanualrun(stored, inputconfirm.confirmed, Date.now());
|
|
16955
|
+
await memory.addmanualrun(decided);
|
|
16956
|
+
if (!inputconfirm.confirmed) {
|
|
16957
|
+
await audit("trigger", `The user cancelled the manual run of the workflow ${record2.name} after its step preview; nothing ran.`, { sessionid: session.id, planid: plan.id });
|
|
16958
|
+
return { confirmed: false };
|
|
16959
|
+
}
|
|
16960
|
+
const runstep2 = { id: `manual${decided.id}`, kind: "runworkflow", summary: `The manual run of ${record2.name}`, risk: "sensitive", options: JSON.stringify({ workflowid: record2.id, reviewed: true }) };
|
|
16961
|
+
const output = await executeworkflowstep(runstep2, session, plan, session.tabid, session.origin).catch((error) => ({ ok: false, summary: error instanceof Error ? error.message : String(error), details: {} }));
|
|
16962
|
+
await audit("trigger", `The user approved the manual run of the workflow ${record2.name} after its ${decided.preview.length} step preview; the run ended ${output.ok ? "done" : "failed"}.`, { sessionid: session.id, planid: plan.id });
|
|
16963
|
+
return { confirmed: true, runid: output.details?.runid, state: output.ok ? "done" : "failed" };
|
|
16964
|
+
}
|
|
16965
|
+
case "firetrigger": {
|
|
16966
|
+
const inputfire = message;
|
|
16967
|
+
const rule = await memory.gettriggerule(inputfire.ruleid ?? "");
|
|
16968
|
+
if (!rule) throw new Error(`No armed trigger rule matches ${inputfire.ruleid ?? ""}.`);
|
|
16969
|
+
const result = await firetrigger(rule, "manual");
|
|
16970
|
+
return { fired: result.fired, ...result.queued !== void 0 ? { queued: result.queued } : {}, ...result.suppressed !== void 0 ? { suppressed: result.suppressed } : {} };
|
|
16971
|
+
}
|
|
16972
|
+
case "receivewebhook": {
|
|
16973
|
+
const inputwebhook = message;
|
|
16974
|
+
const rule = await memory.gettriggerule(inputwebhook.ruleid ?? "");
|
|
16975
|
+
if (!rule || rule.kind !== "webhook") throw new Error(`No webhook rule matches ${inputwebhook.ruleid ?? ""}.`);
|
|
16976
|
+
const verification = verifywebhook({ rule, secret: inputwebhook.secret ?? "", payload: inputwebhook.payload });
|
|
16977
|
+
if (!verification.verified) {
|
|
16978
|
+
await memory.settriggerule(updaterule(rule, { stats: { suppressions: rule.stats.suppressions + 1 } }));
|
|
16979
|
+
await audit("trigger", `A webhook delivery for the rule ${rule.id} was refused: ${verification.reason}; only secret verified payloads ever persist.`, {});
|
|
16980
|
+
return { verified: false, reason: verification.reason };
|
|
16981
|
+
}
|
|
16982
|
+
await memory.addwebhookpayload(rule.id, inputwebhook.payload, Date.now());
|
|
16983
|
+
const result = await firetrigger(rule, "webhook", void 0, void 0, inputwebhook.payload);
|
|
16984
|
+
return { verified: true, fired: result.fired, ...result.queued !== void 0 ? { queued: result.queued } : {}, ...result.suppressed !== void 0 ? { suppressed: result.suppressed } : {} };
|
|
16985
|
+
}
|
|
16986
|
+
case "rotatetriggersecret": {
|
|
16987
|
+
const inputrotate = message;
|
|
16988
|
+
const rule = await memory.gettriggerule(inputrotate.ruleid ?? "");
|
|
16989
|
+
if (!rule || rule.kind !== "webhook") throw new Error(`No webhook rule matches ${inputrotate.ruleid ?? ""}.`);
|
|
16990
|
+
const bytes = crypto.getRandomValues(new Uint8Array(24));
|
|
16991
|
+
const secret = [...bytes].map((byte) => (byte % 36).toString(36)).join("").padEnd(24, "7x9k2m").slice(0, 24);
|
|
16992
|
+
const rotated = { ...rule, secret, state: { ...rule.state }, stats: { ...rule.stats } };
|
|
16993
|
+
await memory.settriggerule(rotated);
|
|
16994
|
+
await audit("trigger", `The user rotated the shared secret of the webhook rule ${rule.id}; the previous secret stops verifying immediately and the new secret was shown once.`, {});
|
|
16995
|
+
return { ruleid: rule.id, secret };
|
|
16996
|
+
}
|
|
16997
|
+
case "settriggerretention": {
|
|
16998
|
+
const inputretention = message;
|
|
16999
|
+
const settings = await memory.getsettings();
|
|
17000
|
+
const retention = typeof inputretention.retention === "number" && Number.isInteger(inputretention.retention) && inputretention.retention >= 0 ? inputretention.retention : void 0;
|
|
17001
|
+
await memory.setsettings({ ...settings, ...retention !== void 0 ? { triggerretention: retention } : {} });
|
|
17002
|
+
await audit("configure", `The user set the trigger fire retention to ${retention === void 0 ? "keep every fire record" : `${retention} fire record${retention === 1 ? "" : "s"}`}; the rule counters always survive and no code ceiling applies.`);
|
|
17003
|
+
return { triggerretention: retention };
|
|
17004
|
+
}
|
|
17005
|
+
case "triggerhistory": {
|
|
17006
|
+
const inputhistory = message;
|
|
17007
|
+
const fires = await memory.listtriggerfires(inputhistory.ruleid);
|
|
17008
|
+
return { fires };
|
|
17009
|
+
}
|
|
17010
|
+
case "buttontrigger": {
|
|
17011
|
+
const rules = (await memory.gettriggerules()).filter((rule) => rule.kind === "button" && rule.state.enabled && rule.state.pausedat === void 0);
|
|
17012
|
+
if (rules.length === 0) return { fired: false, reason: "No enabled button rule is armed." };
|
|
17013
|
+
const results = [];
|
|
17014
|
+
for (const rule of rules) results.push({ ruleid: rule.id, ...await firetrigger(rule, "button") });
|
|
17015
|
+
return { fired: results.some((entry) => entry.fired), rules: results };
|
|
17016
|
+
}
|
|
17017
|
+
case "triggerfiredreport": {
|
|
17018
|
+
const fires = await memory.listtriggerfires();
|
|
17019
|
+
return triggerlist({ rules: await memory.gettriggerules(), workflows: await memory.listworkflows(), queue: await memory.gettriggerqueue() });
|
|
17020
|
+
}
|
|
16032
17021
|
default:
|
|
16033
17022
|
throw new Error("Unknown Devthink request.");
|
|
16034
17023
|
}
|
|
@@ -16128,4 +17117,78 @@ chrome.runtime.onConnect.addListener((port) => {
|
|
|
16128
17117
|
handlerequest(message, port.sender ?? {}).then((value) => port.postMessage({ ok: true, value })).catch((error) => port.postMessage({ ok: false, error: error instanceof Error ? error.message : String(error) }));
|
|
16129
17118
|
});
|
|
16130
17119
|
});
|
|
17120
|
+
{
|
|
17121
|
+
const webnavigation = chrome.webNavigation;
|
|
17122
|
+
try {
|
|
17123
|
+
webnavigation?.onCommitted?.addListener((details) => {
|
|
17124
|
+
if (details.frameId !== 0 || !details.url.startsWith("https://")) return;
|
|
17125
|
+
void evaluatenavigationtriggers(details.url).catch(() => {
|
|
17126
|
+
});
|
|
17127
|
+
});
|
|
17128
|
+
} catch {
|
|
17129
|
+
}
|
|
17130
|
+
}
|
|
17131
|
+
{
|
|
17132
|
+
const menus = chrome.contextMenus;
|
|
17133
|
+
try {
|
|
17134
|
+
menus?.onClicked?.addListener((info) => {
|
|
17135
|
+
const menuid = typeof info.menuItemId === "string" ? info.menuItemId : "";
|
|
17136
|
+
if (!menuid.startsWith("devthinktrigger")) return;
|
|
17137
|
+
void (async () => {
|
|
17138
|
+
const rule = await memory.gettriggerule(menuid.slice("devthinktrigger".length));
|
|
17139
|
+
if (!rule) return;
|
|
17140
|
+
await firetrigger(rule, "menu");
|
|
17141
|
+
})().catch(() => {
|
|
17142
|
+
});
|
|
17143
|
+
});
|
|
17144
|
+
} catch {
|
|
17145
|
+
}
|
|
17146
|
+
}
|
|
17147
|
+
{
|
|
17148
|
+
const commands = chrome.commands;
|
|
17149
|
+
try {
|
|
17150
|
+
commands?.onCommand?.addListener((command) => {
|
|
17151
|
+
void (async () => {
|
|
17152
|
+
for (const { rule } of await memory.listtriggers()) {
|
|
17153
|
+
if (rule.kind !== "key" || rule.command !== command) continue;
|
|
17154
|
+
await firetrigger(rule, "key");
|
|
17155
|
+
}
|
|
17156
|
+
})().catch(() => {
|
|
17157
|
+
});
|
|
17158
|
+
});
|
|
17159
|
+
} catch {
|
|
17160
|
+
}
|
|
17161
|
+
}
|
|
17162
|
+
{
|
|
17163
|
+
const action = chrome.action;
|
|
17164
|
+
try {
|
|
17165
|
+
action?.onClicked?.addListener(() => {
|
|
17166
|
+
void handlerequest({ kind: "buttontrigger" }, {}).catch(() => {
|
|
17167
|
+
});
|
|
17168
|
+
});
|
|
17169
|
+
} catch {
|
|
17170
|
+
}
|
|
17171
|
+
}
|
|
17172
|
+
{
|
|
17173
|
+
const alarms = chrome.alarms;
|
|
17174
|
+
try {
|
|
17175
|
+
alarms?.onAlarm?.addListener((alarm) => {
|
|
17176
|
+
if (alarm.name !== "devthinktriggerwake") return;
|
|
17177
|
+
void evaluatelistedtriggers().then(() => draintriggerqueue()).catch(() => {
|
|
17178
|
+
});
|
|
17179
|
+
});
|
|
17180
|
+
} catch {
|
|
17181
|
+
}
|
|
17182
|
+
}
|
|
17183
|
+
setInterval(() => {
|
|
17184
|
+
void evaluatelistedtriggers().then(() => draintriggerqueue()).catch(() => {
|
|
17185
|
+
});
|
|
17186
|
+
}, 3e4);
|
|
17187
|
+
async function restoretriggers() {
|
|
17188
|
+
await registermenurules();
|
|
17189
|
+
await evaluatelistedtriggers();
|
|
17190
|
+
await draintriggerqueue();
|
|
17191
|
+
}
|
|
17192
|
+
restoretriggers().catch(() => {
|
|
17193
|
+
});
|
|
16131
17194
|
//# sourceMappingURL=background.js.map
|