8458cbb40e
Sandboxed preloads (Electron's default with contextIsolation) cannot
require node modules. Commit 702b733 added node:path / node:url imports
to preload.ts which fail at load time:
Unable to load preload script: dist-electron/preload.mjs
Error: module not found: node:path
This left window.electronAPI undefined, breaking every IPC call.
Compute the asset base URL in main process (windows.ts) and pass it
to preload via webPreferences.additionalArguments. Preload reads it
from process.argv. Sync API for renderer is preserved.
164 lines
6.0 KiB
TypeScript
164 lines
6.0 KiB
TypeScript
import { contextBridge, ipcRenderer } from "electron";
|
|
import type { RecordingSession, StoreRecordedSessionInput } from "../src/lib/recordingSession";
|
|
|
|
// Asset base URL is passed from the main process via webPreferences.additionalArguments
|
|
// (see windows.ts). Sandboxed preloads cannot import node:path / node:url, so we
|
|
// can't compute it here.
|
|
const ASSET_BASE_URL_ARG_PREFIX = "--asset-base-url=";
|
|
const assetBaseUrlArg = process.argv.find((arg) => arg.startsWith(ASSET_BASE_URL_ARG_PREFIX));
|
|
const assetBaseUrl = assetBaseUrlArg ? assetBaseUrlArg.slice(ASSET_BASE_URL_ARG_PREFIX.length) : "";
|
|
|
|
contextBridge.exposeInMainWorld("electronAPI", {
|
|
assetBaseUrl,
|
|
hudOverlayHide: () => {
|
|
ipcRenderer.send("hud-overlay-hide");
|
|
},
|
|
hudOverlayClose: () => {
|
|
ipcRenderer.send("hud-overlay-close");
|
|
},
|
|
getSources: async (opts: Electron.SourcesOptions) => {
|
|
return await ipcRenderer.invoke("get-sources", opts);
|
|
},
|
|
switchToEditor: () => {
|
|
return ipcRenderer.invoke("switch-to-editor");
|
|
},
|
|
switchToHud: () => {
|
|
return ipcRenderer.invoke("switch-to-hud");
|
|
},
|
|
startNewRecording: () => {
|
|
return ipcRenderer.invoke("start-new-recording");
|
|
},
|
|
openSourceSelector: () => {
|
|
return ipcRenderer.invoke("open-source-selector");
|
|
},
|
|
selectSource: (source: ProcessedDesktopSource) => {
|
|
return ipcRenderer.invoke("select-source", source);
|
|
},
|
|
getSelectedSource: () => {
|
|
return ipcRenderer.invoke("get-selected-source");
|
|
},
|
|
requestCameraAccess: () => {
|
|
return ipcRenderer.invoke("request-camera-access");
|
|
},
|
|
|
|
storeRecordedVideo: (videoData: ArrayBuffer, fileName: string) => {
|
|
return ipcRenderer.invoke("store-recorded-video", videoData, fileName);
|
|
},
|
|
storeRecordedSession: (payload: StoreRecordedSessionInput) => {
|
|
return ipcRenderer.invoke("store-recorded-session", payload);
|
|
},
|
|
|
|
getRecordedVideoPath: () => {
|
|
return ipcRenderer.invoke("get-recorded-video-path");
|
|
},
|
|
setRecordingState: (recording: boolean) => {
|
|
return ipcRenderer.invoke("set-recording-state", recording);
|
|
},
|
|
getCursorTelemetry: (videoPath?: string) => {
|
|
return ipcRenderer.invoke("get-cursor-telemetry", videoPath);
|
|
},
|
|
onStopRecordingFromTray: (callback: () => void) => {
|
|
const listener = () => callback();
|
|
ipcRenderer.on("stop-recording-from-tray", listener);
|
|
return () => ipcRenderer.removeListener("stop-recording-from-tray", listener);
|
|
},
|
|
openExternalUrl: (url: string) => {
|
|
return ipcRenderer.invoke("open-external-url", url);
|
|
},
|
|
saveExportedVideo: (videoData: ArrayBuffer, fileName: string) => {
|
|
return ipcRenderer.invoke("save-exported-video", videoData, fileName);
|
|
},
|
|
openVideoFilePicker: () => {
|
|
return ipcRenderer.invoke("open-video-file-picker");
|
|
},
|
|
setCurrentVideoPath: (path: string) => {
|
|
return ipcRenderer.invoke("set-current-video-path", path);
|
|
},
|
|
setCurrentRecordingSession: (session: RecordingSession | null) => {
|
|
return ipcRenderer.invoke("set-current-recording-session", session);
|
|
},
|
|
getCurrentVideoPath: () => {
|
|
return ipcRenderer.invoke("get-current-video-path");
|
|
},
|
|
getCurrentRecordingSession: () => {
|
|
return ipcRenderer.invoke("get-current-recording-session");
|
|
},
|
|
readBinaryFile: (filePath: string) => {
|
|
return ipcRenderer.invoke("read-binary-file", filePath);
|
|
},
|
|
clearCurrentVideoPath: () => {
|
|
return ipcRenderer.invoke("clear-current-video-path");
|
|
},
|
|
saveProjectFile: (projectData: unknown, suggestedName?: string, existingProjectPath?: string) => {
|
|
return ipcRenderer.invoke("save-project-file", projectData, suggestedName, existingProjectPath);
|
|
},
|
|
loadProjectFile: () => {
|
|
return ipcRenderer.invoke("load-project-file");
|
|
},
|
|
loadCurrentProjectFile: () => {
|
|
return ipcRenderer.invoke("load-current-project-file");
|
|
},
|
|
onMenuLoadProject: (callback: () => void) => {
|
|
const listener = () => callback();
|
|
ipcRenderer.on("menu-load-project", listener);
|
|
return () => ipcRenderer.removeListener("menu-load-project", listener);
|
|
},
|
|
onMenuSaveProject: (callback: () => void) => {
|
|
const listener = () => callback();
|
|
ipcRenderer.on("menu-save-project", listener);
|
|
return () => ipcRenderer.removeListener("menu-save-project", listener);
|
|
},
|
|
onMenuSaveProjectAs: (callback: () => void) => {
|
|
const listener = () => callback();
|
|
ipcRenderer.on("menu-save-project-as", listener);
|
|
return () => ipcRenderer.removeListener("menu-save-project-as", listener);
|
|
},
|
|
getPlatform: () => {
|
|
return ipcRenderer.invoke("get-platform");
|
|
},
|
|
revealInFolder: (filePath: string) => {
|
|
return ipcRenderer.invoke("reveal-in-folder", filePath);
|
|
},
|
|
getShortcuts: () => {
|
|
return ipcRenderer.invoke("get-shortcuts");
|
|
},
|
|
saveShortcuts: (shortcuts: unknown) => {
|
|
return ipcRenderer.invoke("save-shortcuts", shortcuts);
|
|
},
|
|
setLocale: (locale: string) => {
|
|
return ipcRenderer.invoke("set-locale", locale);
|
|
},
|
|
setMicrophoneExpanded: (expanded: boolean) => {
|
|
ipcRenderer.send("hud:setMicrophoneExpanded", expanded);
|
|
},
|
|
setHasUnsavedChanges: (hasChanges: boolean) => {
|
|
ipcRenderer.send("set-has-unsaved-changes", hasChanges);
|
|
},
|
|
showCountdownOverlay: (value: number, runId: number) => {
|
|
return ipcRenderer.invoke("countdown-overlay-show", value, runId);
|
|
},
|
|
setCountdownOverlayValue: (value: number, runId: number) => {
|
|
return ipcRenderer.invoke("countdown-overlay-set-value", value, runId);
|
|
},
|
|
hideCountdownOverlay: (runId: number) => {
|
|
return ipcRenderer.invoke("countdown-overlay-hide", runId);
|
|
},
|
|
onCountdownOverlayValue: (callback: (value: number | null) => void) => {
|
|
const listener = (_event: unknown, value: number | null) => callback(value);
|
|
ipcRenderer.on("countdown-overlay-value", listener);
|
|
return () => ipcRenderer.removeListener("countdown-overlay-value", listener);
|
|
},
|
|
onRequestSaveBeforeClose: (callback: () => Promise<boolean> | boolean) => {
|
|
const listener = async () => {
|
|
try {
|
|
const shouldClose = await callback();
|
|
ipcRenderer.send("save-before-close-done", shouldClose);
|
|
} catch {
|
|
ipcRenderer.send("save-before-close-done", false);
|
|
}
|
|
};
|
|
ipcRenderer.on("request-save-before-close", listener);
|
|
return () => ipcRenderer.removeListener("request-save-before-close", listener);
|
|
},
|
|
});
|