229 lines
10 KiB
JavaScript
229 lines
10 KiB
JavaScript
/**
|
||
* OFFLINE image runtime gate: exercise the shipped Photon/WASM resize core and
|
||
* actual worker, with file modules confined to the standalone payload. No app,
|
||
* provider, canvas, or network needed; a tiny synthetic PNG is generated here.
|
||
*
|
||
* node scripts/smoke-image-runtime.mjs [--standalone <dir>] [--runtime <Node/Electron>]
|
||
*/
|
||
import assert from "node:assert/strict";
|
||
import { spawnSync } from "node:child_process";
|
||
import * as fs from "node:fs";
|
||
import { createRequire } from "node:module";
|
||
import * as os from "node:os";
|
||
import * as path from "node:path";
|
||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||
import { Worker } from "node:worker_threads";
|
||
import { deflateSync } from "node:zlib";
|
||
import { formatProbeError, probeEnv } from "./smoke-demo-runtime.mjs";
|
||
|
||
export function verifyImageRuntime(standalone, runtime = process.execPath) {
|
||
const home = fs.mkdtempSync(path.join(os.tmpdir(), "lh-image-runtime-"));
|
||
try {
|
||
fs.writeFileSync(path.join(home, "auth.json"), "{}\n", { mode: 0o600 });
|
||
const result = spawnSync(runtime, [fileURLToPath(import.meta.url), "--probe", path.resolve(standalone)], {
|
||
cwd: home,
|
||
env: { ...probeEnv(process.env, home), ELECTRON_RUN_AS_NODE: "1" },
|
||
encoding: "utf8", timeout: 30_000, killSignal: "SIGKILL", maxBuffer: 128 * 1024,
|
||
});
|
||
if (result.status !== 0 || result.error) {
|
||
throw new Error(`image runtime probe failed (${result.error?.message ?? result.status}):\n${result.stdout ?? ""}${result.stderr ?? ""}`);
|
||
}
|
||
assert.match(result.stdout, /Photon in-process and worker resizing passed/, "probe exited without success marker");
|
||
process.stdout.write(result.stdout);
|
||
} finally { fs.rmSync(home, { recursive: true, force: true }); }
|
||
}
|
||
|
||
// Self-contained so the eval worker can install the SAME guards using only
|
||
// builtins, before importing any payload module. Module hooks do not otherwise
|
||
// propagate from the dedicated probe process into Worker threads.
|
||
function installGuards(require, standalone) {
|
||
const assert = require("node:assert/strict");
|
||
const fs = require("node:fs");
|
||
const path = require("node:path");
|
||
const { fileURLToPath } = require("node:url");
|
||
const { registerHooks, syncBuiltinESMExports } = require("node:module");
|
||
const root = fs.realpathSync(standalone);
|
||
const inside = (file) => {
|
||
const real = fs.realpathSync(file);
|
||
const relative = path.relative(root, real);
|
||
assert.ok(relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative),
|
||
`dependency escaped standalone payload: ${file}`);
|
||
return real;
|
||
};
|
||
const photonRoot = path.join(root, "node_modules/@silvia-odwyer/photon-node");
|
||
const manifest = JSON.parse(fs.readFileSync(inside(path.join(photonRoot, "package.json")), "utf8"));
|
||
assert.equal(manifest.version, "0.3.4", "unexpected Photon engine version");
|
||
const photonEntry = inside(path.join(photonRoot, "photon_rs.js"));
|
||
const photonWasm = inside(path.join(photonRoot, "photon_rs_bg.wasm"));
|
||
assert.ok(fs.statSync(photonWasm).size > 0, "empty Photon WASM asset");
|
||
let attempts = 0;
|
||
const deny = () => { attempts++; throw new Error("OFFLINE image gate: network forbidden"); };
|
||
globalThis.fetch = deny;
|
||
globalThis.WebSocket = class { constructor() { deny(); } };
|
||
for (const [name, methods] of [
|
||
["http", ["request", "get"]], ["https", ["request", "get"]],
|
||
["net", ["connect", "createConnection"]], ["tls", ["connect"]],
|
||
["dgram", ["createSocket"]],
|
||
["dns", ["lookup", "resolve", "resolve4", "resolve6", "resolveAny", "reverse"]],
|
||
["dns/promises", ["lookup", "resolve", "resolve4", "resolve6", "resolveAny", "reverse"]],
|
||
]) {
|
||
const module = require(`node:${name}`);
|
||
for (const method of methods) module[method] = deny;
|
||
}
|
||
require("node:net").Socket.prototype.connect = deny;
|
||
const loaded = new Set();
|
||
const wasmReads = new Set();
|
||
const originalRead = fs.readFileSync;
|
||
fs.readFileSync = function (file, ...args) {
|
||
const filename = file instanceof URL ? fileURLToPath(file) : file;
|
||
const wasmFile = typeof filename === "string" && filename.endsWith(".wasm") ? inside(filename) : null;
|
||
const bytes = originalRead.call(this, file, ...args);
|
||
if (wasmFile) wasmReads.add(wasmFile);
|
||
return bytes;
|
||
};
|
||
syncBuiltinESMExports();
|
||
const hooks = registerHooks({
|
||
resolve(specifier, context, nextResolve) {
|
||
const result = nextResolve(specifier, context);
|
||
if (result.url.startsWith("file:")) loaded.add(inside(fileURLToPath(result.url)));
|
||
else assert.ok(result.url.startsWith("node:"), `unexpected dependency URL: ${result.url}`);
|
||
return result;
|
||
},
|
||
});
|
||
return {
|
||
report() {
|
||
assert.equal(attempts, 0, "image runtime attempted network access");
|
||
assert.ok(loaded.has(photonEntry), "Photon JS was not loaded from the payload");
|
||
assert.ok(wasmReads.has(photonWasm), "Photon WASM was not loaded from the payload");
|
||
return { modules: loaded.size, wasmFiles: [...wasmReads], networkAttempts: attempts };
|
||
},
|
||
cleanup() {
|
||
hooks.deregister();
|
||
fs.readFileSync = originalRead;
|
||
syncBuiltinESMExports();
|
||
},
|
||
};
|
||
}
|
||
|
||
/** Pure Node 2×2 RGBA PNG, with valid PNG CRCs and zlib image data. */
|
||
function fixturePng() {
|
||
const chunk = (type, data) => {
|
||
const buffer = Buffer.alloc(data.length + 12);
|
||
buffer.writeUInt32BE(data.length);
|
||
buffer.write(type, 4, "ascii");
|
||
data.copy(buffer, 8);
|
||
let crc = ~0;
|
||
for (const byte of buffer.subarray(4, -4)) {
|
||
crc ^= byte;
|
||
for (let bit = 0; bit < 8; bit++) crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
|
||
}
|
||
buffer.writeUInt32BE(~crc >>> 0, buffer.length - 4);
|
||
return buffer;
|
||
};
|
||
const header = Buffer.alloc(13);
|
||
header.writeUInt32BE(2, 0);
|
||
header.writeUInt32BE(2, 4);
|
||
header[8] = 8; // bit depth
|
||
header[9] = 6; // RGBA
|
||
const pixels = Buffer.from([
|
||
0, 255, 0, 0, 255, 0, 255, 0, 255,
|
||
0, 0, 0, 255, 255, 255, 255, 255, 255,
|
||
]);
|
||
return Buffer.concat([Buffer.from("89504e470d0a1a0a", "hex"), chunk("IHDR", header), chunk("IDAT", deflateSync(pixels)), chunk("IEND", Buffer.alloc(0))]);
|
||
}
|
||
|
||
function assertResized(result) {
|
||
assert.ok(result, "resize returned null (missing/broken Photon must not silently pass)");
|
||
assert.equal(result.originalWidth, 2);
|
||
assert.equal(result.originalHeight, 2);
|
||
assert.equal(result.width, 1);
|
||
assert.equal(result.height, 1);
|
||
assert.equal(result.wasResized, true);
|
||
assert.equal(result.mimeType, "image/png");
|
||
const png = Buffer.from(result.data, "base64");
|
||
assert.equal(png.subarray(0, 8).toString("hex"), "89504e470d0a1a0a");
|
||
assert.equal(png.readUInt32BE(16), 1);
|
||
assert.equal(png.readUInt32BE(20), 1);
|
||
}
|
||
|
||
async function probeWorker(root, inputBytes, options, responseType) {
|
||
const readyType = "lh:image-runtime-ready";
|
||
const worker = new Worker(`
|
||
const { parentPort, workerData } = require("node:worker_threads");
|
||
const guards = (${installGuards.toString()})(require, workerData.root);
|
||
const post = parentPort.postMessage.bind(parentPort);
|
||
parentPort.postMessage = (message, ...args) => {
|
||
if (message?.type === workerData.responseType) {
|
||
post({ ...message, runtimeProof: guards.report() }, ...args);
|
||
} else post(message, ...args);
|
||
};
|
||
void import(workerData.entry).then(() => post({ type: workerData.readyType }));
|
||
`, {
|
||
eval: true,
|
||
workerData: { root, responseType, readyType, entry: pathToFileURL(path.join(root, "src/agent/image-processing/resize-worker.mjs")).href },
|
||
});
|
||
try {
|
||
return await new Promise((resolve, reject) => {
|
||
const timer = setTimeout(() => reject(new Error("image resize worker timed out")), 10_000);
|
||
const fail = (error) => { clearTimeout(timer); reject(error); };
|
||
let ready = false;
|
||
worker.once("error", fail);
|
||
worker.once("exit", (code) => fail(new Error(`image resize worker exited before response: ${code}`)));
|
||
worker.on("message", (message) => {
|
||
try {
|
||
if (message?.type === readyType) {
|
||
assert.equal(ready, false, "duplicate worker READY");
|
||
ready = true;
|
||
worker.postMessage({ inputBytes, mimeType: "image/png", options });
|
||
} else if (message?.type === responseType) {
|
||
assert.equal(ready, true, "worker replied before its import completed");
|
||
assert.equal(message.error, undefined, `worker resize failed: ${message.error}`);
|
||
assertResized(message.result);
|
||
assert.ok(message.runtimeProof?.modules > 0, "missing worker module-confinement proof");
|
||
assert.equal(message.runtimeProof.networkAttempts, 0);
|
||
assert.equal(message.runtimeProof.wasmFiles.length, 1);
|
||
clearTimeout(timer);
|
||
resolve(message.runtimeProof);
|
||
}
|
||
} catch (error) { fail(error); }
|
||
});
|
||
});
|
||
} finally { await worker.terminate(); }
|
||
}
|
||
|
||
async function probe(standalone) {
|
||
const root = fs.realpathSync(standalone);
|
||
const guards = installGuards(createRequire(import.meta.url), root);
|
||
try {
|
||
const entry = pathToFileURL(path.join(root, "src/agent/image-processing/resize-core.mjs"));
|
||
const { resizeImageInProcess, IMAGE_RESIZE_WORKER_RESPONSE_TYPE } = await import(entry.href);
|
||
assert.equal(typeof resizeImageInProcess, "function");
|
||
assert.equal(IMAGE_RESIZE_WORKER_RESPONSE_TYPE, "pi:image-resize-response");
|
||
const input = fixturePng();
|
||
const options = { maxWidth: 1, maxHeight: 1, maxBytes: 1024 * 1024 };
|
||
assertResized(await resizeImageInProcess(input, "image/png", options));
|
||
const inProcess = guards.report();
|
||
console.log(`[smoke-image-runtime] shipped resize core: PNG 2×2 → 1×1; ${inProcess.modules} modules and Photon WASM confined to ${root}`);
|
||
const worker = await probeWorker(root, input, options, IMAGE_RESIZE_WORKER_RESPONSE_TYPE);
|
||
guards.report();
|
||
console.log(`[smoke-image-runtime] shipped resize worker: READY then tagged PNG 2×2 → 1×1; ${worker.modules} modules and Photon WASM confined to ${root}`);
|
||
console.log("[smoke-image-runtime] Photon in-process and worker resizing passed; zero network attempts");
|
||
} finally { guards.cleanup(); }
|
||
}
|
||
|
||
async function main() {
|
||
const args = process.argv.slice(2);
|
||
if (args[0] === "--probe") return probe(args[1]);
|
||
const value = (name, fallback) => {
|
||
const index = args.indexOf(name);
|
||
if (index < 0) return fallback;
|
||
assert.ok(args[index + 1] && !args[index + 1].startsWith("--"), `${name} requires a path`);
|
||
return path.resolve(args[index + 1]);
|
||
};
|
||
verifyImageRuntime(value("--standalone", path.resolve(import.meta.dirname, "../.next/standalone")), value("--runtime", process.execPath));
|
||
}
|
||
|
||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||
main().catch((error) => { console.error(`[smoke-image-runtime] FAIL: ${formatProbeError(error)}`); process.exitCode = 1; });
|
||
}
|