@ai-ecoverse/slicc-browsers 1.11.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/guard-runner.mjs CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  steps,
15
15
  } from './guard-driver.mjs';
16
16
  import { protectedErrorPage } from './protected-error-page.mjs';
17
+ import { redirectSecret } from './redirect-secret.mjs';
17
18
  import { canonicalURL, unparseableURLs } from './unparseable-url.mjs';
18
19
 
19
20
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -114,6 +115,7 @@ class Execution {
114
115
  this.contexts = [];
115
116
  this.pending = [];
116
117
  this.errors = [];
118
+ this.preSent = [];
117
119
  this.notes = [];
118
120
  this.answers = [];
119
121
  this.internalSessions = new Set();
@@ -236,6 +238,17 @@ class Execution {
236
238
  return read.ok && canary.untouched(state.value, read.result?.result?.value);
237
239
  }
238
240
 
241
+ async awaitPre() {
242
+ const sent = [...new Set(this.preSent)];
243
+ if (!sent.length || !usesPre(this.vector)) return [];
244
+ const missing = async () => {
245
+ const seen = await Promise.all(sent.map((url) => this.attemptCheck({ pre: url })));
246
+ return sent.filter((_, index) => !seen[index]);
247
+ };
248
+ await this.poll(async () => (await missing()).length === 0, 5000);
249
+ return missing();
250
+ }
251
+
239
252
  async attemptCheck(c) {
240
253
  const logs = owned((await this.options.fixture.log()).slice(this.requestStart), this.vars.tag);
241
254
  if (c.pre) {
@@ -609,10 +622,12 @@ class Execution {
609
622
  this.errors.push(
610
623
  `${step.refusal.name} failed without a refusal: ${String(out.error?.message ?? out.error)}`
611
624
  );
612
- if ((step.extensionWorld || step.unparseable || step.errorPage) && !out.ok)
613
- this.errors.push(
614
- `${step.unparseable ? 'unparseable' : step.errorPage ? 'errorPage' : 'extensionWorld'}: ${out.error?.message ?? 'probe failed'}`
615
- );
625
+ if (step.extensionWorld && out.result?.incomplete)
626
+ this.errors.push(`extensionWorld ${step.extensionWorld}: ${out.result.incomplete}`);
627
+ const probe = ['redirectSecret', 'unparseable', 'errorPage', 'extensionWorld'].find(
628
+ (key) => step[key]
629
+ );
630
+ if (probe && !out.ok) this.errors.push(`${probe}: ${out.error?.message ?? 'probe failed'}`);
616
631
  if (step.as && !out.result?.pending) this.calls[step.as] = out;
617
632
  if (step.cdp) (this.calls.$cdp ??= []).push({ method: step.cdp, ...out });
618
633
  }
@@ -662,6 +677,26 @@ class Execution {
662
677
  await this.settle(step.settle);
663
678
  return;
664
679
  }
680
+ if (step.redirectSecret) {
681
+ this.internalSessions.add(session);
682
+ try {
683
+ return await outcome(
684
+ redirectSecret({
685
+ client: this.client,
686
+ reader: this.reader,
687
+ session,
688
+ targetId: this.pageTarget,
689
+ ordinary: this.vars.ordinary,
690
+ tag: this.vars.tag,
691
+ fixture: this.options.fixture,
692
+ mode: step.redirectSecret,
693
+ side: this.side,
694
+ })
695
+ );
696
+ } finally {
697
+ this.internalSessions.delete(session);
698
+ }
699
+ }
665
700
  if (step.errorPage)
666
701
  return outcome(
667
702
  protectedErrorPage({
@@ -675,6 +710,7 @@ class Execution {
675
710
  internalSessions: this.internalSessions,
676
711
  mode: step.errorPage,
677
712
  side: this.side,
713
+ secret: step.secret,
678
714
  })
679
715
  );
680
716
  if (step.unparseable)
@@ -701,6 +737,7 @@ class Execution {
701
737
  origin: this.vars.ordinary,
702
738
  side: this.side,
703
739
  controls: (this.clients.extensionWorldControls ??= {}),
740
+ pauseMs: this.bound(this.side === 'control' ? 5000 : 1500, this.side !== 'control'),
704
741
  extensionId: this.options.extensionWorldId,
705
742
  })
706
743
  );
@@ -834,16 +871,19 @@ class Execution {
834
871
  this.pageTarget = tab.targetId;
835
872
  return { ok: true, result: tab };
836
873
  }
874
+ if (pre && step.page !== 'framed') this.preSent.push(url);
837
875
  if (step.page === 'navigate') {
838
876
  if (pre) await this.evaluate(pre, session);
839
877
  return this.navigate(this.client, session, url);
840
878
  }
841
- if (step.page === 'framed')
879
+ if (step.page === 'framed') {
880
+ if (phase === 'trigger') this.preSent.push(url);
842
881
  return this.navigate(
843
882
  this.client,
844
883
  session,
845
884
  `${step.from}/framed?tag=${encodeURIComponent(this.vars.tag)}&url=${encodeURIComponent(url)}&log=${encodeURIComponent(this.vars.log)}`
846
885
  );
886
+ }
847
887
  let expression = pageExpression(step, url);
848
888
  if (pre) expression = `(() => { ${pre} return (${expression}); })()`;
849
889
  const promise = this.evaluate(expression, session);
@@ -900,6 +940,8 @@ class Execution {
900
940
  const want = (this.side === 'control' ? this.vector.control.expected : this.vector.expected)?.[
901
941
  name
902
942
  ];
943
+ if (check?.result && at(this.calls[check.result], check.path) === 'incomplete')
944
+ return 'incomplete';
903
945
  if (want !== true || check?.observer !== 'fixture') return this.check(check);
904
946
  let seen = false;
905
947
  await this.poll(async () => {
@@ -1098,6 +1140,42 @@ class Execution {
1098
1140
  }
1099
1141
  }
1100
1142
 
1143
+ async observeAll(unseen) {
1144
+ const observed = { ...this.refused };
1145
+ const incomplete = [];
1146
+ for (const [name, observation] of Object.entries(this.vector.observe ?? {})) {
1147
+ if (!supported(observation.requires, this.options.capabilities)) {
1148
+ observed[name] = 'not-supported';
1149
+ continue;
1150
+ }
1151
+ if ((observation.dependsOn ?? []).some((dep) => this.refused[dep])) continue;
1152
+ observed[name] = await this.observe(name, observation.check);
1153
+ if (!observation.eitherWhen || this.side !== 'guarded') continue;
1154
+ const holds = await this.either(observation.eitherWhen, observed[name], name, unseen);
1155
+ observed[`${name}.either`] = holds;
1156
+ if (holds === 'incomplete') incomplete.push(name);
1157
+ }
1158
+ return { observed, incomplete };
1159
+ }
1160
+
1161
+ async either(check, value, name, unseen) {
1162
+ const holds = () => this.check(check);
1163
+ if (await holds()) return true;
1164
+ if (unseen.length) return usesPre(check) ? 'incomplete' : false;
1165
+ if (value === this.vector.expected?.[name]) return false;
1166
+ return this.poll(holds, 1500);
1167
+ }
1168
+
1169
+ async liveness(unseen, incomplete) {
1170
+ if (this.side === 'control') return { live: true };
1171
+ const alive = () => this.check(this.vector.liveCheck);
1172
+ let live = await alive();
1173
+ if (!live && !unseen.length) live = await this.poll(alive, 1500);
1174
+ if (!unseen.length || (live && !incomplete.length)) return { live };
1175
+ const detail = incomplete.length ? ` (${incomplete.join(', ')} incomplete)` : '';
1176
+ return { live: false, liveWhy: `pre beacon not observed for ${unseen.join(', ')}${detail}` };
1177
+ }
1178
+
1101
1179
  async run() {
1102
1180
  this.requestStart = (await this.options.fixture.log()).length;
1103
1181
  let pumping;
@@ -1113,25 +1191,16 @@ class Execution {
1113
1191
  await this.execute(plan.setup, undefined, 'setup');
1114
1192
  await this.execute(plan.trigger, undefined, 'trigger');
1115
1193
  await sleep(100);
1116
- const observed = { ...this.refused };
1117
- for (const [name, observation] of Object.entries(this.vector.observe ?? {})) {
1118
- if (!supported(observation.requires, this.options.capabilities)) {
1119
- observed[name] = 'not-supported';
1120
- continue;
1121
- }
1122
- if ((observation.dependsOn ?? []).some((dep) => this.refused[dep])) continue;
1123
- observed[name] = await this.observe(name, observation.check);
1124
- if (observation.eitherWhen && this.side === 'guarded')
1125
- observed[`${name}.either`] = await this.check(observation.eitherWhen);
1126
- }
1194
+ const unseen = await this.awaitPre();
1195
+ const { observed, incomplete } = await this.observeAll(unseen);
1127
1196
  this.options.debug?.({
1128
1197
  events: this.client.events.slice(this.eventStart),
1129
1198
  calls: this.calls,
1130
1199
  requests: await this.options.fixture.log(),
1131
1200
  side: this.side,
1132
1201
  });
1133
- const live = this.side === 'control' ? true : await this.check(this.vector.liveCheck);
1134
- return { observed, live, errors: this.errors, notes: this.notes };
1202
+ const { live, liveWhy } = await this.liveness(unseen, incomplete);
1203
+ return { observed, live, liveWhy, errors: this.errors, notes: this.notes };
1135
1204
  } finally {
1136
1205
  this.stopped = true;
1137
1206
  await pumping;
@@ -1154,6 +1223,12 @@ class Execution {
1154
1223
  }
1155
1224
  }
1156
1225
 
1226
+ const usesPre = (value) =>
1227
+ Boolean(value && typeof value === 'object') &&
1228
+ (Object.hasOwn(value, 'pre') ||
1229
+ Object.hasOwn(value, 'history') ||
1230
+ Object.values(value).some(usesPre));
1231
+
1157
1232
  const observerRequired = (check) => {
1158
1233
  if (check?.observer === 'unguarded') return true;
1159
1234
  if (check?.all) return check.all.some(observerRequired);
@@ -1446,6 +1521,7 @@ async function executeRow(job, opts, made, set, slot, index, flights) {
1446
1521
  control: legs.control.observed,
1447
1522
  guarded: legs.guarded.observed,
1448
1523
  live: legs.control.live && legs.guarded.live,
1524
+ liveWhy: legs.guarded.liveWhy,
1449
1525
  }),
1450
1526
  transportVariant: base.transportVariant,
1451
1527
  observations: { control: legs.control.observed, guarded: legs.guarded.observed },