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" }; } }