Files
backnotprop__plannotator/packages/shared/live-proxy-bridge-inline.test.ts
Michael Ramos 7d6dd29c08 perf(ui): load the HTML viewer bridge by URL for hosts, with a protocol version and ready timeout (#1398)
Opt-in bridgeScriptUrl on HtmlViewer so multi-chunk hosts can serve the 185 KB bridge as a hashed asset instead of an inlined string; the inline bridge stays the default and Plannotator's own builds, the Pi and OpenCode copies, and the live-app proxy are unchanged apart from a protocolVersion field on the bridge's ready message. The parent checks the version (one warning naming both versions; on the URL path a dismissible banner plus onBridgeUnavailable while the old bridge keeps working), arms a ready timeout on the URL path only, and resolves the URL against the parent document before it reaches the frame so a page's own base href cannot redirect the load. A prepack-generated bridge-script.asset.js (byte-for-byte the inline string) and a bridge-script.lite.ts alias target ship in the tarball. CSP and CORP requirements for hosts are documented.

AI-assisted (Claude) under maintainer direction.
2026-08-27 08:45:22 -07:00

62 lines
2.7 KiB
TypeScript

/**
* Live app annotation is untouched by the HtmlViewer `bridgeScriptUrl` seam:
* the proxy keeps composing the INLINE bridge body it serves from its own
* route and injecting that route's tag into proxied HTML. A later change
* that routed the live path through the package asset (or its URL prop)
* would move a per-session token-bearing body onto a host-served file, so
* this pins the current shape at source level, on both transports and both
* runtimes' composers.
*/
import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
// Relative on purpose: @plannotator/ui is not a dependency of shared; only the
// CLI and Pi import the bridge module, and this test reads the same file.
import { BRIDGE_SCRIPT, LIVE_BRIDGE_BOOTSTRAP } from "../ui/components/html-viewer/bridge-script";
import {
LIVE_PROXY_BRIDGE_PATH,
LIVE_PROXY_BRIDGE_TAG,
composeLiveBridgeJs,
} from "./live-proxy-core";
const root = resolve(import.meta.dir, "../..");
const read = (path: string) => readFileSync(resolve(root, path), "utf8");
describe("live proxy bridge delivery", () => {
test("the proxy-served body carries the inline bridge verbatim", () => {
const body = composeLiveBridgeJs({
token: "tok",
editorOrigins: ["http://localhost:1"],
annotationCss: ".x{}",
bridgeBootstrap: LIVE_BRIDGE_BOOTSTRAP,
bridgeScript: BRIDGE_SCRIPT,
});
expect(body.endsWith(BRIDGE_SCRIPT)).toBe(true);
expect(body).toContain(LIVE_BRIDGE_BOOTSTRAP);
// The injected tag names the proxy's own route, never a host asset.
expect(LIVE_PROXY_BRIDGE_TAG).toBe(`<script src="${LIVE_PROXY_BRIDGE_PATH}"></script>`);
expect(LIVE_PROXY_BRIDGE_PATH).toBe("/__plannotator__/bridge.js");
});
test.each([
"packages/shared/live-proxy-core.ts",
"packages/shared/live-proxy-node.ts",
"packages/server/live-proxy.ts",
"packages/server/annotate.ts",
"apps/hook/server/index.ts",
"apps/pi-extension/plannotator-browser.ts",
"apps/pi-extension/server/serverAnnotate.ts",
])("%s never references the srcdoc URL seam or the generated asset", (path) => {
const source = read(path);
expect(source).not.toContain("bridgeScriptUrl");
expect(source).not.toContain("bridge-script.asset");
expect(source).not.toContain("bridge-script.lite");
});
test("both runtimes still hand the inline string exports to the composer", () => {
expect(read("apps/hook/server/index.ts")).toContain("bridgeScript: BRIDGE_SCRIPT,");
expect(read("apps/pi-extension/plannotator-browser.ts")).toContain("bridgeScript: bridge.BRIDGE_SCRIPT,");
expect(read("packages/shared/live-proxy-core.ts")).toContain("+ sources.bridgeScript");
});
});