desktop/preload.ts
import { contextBridge, ipcRenderer } from "electron";
import type { UpdateCommand, UpdateState } from "../shared/updates.js";
contextBridge.exposeInMainWorld("desktop", {
platform: process.platform,
// The window has an OS material behind it: macOS vibrancy, or Mica and
// Acrylic on Windows 11 22H2 (build 22621) and later.
materials: process.platform === "darwin" ||
(process.platform === "win32" && Number(process.getSystemVersion().split(".")[2]) >= 22621),
setMaterial: (material: "none" | "mica" | "acrylic") => ipcRenderer.send("window-material", material),
setScheme: (mode: "system" | "light" | "dark") => ipcRenderer.send("window-scheme", mode),
// Outside macOS: the app's drawn traffic lights or the system caption buttons.
windowControls: process.platform !== "darwin" && process.argv?.includes("--emc2-window-controls=windows") ? "windows" : "mac",
setWindowControls: (controls: "mac" | "windows") => ipcRenderer.send("window-controls", controls),
setOverlay: (colors: { color: string; symbolColor: string }) => ipcRenderer.send("window-overlay", colors),
mailMenu: (items: unknown, position?: unknown) => ipcRenderer.invoke("mail-menu", items, position),
credentialFallback: process.argv?.includes("--emc2-credential-fallback=linux-local")
? "linux-local" : undefined,
updates: process.argv?.includes("--emc2-disable-updates") ? undefined : {
state: () => ipcRenderer.invoke("update-state"),
command: (command: UpdateCommand) => ipcRenderer.invoke("update-command", command),
onState: (callback: (state: UpdateState) => void) => {
const listener = (_event: Electron.IpcRendererEvent, state: UpdateState) => callback(state);
ipcRenderer.on("update-state", listener);
return () => ipcRenderer.removeListener("update-state", listener);
},
onDrain: (callback: () => Promise<void>) => {
const listener = (_event: Electron.IpcRendererEvent, request: string) => {
if (typeof request !== "string" || !/^[a-f0-9]{32}$/.test(request)) return;
void callback().then(() => ipcRenderer.send("update-drained", request), () => {});
};
ipcRenderer.on("update-drain", listener);
return () => ipcRenderer.removeListener("update-drain", listener);
},
},
onNavigation: (callback: (direction: "back" | "forward") => void) => {
const listener = (_event: Electron.IpcRendererEvent, direction: string) => {
if (direction === "back" || direction === "forward") callback(direction);
};
ipcRenderer.on("mail-navigation", listener);
return () => ipcRenderer.removeListener("mail-navigation", listener);
},
onMailChanged: (callback: () => void) => {
const listener = () => callback();
ipcRenderer.on("mail-changed", listener);
return () => ipcRenderer.removeListener("mail-changed", listener);
},
oauthUrl: "http://127.0.0.1:32145/api/oauth/google",
windowCommand: (command: "minimize" | "maximize" | "close") =>
ipcRenderer.send("window-command", command),
onWindowState: (callback: (maximized: boolean) => void) => {
const listener = (_event: Electron.IpcRendererEvent, maximized: boolean) =>
callback(maximized);
ipcRenderer.on("window-state", listener);
return () => ipcRenderer.removeListener("window-state", listener);
},
onAccountConnected: (callback: (error: string | null) => void) => {
const listener = (
_event: Electron.IpcRendererEvent,
error: string | null,
) => callback(error);
ipcRenderer.on("account-connected", listener);
return () => ipcRenderer.removeListener("account-connected", listener);
},
});