Files
Recordly/electron/ipc/utils.ts
T
webadderall d9f40571a6 refactor: split handlers.ts into focused sub-modules
handlers.ts was ~5967 lines. Extracted into 22 focused modules:

- ipc/types.ts — shared TypeScript interfaces and types
- ipc/constants.ts — module-level constants
- ipc/state.ts — all mutable state with typed setters
- ipc/utils.ts — shared low-level utilities (getScreen, normalizePath, etc.)
- ipc/ffmpeg/binary.ts — ffmpeg binary resolution
- ipc/ffmpeg/filters.ts — audio sync/filter builders
- ipc/captions/parser.ts — SRT/Whisper JSON parsers
- ipc/captions/whisper.ts — Whisper model download/status
- ipc/captions/generate.ts — auto-caption generation
- ipc/paths/binaries.ts — native binary path resolution
- ipc/cursor/monitor.ts — cursor monitor process management
- ipc/cursor/telemetry.ts — cursor sampling and telemetry
- ipc/cursor/bounds.ts — window bounds capture and resolution
- ipc/cursor/interaction.ts — mouse hook and interaction capture
- ipc/recording/events.ts — recording lifecycle events
- ipc/recording/diagnostics.ts — media validation and diagnostics
- ipc/recording/prune.ts — auto-recording cleanup
- ipc/recording/ffmpeg.ts — FFmpeg screen capture
- ipc/recording/windows.ts — Windows native capture (WGC)
- ipc/recording/mac.ts — Mac ScreenCaptureKit integration
- ipc/export/native-video.ts — native video export sessions
- ipc/project/session.ts — recording session manifests
- ipc/project/manager.ts — project library and file management

handlers.ts reduced from 5967 → 2930 lines (registerIpcHandlers + helpers only)
2026-04-17 19:57:26 +10:00

106 lines
2.7 KiB
TypeScript

import { createRequire } from "node:module";
import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { app } from "electron";
import { RECORDINGS_DIR } from "../appPaths";
import { RECORDINGS_SETTINGS_FILE, AUTO_RECORDING_PREFIX } from "./constants";
import {
customRecordingsDir,
setCustomRecordingsDir,
recordingsDirLoaded,
setRecordingsDirLoaded,
} from "./state";
const nodeRequire = createRequire(import.meta.url);
export function getScreen() {
if (!app.isReady()) {
throw new Error(
"getScreen() called before app is ready. Ensure all screen access happens after app.whenReady().",
);
}
return nodeRequire("electron").screen as typeof import("electron").screen;
}
export function normalizePath(filePath: string) {
return path.resolve(filePath);
}
export function normalizeVideoSourcePath(videoPath?: string | null): string | null {
if (typeof videoPath !== "string") {
return null;
}
const trimmed = videoPath.trim();
if (!trimmed) {
return null;
}
if (/^file:\/\//i.test(trimmed)) {
try {
return fileURLToPath(trimmed);
} catch {
// Fall through and keep best-effort string path below.
}
}
return trimmed;
}
export function parseWindowId(sourceId?: string) {
if (!sourceId) return null;
const match = sourceId.match(/^window:(\d+)/);
return match ? Number.parseInt(match[1], 10) : null;
}
export function getTelemetryPathForVideo(videoPath: string) {
return `${videoPath}.cursor.json`;
}
export function isAutoRecordingPath(filePath: string) {
return path.basename(filePath).startsWith(AUTO_RECORDING_PREFIX);
}
export async function moveFileWithOverwrite(sourcePath: string, destinationPath: string) {
await fs.mkdir(path.dirname(destinationPath), { recursive: true });
await fs.rm(destinationPath, { force: true });
try {
await fs.rename(sourcePath, destinationPath);
} catch (error) {
const nodeError = error as NodeJS.ErrnoException;
if (nodeError.code !== "EXDEV") {
throw error;
}
await fs.copyFile(sourcePath, destinationPath);
await fs.unlink(sourcePath);
}
}
async function loadRecordingsDirectorySetting() {
if (recordingsDirLoaded) {
return;
}
setRecordingsDirLoaded(true);
try {
const content = await fs.readFile(RECORDINGS_SETTINGS_FILE, "utf-8");
const parsed = JSON.parse(content) as { recordingsDir?: unknown };
if (typeof parsed.recordingsDir === "string" && parsed.recordingsDir.trim()) {
setCustomRecordingsDir(path.resolve(parsed.recordingsDir));
}
} catch {
setCustomRecordingsDir(null);
}
}
export async function getRecordingsDir() {
await loadRecordingsDirectorySetting();
const targetDir = customRecordingsDir ?? RECORDINGS_DIR;
await fs.mkdir(targetDir, { recursive: true });
return targetDir;
}