Files
Recordly/electron/instanceSession.ts
T
webadderall eaf87be95b Recordly 2.0 local review stack 2/6
Dependency-ordered review preparation. Not pushed or remotely reviewed.
2026-11-06 05:24:21 +11:00

63 lines
2.1 KiB
TypeScript

import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import path from "node:path";
import { lockSync } from "proper-lockfile";
const STALE_MS = 30_000;
function hasLiveOwner(ownerPath: string): boolean {
try {
const pid: unknown = JSON.parse(readFileSync(ownerPath, "utf8")).pid;
if (typeof pid !== "number" || !Number.isSafeInteger(pid) || pid <= 0) return false;
try {
process.kill(pid, 0);
return true;
} catch (error) {
return (error as NodeJS.ErrnoException).code !== "ESRCH";
}
} catch {
return false;
}
}
/** Reserve durable Chromium storage without opening another process's profile. */
export function reserveInstanceSession(userDataPath: string, primarySessionPath: string) {
const sessionsPath = path.join(userDataPath, "instance-sessions");
const locksPath = path.join(sessionsPath, ".locks");
mkdirSync(locksPath, { recursive: true });
for (let slot = 0; slot < 128; slot++) {
const name = slot === 0 ? "primary" : `slot-${slot}`;
const sessionPath = slot === 0 ? primarySessionPath : path.join(sessionsPath, name);
const lockfilePath = path.join(locksPath, `${name}.lock`);
const ownerPath = path.join(locksPath, `${name}.owner.json`);
// A sleeping Mac pauses heartbeats. Never steal a live owner's profile
// merely because its lock timestamp has aged beyond the stale threshold.
if (hasLiveOwner(ownerPath)) continue;
mkdirSync(sessionPath, { recursive: true });
let unlock: () => void;
try {
unlock = lockSync(sessionPath, { lockfilePath, stale: STALE_MS });
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ELOCKED") continue;
throw error;
}
try {
writeFileSync(ownerPath, JSON.stringify({ pid: process.pid }), { mode: 0o600 });
} catch (error) {
unlock();
throw error;
}
let released = false;
return {
sessionPath,
release() {
if (released) return;
released = true;
// Remove our marker before unlocking; a new owner must not lose its marker.
rmSync(ownerPath, { force: true });
unlock();
},
};
}
throw new Error("No free Recordly session profile is available.");
}