client/apps/electron/src/preload/preload.ts
Alois 790a1db788
All checks were successful
/ build-web (push) Successful in 7m26s
/ build-desktop (linux) (push) Successful in 11m29s
/ build-mobile (push) Successful in 19m15s
/ release (push) Successful in 3m3s
(feat): add cache package
(feat): improve local storage security
(feat): move settings to dedicated settings package
2026-07-11 22:08:01 +02:00

89 lines
3 KiB
TypeScript

import { contextBridge, ipcRenderer } from "electron";
import {
ipcChannels,
type DesktopCallStatus,
type DesktopScreenShareSource,
secureStorageLimits,
} from "../shared/ipc.js";
function windowAction(channel: string) {
return () => ipcRenderer.invoke(channel);
}
function validKey(key: string) {
return (
typeof key === "string" &&
key.length > 0 &&
Buffer.byteLength(key, "utf8") <= secureStorageLimits.maxKeyBytes
);
}
const desktopApi = {
media: {
listScreenShareSources: () =>
ipcRenderer.invoke(ipcChannels.listScreenShareSources),
listScreenShareAudioOutputs: () =>
ipcRenderer.invoke(ipcChannels.listScreenShareAudioOutputs),
getScreenShareCapabilities: () =>
ipcRenderer.invoke(ipcChannels.getScreenShareCapabilities),
selectScreenShareSource: (sourceId: DesktopScreenShareSource["id"]) => {
if (typeof sourceId !== "string" || sourceId.length === 0) {
return Promise.reject(new Error("Invalid screen share source id."));
}
return ipcRenderer.invoke(ipcChannels.selectScreenShareSource, sourceId);
},
},
app: {
getVersion: () => ipcRenderer.invoke(ipcChannels.getVersion),
},
updates: {
checkForUpdates: () => ipcRenderer.invoke(ipcChannels.checkForUpdates),
},
call: {
setStatus: (status: DesktopCallStatus) => {
if (
typeof status?.inCall !== "boolean" ||
typeof status?.speaking !== "boolean" ||
(status.iconDataUrl !== undefined &&
(typeof status.iconDataUrl !== "string" ||
!status.iconDataUrl.startsWith("data:image/png;base64,")))
) {
return Promise.reject(new Error("Invalid call status."));
}
return ipcRenderer.invoke(ipcChannels.setCallStatus, status);
},
},
secureStorage: {
getStatus: () => ipcRenderer.invoke(ipcChannels.getSecureStorageStatus),
load: (key: string) =>
validKey(key)
? ipcRenderer.invoke(ipcChannels.loadSecureStorage, key)
: Promise.reject(new Error("Invalid secure storage key.")),
save: (key: string, value: string) =>
validKey(key) &&
typeof value === "string" &&
Buffer.byteLength(value, "utf8") <= secureStorageLimits.maxValueBytes
? ipcRenderer.invoke(ipcChannels.saveSecureStorage, key, value)
: Promise.reject(new Error("Invalid secure storage key or value.")),
delete: (key: string) =>
validKey(key)
? ipcRenderer.invoke(ipcChannels.deleteSecureStorage, key)
: Promise.reject(new Error("Invalid secure storage key.")),
clear: () => ipcRenderer.invoke(ipcChannels.clearSecureStorage),
},
window: {
minimize: () => windowAction(ipcChannels.minimizeWindow),
maximize: () => windowAction(ipcChannels.maximizeWindow),
close: () => windowAction(ipcChannels.closeWindow),
},
};
contextBridge.exposeInMainWorld("tensaminDesktop", desktopApi);
contextBridge.exposeInMainWorld(
"tensaminShowWindowControls",
process.env.TENSAMIN_HIDE_CONTROLS == null,
);
export type TensaminDesktopApi = typeof desktopApi;