Files
openclaw/extensions/diffs-language-pack/src/plugin.ts
Peter Steinberger 2ded26a5d6 fix(diffs): share SSR preloads and repair language-pack hydration (#100487)
* fix(diffs): share SSR preloads and repair language-pack hydration

Render viewer and file documents from a single @pierre/diffs SSR preload
per file (mode=both previously ran the full diff+highlight pipeline
twice; 651ms -> 303ms on an 8-file patch), apply the file-mode font bump
as a document-level override, and keep hydration payloads
variant-faithful.

Fix the language-pack runtime downgrading pack-only languages to plain
text at hydration by defining a per-target build flag and forwarding it
to payload normalization.

Also: case-insensitive language hints, identical before/after
short-circuit with details.changed, patch input failures classified as
tool input errors, canonical config values now win over deprecated
aliases, hash-pinned viewer runtime served immutable, truthful
browser-vs-render errors, timing-safe artifact token compare, unref
idle browser timer.

* docs(changelog): link diffs rendering entry to PR

* test(diffs): narrow manifest validation results before value access

* test(tooling): allowlist diffs viewer-client define suppression
2026-07-06 00:34:17 +01:00

76 lines
2.1 KiB
TypeScript

// Diffs Language Pack plugin module implements plugin behavior.
import type { IncomingMessage, ServerResponse } from "node:http";
import type { OpenClawPluginApi } from "../api.js";
import { VIEWER_ASSET_PREFIX, VIEWER_RUNTIME_PATH, getServedViewerAsset } from "./viewer-assets.js";
const IMMUTABLE_ASSET_CACHE_CONTROL = "public, max-age=31536000, immutable";
export function registerDiffsLanguagePackPlugin(api: OpenClawPluginApi): void {
api.registerHttpRoute({
path: "/plugins/diffs-language-pack",
auth: "plugin",
match: "prefix",
handler: createDiffsLanguagePackHttpHandler(),
});
}
function createDiffsLanguagePackHttpHandler() {
return async (req: IncomingMessage, res: ServerResponse): Promise<boolean> => {
const parsed = parseRequestUrl(req.url);
if (!parsed?.pathname.startsWith(VIEWER_ASSET_PREFIX)) {
return false;
}
if (req.method !== "GET" && req.method !== "HEAD") {
respondText(res, 405, "Method not allowed");
return true;
}
const asset = await getServedViewerAsset(parsed.pathname);
if (!asset) {
respondText(res, 404, "Asset not found");
return true;
}
res.statusCode = 200;
setSharedHeaders(
res,
asset.contentType,
parsed.pathname === VIEWER_RUNTIME_PATH ? IMMUTABLE_ASSET_CACHE_CONTROL : undefined,
);
if (req.method === "HEAD") {
res.end();
} else {
res.end(asset.body);
}
return true;
};
}
function parseRequestUrl(rawUrl?: string): URL | null {
if (!rawUrl) {
return null;
}
try {
return new URL(rawUrl, "http://127.0.0.1");
} catch {
return null;
}
}
function respondText(res: ServerResponse, statusCode: number, body: string): void {
res.statusCode = statusCode;
setSharedHeaders(res, "text/plain; charset=utf-8");
res.end(body);
}
function setSharedHeaders(
res: ServerResponse,
contentType: string,
cacheControl = "no-store, max-age=0",
): void {
res.setHeader("cache-control", cacheControl);
res.setHeader("content-type", contentType);
res.setHeader("x-content-type-options", "nosniff");
res.setHeader("referrer-policy", "no-referrer");
}