Fork Horse repository, listed by its owner. Publisher snapshot.

packages/forkhorse-gatekeeper/test/fixture-main.ts

Snapshot at 34e3175

Containing directory

5,411 bytes · typescriptView raw
1import { newMessagePortRpcSession, RpcTarget, type RpcStub } from "capnweb";
2import type {
3  GatekeeperAppTheme,
4  GatekeeperAppThemeReceiver,
5} from "@gadgets/workshop-shared/theme";
6import { createRateLimitedCapability } from "../../../cloudflare-os/packages/workshop-frontend/src/rateLimitedCapability.ts";
7import { fixtureState } from "../../forkhorse-dashboard/test/fixtures.ts";
8import { stateView } from "../../forkhorse-dashboard/src/model.ts";
9import { SnapshotFeed } from "../src/feed.ts";
10import { FixtureUi } from "./fixture-ui.ts";
11
12const state = stateView(fixtureState());
13const feed = new SnapshotFeed(state);
14const recovery = new URL(location.href).searchParams.has("recovery");
15let oldPolls = 0;
16const fixture = recovery
17  ? new (class extends FixtureUi {
18      async subscribe(): Promise<never> {
19        throw new Error("Fixture stalled push channel");
20      }
21      async waitForChanges(): Promise<never> {
22        oldPolls++;
23        return new Promise(() => {});
24      }
25    })(feed)
26  : new FixtureUi(feed);
27const limited = createRateLimitedCapability(fixture, {
28  maxConcurrency: 8,
29  maxCallsPerMinute: 600,
30  maxPendingCalls: 128,
31  onRateLimit: "throttle",
32  label: "Fixture sidebar",
33});
34const output = document.getElementById("fixture-output")!;
35const freshState = stateView(fixtureState());
36freshState.packets[0]!.title = "Fresh recovered fixture data";
37const freshFeed = new SnapshotFeed(freshState);
38const fresh = new FixtureUi(freshFeed);
39const freshLimited = createRateLimitedCapability(fresh, {
40  maxConcurrency: 8,
41  maxCallsPerMinute: 600,
42  maxPendingCalls: 128,
43  onRateLimit: "throttle",
44  label: "Fixture recovered sidebar",
45});
46let acquired = 0,
47  themes = 0;
48let presenting = false;
49const presentationCalls: boolean[] = [];
50let theme: GatekeeperAppTheme = {
51  mode: matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light",
52  accentColor: null,
53};
54let themeReceiver: RpcStub<GatekeeperAppThemeReceiver> | undefined;
55function evidence() {
56  output.textContent = JSON.stringify({
57    acquired,
58    oldPolls,
59    freshCalls: fresh.calls,
60    themes,
61    presenting,
62    presentationCalls,
63    hostMode: theme.mode,
64    fixture: "offline replacement host capability",
65  });
66}
67class FixtureHost extends RpcTarget {
68  get ui() {
69    acquired++;
70    evidence();
71    return recovery && acquired > 1 ? freshLimited.capability : limited.capability;
72  }
73  subscribeTheme(receiver: RpcStub<GatekeeperAppThemeReceiver>) {
74    themeReceiver?.[Symbol.dispose]();
75    themeReceiver = receiver.dup();
76    themes++;
77    evidence();
78    return theme;
79  }
80  openPrompt(prompt: string) {
81    output.textContent = `Fixture openPrompt received: ${prompt}`;
82  }
83  setPresenting(active: boolean) {
84    const rect = iframe.getBoundingClientRect().toJSON();
85    presentationCalls.push(active);
86    presenting = active;
87    iframe.style.cssText = active
88      ? "position:fixed;inset:0;width:100vw;height:100vh;border:0;z-index:10"
89      : "width:100%;height:100vh;border:0";
90    evidence();
91    return { rect: active ? rect : null, willResize: true };
92  }
93}
94if (recovery)
95  for (const mode of ["light", "dark"] as const) {
96    const button = document.createElement("button");
97    button.textContent = `Fixture host ${mode} during stall`;
98    button.addEventListener("click", () => {
99      // Simulate a dropped theme callback; recovery must apply subscribeTheme's current reply.
100      theme = { mode, accentColor: null };
101      document.documentElement.dataset.mode = mode;
102      document.documentElement.style.colorScheme = mode;
103      evidence();
104    });
105    document.body.insertBefore(button, document.getElementById("sidebar"));
106  }
107
108// Exercise the actual built document in the same opaque-origin sandbox as pinned OS.
109const response = await fetch("/dist/sidebar.html");
110if (!response.ok) throw new Error("Build the sidebar before starting its fixture host.");
111const html = await response.text();
112const iframe = document.createElement("iframe");
113iframe.title = "Local fixture sidebar";
114iframe.setAttribute("sandbox", "allow-scripts");
115iframe.style.cssText = "width:100%;height:100vh;border:0";
116// Label only the fixture document; the production build has no fixture data or marker.
117iframe.srcdoc = html.replace('<div id="sidebar">', '<div id="sidebar" data-fixture="true" data-core-origin="https://core.fixture.idiot.tax">');
118let session: ReturnType<typeof newMessagePortRpcSession> | undefined;
119const handshake = (event: MessageEvent) => {
120  if (
121    event.source !== iframe.contentWindow ||
122    event.origin !== "null" ||
123    event.data?.type !== "handshake" ||
124    !event.ports[0]
125  )
126    return;
127  if (session) {
128    event.ports[0].close();
129    return;
130  }
131  session = newMessagePortRpcSession(event.ports[0], new FixtureHost());
132};
133window.addEventListener("message", handshake);
134document.getElementById("sidebar")!.replaceChildren(iframe);
135let batches = 0;
136const timer = setInterval(() => {
137  if (recovery) {
138    evidence();
139    return;
140  }
141  batches++;
142  state.packets[0]!.title = `Fixture update batch ${batches}`;
143  feed.publish(state);
144}, 250);
145window.addEventListener(
146  "pagehide",
147  () => {
148    clearInterval(timer);
149    window.removeEventListener("message", handshake);
150    feed.dispose();
151    freshFeed.dispose();
152    limited.dispose();
153    freshLimited.dispose();
154    themeReceiver?.[Symbol.dispose]();
155    session?.[Symbol.dispose]();
156  },
157  { once: true },
158);
159