Files
linuxcncWebWASM/web/workspace/opfs-runtime.mjs

89 lines
5.6 KiB
JavaScript

import { fnv1a64 } from "../worker/replay.mjs";
export const OPFS_RUNTIME_SCHEMA = "opfs-runtime.v1";
const FILE_NAME = "commit-record.json";
const TEXT_ENCODER = new TextEncoder();
const PROBE_TIMEOUT_MS = 1500;
function capability(storage) {
return storage && typeof storage.getDirectory === "function";
}
export function opfsCapability(scope = globalThis) {
const storage = scope?.navigator?.storage;
if (!capability(storage)) return { status: "blocked", code: "opfs_unavailable", reason: "storage_get_directory_missing" };
return { status: "verified", code: "opfs_available", mode: "dedicated_worker_sync_access_handle" };
}
function validProjectId(projectId) {
return typeof projectId === "string" && /^[a-z0-9][a-z0-9_-]{0,63}$/i.test(projectId);
}
function committedRecord(projectId) {
const content = "s6-05-opfs-round-trip";
return { schema: OPFS_RUNTIME_SCHEMA, project_id: projectId, phase: "commit", commit_seq: 1, content, content_hash: fnv1a64(content) };
}
function validateCommittedRecord(record, projectId) {
return record?.schema === OPFS_RUNTIME_SCHEMA && record.project_id === projectId && record.phase === "commit" && record.commit_seq === 1 && record.content_hash === fnv1a64(record.content);
}
async function rootHandle(storage) {
const timeout = new Promise((resolve) => setTimeout(() => resolve({ timeout: true }), PROBE_TIMEOUT_MS));
const rootResult = await Promise.race([storage.getDirectory().then((root) => ({ root })), timeout]);
return rootResult.timeout ? { blocked: { code: "opfs_probe_timeout", stage: "root_start" } } : { root: rootResult.root };
}
export async function runOpfsProbe({ storage, projectId = "s6-05-opfs", mode = "round_trip", cleanup = true } = {}) {
const probe = opfsCapability({ navigator: { storage } });
if (probe.status !== "verified") return { schema: OPFS_RUNTIME_SCHEMA, ...probe, project_id: projectId };
if (!validProjectId(projectId)) return { schema: OPFS_RUNTIME_SCHEMA, status: "blocked", code: "opfs_project_id_invalid" };
if (!["round_trip", "write", "read", "corrupt"].includes(mode)) return { schema: OPFS_RUNTIME_SCHEMA, status: "blocked", code: "opfs_mode_invalid", project_id: projectId };
let access = null;
try {
const rootResult = await rootHandle(storage);
if (rootResult.blocked) return { schema: OPFS_RUNTIME_SCHEMA, status: "blocked", ...rootResult.blocked, project_id: projectId };
const root = rootResult.root;
const directory = await root.getDirectoryHandle("linuxcnc-web-wasm", { create: true });
const project = await directory.getDirectoryHandle(projectId, { create: true });
const file = await project.getFileHandle(FILE_NAME, { create: mode !== "read" });
if (mode === "read") {
const persisted = JSON.parse(await (await file.getFile()).text());
const valid = validateCommittedRecord(persisted, projectId);
if (cleanup) { await project.removeEntry(FILE_NAME); await directory.removeEntry(projectId); }
return valid ? { schema: OPFS_RUNTIME_SCHEMA, status: "verified", mode, project_id: projectId, commit_seq: persisted.commit_seq, content_hash: persisted.content_hash, cleanup } : { schema: OPFS_RUNTIME_SCHEMA, status: "blocked", code: "opfs_persisted_record_invalid", project_id: projectId };
}
if (typeof file.createSyncAccessHandle !== "function") return { schema: OPFS_RUNTIME_SCHEMA, status: "blocked", code: "opfs_sync_access_handle_missing", project_id: projectId };
access = await file.createSyncAccessHandle();
const record = committedRecord(projectId);
const encoded = TEXT_ENCODER.encode(JSON.stringify(record));
access.truncate(0);
const written = access.write(encoded, { at: 0 });
access.flush();
const size = access.getSize();
access.close();
access = null;
if (mode === "write") return { schema: OPFS_RUNTIME_SCHEMA, status: "verified", mode, project_id: projectId, bytes: encoded.byteLength, commit_seq: record.commit_seq, content_hash: record.content_hash, cleanup: false };
if (mode === "corrupt") {
const corrupt = await file.createSyncAccessHandle();
corrupt.write(TEXT_ENCODER.encode("{corrupt"), { at: 0 });
corrupt.flush();
corrupt.close();
let damaged = null;
try { damaged = JSON.parse(await (await file.getFile()).text()); } catch { damaged = null; }
if (cleanup) { await project.removeEntry(FILE_NAME); await directory.removeEntry(projectId); }
return !validateCommittedRecord(damaged, projectId)
? { schema: OPFS_RUNTIME_SCHEMA, status: "verified", mode, project_id: projectId, corruption_detected: true, cleanup }
: { schema: OPFS_RUNTIME_SCHEMA, status: "blocked", code: "opfs_corruption_undetected", project_id: projectId };
}
const persisted = JSON.parse(await (await file.getFile()).text());
const valid = written === encoded.byteLength && size === encoded.byteLength && JSON.stringify(persisted) === JSON.stringify(record);
if (cleanup) { await project.removeEntry(FILE_NAME); await directory.removeEntry(projectId); }
if (!valid) return { schema: OPFS_RUNTIME_SCHEMA, status: "blocked", code: "opfs_round_trip_mismatch", project_id: projectId };
return { schema: OPFS_RUNTIME_SCHEMA, status: "verified", mode: "dedicated_worker_sync_access_handle", project_id: projectId, bytes: encoded.byteLength, commit_seq: record.commit_seq, content_hash: record.content_hash, cleanup: true };
} catch (error) {
try { access?.close(); } catch { /* best effort close after a browser fault */ }
return { schema: OPFS_RUNTIME_SCHEMA, status: "blocked", code: "opfs_probe_failed", project_id: projectId, reason: error?.name ?? "unknown" };
}
}