Files
jb fd63b0ccec
fast-browser scenarios / Offline scenario contract tests (push) Failing after 44s
fast-browser scenarios / Local Chromium headless scenarios (push) Failing after 23s
fast-browser-with-jev
2026-09-20 12:24:08 -04:00

258 lines
9.5 KiB
TypeScript

import {
chmodSync,
closeSync,
fstatSync,
lstatSync,
mkdirSync,
openSync,
readlinkSync,
unlinkSync,
writeSync,
} from "node:fs";
import { hostname, homedir } from "node:os";
import { isAbsolute, join, normalize, relative } from "node:path";
import { validateBrowserProfileName } from "./config.ts";
const PROFILE_ROOT_MODE = 0o700;
const PROFILE_LOCK_MODE = 0o600;
export interface ProfileSingletonLockOwnership {
path: string;
target: string;
pid: number;
processStartTime?: string;
device: number;
inode: number;
}
export interface ProfileLease {
name: string;
profileDirectory: string;
lockPath: string;
fd?: number;
device: number;
inode: number;
/** Set only after the launched process proves the lock belongs to it. */
singletonLock?: ProfileSingletonLockOwnership;
}
export interface ProfileLeaseOptions {
/** Test seam for the managed profile root. */
profileRoot?: string;
}
export const validateProfileName = validateBrowserProfileName;
export function profileStorageRoot(env: NodeJS.ProcessEnv = process.env): string {
const dataHome = env.XDG_DATA_HOME?.trim();
if (dataHome && !isAbsolute(dataHome)) {
throw new Error("XDG_DATA_HOME must be an absolute path for named browser profiles");
}
return join(dataHome || join(env.HOME?.trim() || homedir(), ".local", "share"), "fast-browser", "profiles");
}
function assertContained(root: string, candidate: string): void {
const rootPath = normalize(root);
const candidatePath = normalize(candidate);
const remainder = relative(rootPath, candidatePath);
if (!remainder || remainder.startsWith("..") || isAbsolute(remainder)) {
throw new Error("browser profile path escaped the managed profile root");
}
}
function ensurePrivateDirectory(path: string, label: string): void {
mkdirSync(path, { recursive: true, mode: PROFILE_ROOT_MODE });
const before = lstatSync(path);
if (!before.isDirectory() || before.isSymbolicLink()) throw new Error(`${label} must be a real directory`);
if (typeof process.getuid === "function" && before.uid !== process.getuid()) {
throw new Error(`${label} is not owned by this user`);
}
chmodSync(path, PROFILE_ROOT_MODE);
const after = lstatSync(path);
if (!after.isDirectory() || after.isSymbolicLink() || (after.mode & 0o077) !== 0) {
throw new Error(`${label} must be private (0700)`);
}
}
export function profileDirectory(name: string, options: ProfileLeaseOptions = {}): string {
const safeName = validateProfileName(name);
const root = options.profileRoot ?? profileStorageRoot();
ensurePrivateDirectory(root, "browser profile root");
const directory = join(root, safeName);
assertContained(root, directory);
if (!lstatDirectoryOrMissing(directory)) throw new Error("browser profile must be a real directory, not a symlink");
mkdirSync(directory, { recursive: true, mode: PROFILE_ROOT_MODE });
ensurePrivateDirectory(directory, "browser profile");
return directory;
}
function lstatDirectoryOrMissing(path: string): boolean {
try {
const stats = lstatSync(path);
return stats.isDirectory() && !stats.isSymbolicLink();
} catch (error) {
if ((error as NodeJS.ErrnoException)?.code === "ENOENT") return true;
throw error;
}
}
function lockPathFor(root: string, name: string): string {
const path = join(root, `.${name}.fast-browser-profile.lock`);
assertContained(root, path);
return path;
}
function activeSingletonLock(directory: string): string | undefined {
const path = join(directory, "SingletonLock");
try {
const stats = lstatSync(path);
if (stats.isSymbolicLink() || stats.isFile()) return path;
throw new Error("browser profile SingletonLock is not a regular lock entry");
} catch (error) {
if ((error as NodeJS.ErrnoException)?.code === "ENOENT") return undefined;
throw error;
}
}
export function acquireProfileLease(name: string, options: ProfileLeaseOptions = {}): ProfileLease {
const safeName = validateProfileName(name);
const root = options.profileRoot ?? profileStorageRoot();
ensurePrivateDirectory(root, "browser profile root");
const directory = profileDirectory(safeName, { profileRoot: root });
const lockPath = lockPathFor(root, safeName);
let fd: number;
try {
fd = openSync(lockPath, "wx", PROFILE_LOCK_MODE);
} catch (error) {
if ((error as NodeJS.ErrnoException)?.code === "EEXIST") {
throw new Error(`browser profile ${safeName} already has an active or stale lease at ${lockPath}; verify the owning process is gone and remove the lease manually before retrying`);
}
throw new Error(`unable to acquire browser profile lease for ${safeName}`);
}
try {
const singleton = activeSingletonLock(directory);
if (singleton) {
throw new Error(`browser profile ${safeName} has SingletonLock at ${singleton}; verify Chromium is not running and remove it manually only after inspection`);
}
const metadata = JSON.stringify({ pid: process.pid, profile: safeName }) + "\n";
writeSync(fd, metadata, undefined, "utf8");
const stats = fstatSync(fd);
return {
name: safeName,
profileDirectory: directory,
lockPath,
fd,
device: stats.dev,
inode: stats.ino,
};
} catch (error) {
try { closeSync(fd); } catch { /* best effort */ }
try { unlinkSync(lockPath); } catch { /* this process owns it; preserve unexpected replacement */ }
throw error instanceof Error ? error : new Error(String(error));
}
}
export function releaseProfileLease(lease: ProfileLease): void {
let failure: Error | undefined;
try {
if (activeSingletonLock(lease.profileDirectory)) {
throw new Error(`browser profile SingletonLock remains at ${join(lease.profileDirectory, "SingletonLock")}; refusing to release the profile lease`);
}
const stats = lstatSync(lease.lockPath);
if (stats.dev !== lease.device || stats.ino !== lease.inode) {
throw new Error(`browser profile lease changed unexpectedly; refusing to remove ${lease.lockPath}`);
}
unlinkSync(lease.lockPath);
} catch (error) {
failure = error instanceof Error ? error : new Error(String(error));
} finally {
if (lease.fd !== undefined) {
const fd = lease.fd;
lease.fd = undefined;
try { closeSync(fd); } catch (error) {
if (!failure) failure = error instanceof Error ? error : new Error(String(error));
}
}
}
if (failure) throw failure;
}
function singletonLockDetails(lease: ProfileLease): ProfileSingletonLockOwnership | undefined {
const path = join(lease.profileDirectory, "SingletonLock");
let stats;
try { stats = lstatSync(path); }
catch (error) {
if ((error as NodeJS.ErrnoException)?.code === "ENOENT") return undefined;
throw error;
}
if (!stats.isSymbolicLink()) {
throw new Error(`browser profile SingletonLock is not a symlink; refusing to claim ownership of ${path}`);
}
const target = readlinkSync(path);
const separator = target.lastIndexOf("-");
const host = separator > 0 ? target.slice(0, separator) : "";
const pid = separator > 0 ? Number(target.slice(separator + 1)) : NaN;
if (host !== hostname() || !Number.isSafeInteger(pid) || pid < 1) {
throw new Error(`browser profile SingletonLock ownership is not provable for ${path}`);
}
return { path, target, pid, device: stats.dev, inode: stats.ino };
}
/**
* Record the Chromium SingletonLock only when it names the process we spawned.
* A missing marker is normal during early startup; an unexpected marker is
* retained as an ownership failure rather than guessed to be stale.
*/
export function captureOwnedSingletonLock(
lease: ProfileLease,
pid: number | undefined,
processStartTime?: string,
): void {
if (pid === undefined) return;
const details = singletonLockDetails(lease);
if (!details) return;
if (details.pid !== pid) {
throw new Error(`browser profile SingletonLock names pid ${details.pid}, not owned browser pid ${pid}`);
}
lease.singletonLock = processStartTime === undefined
? details
: { ...details, processStartTime };
}
/**
* Remove only the exact marker captured from our launched process, and only
* after the caller has independently proved that that process is gone.
*/
export function removeOwnedSingletonLock(
lease: ProfileLease,
processGone: (ownership: ProfileSingletonLockOwnership) => boolean,
): void {
const current = singletonLockDetails(lease);
if (!current) return;
const owned = lease.singletonLock;
if (!owned || owned.path !== current.path || owned.target !== current.target || owned.device !== current.device || owned.inode !== current.inode) {
throw new Error(`browser profile SingletonLock ownership is uncertain at ${current.path}; retaining the profile lease`);
}
if (!processGone(owned)) {
throw new Error(`browser profile SingletonLock owner has not been proven gone at ${current.path}; retaining the profile lease`);
}
unlinkSync(current.path);
lease.singletonLock = undefined;
}
/** Remove stale CDP metadata only while an exclusive named-profile lease is held. */
export function removeStaleDevToolsActivePort(lease: ProfileLease): void {
const path = join(lease.profileDirectory, "DevToolsActivePort");
const stats = (() => {
try { return lstatSync(path); }
catch (error) {
if ((error as NodeJS.ErrnoException)?.code === "ENOENT") return undefined;
throw error;
}
})();
if (!stats) return;
if (stats.isSymbolicLink() || !stats.isFile()) {
throw new Error(`browser profile DevToolsActivePort is not a regular file; refusing to remove ${path}`);
}
unlinkSync(path);
}