const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('cryptoAPI', { generateKeys: () => ipcRenderer.invoke('generate-keys'), randomBytes: (size) => ipcRenderer.invoke('random-bytes', size), sha256: (data) => ipcRenderer.invoke('sha256', data), signMessage: (privateKey, message) => ipcRenderer.invoke('sign-message', privateKey, message), verifySignature: (publicKey, message, signature) => ipcRenderer.invoke('verify-signature', publicKey, message, signature), deriveAuthKeys: (password, salt) => ipcRenderer.invoke('derive-auth-keys', password, salt), encryptData: (data, key) => ipcRenderer.invoke('encrypt-data', data, key), decryptData: (encryptedData, key, iv, tag, options) => ipcRenderer.invoke('decrypt-data', encryptedData, key, iv, tag, options), decryptBatch: (items) => ipcRenderer.invoke('decrypt-batch', items), verifyBatch: (items) => ipcRenderer.invoke('verify-batch', items), // RSA Helpers publicEncrypt: (publicKey, data) => ipcRenderer.invoke('public-encrypt', publicKey, data), privateDecrypt: (privateKey, encryptedHex) => ipcRenderer.invoke('private-decrypt', privateKey, encryptedHex), fetchMetadata: (url) => ipcRenderer.invoke('fetch-metadata', url), openExternal: (url) => ipcRenderer.invoke('open-external', url), getScreenSources: () => ipcRenderer.invoke('get-screen-sources'), setPendingScreenSource: (payload) => ipcRenderer.invoke('screen-share:set-pending-source', payload), }); contextBridge.exposeInMainWorld('windowControls', { minimize: () => ipcRenderer.send('window-minimize'), maximize: () => ipcRenderer.send('window-maximize'), close: () => ipcRenderer.send('window-close'), flashFrame: (on) => ipcRenderer.send('flash-frame', on !== false), }); contextBridge.exposeInMainWorld('notificationAPI', { show: (opts) => ipcRenderer.send('notification:show', opts), setBadge: (count) => ipcRenderer.send('notification:set-badge', count), flashFrame: (on) => ipcRenderer.send('flash-frame', on !== false), }); contextBridge.exposeInMainWorld('appSettings', { get: (key) => ipcRenderer.invoke('get-setting', key), set: (key, value) => ipcRenderer.invoke('set-setting', key, value), }); contextBridge.exposeInMainWorld('updateAPI', { checkFlatpakUpdate: () => ipcRenderer.invoke('check-flatpak-update'), getStatus: () => ipcRenderer.invoke('update:get-status'), downloadAndInstall: () => ipcRenderer.invoke('update:download-and-install'), onStatusChanged: (callback) => { const handler = (_event, status) => callback(status); ipcRenderer.on('update:status', handler); return () => ipcRenderer.removeListener('update:status', handler); }, }); contextBridge.exposeInMainWorld('lifecycleAPI', { get: () => ipcRenderer.invoke('lifecycle:get'), set: (patch) => ipcRenderer.invoke('lifecycle:set', patch), show: () => ipcRenderer.send('window:show'), onTrayAction: (callback) => { const handler = (_event, action) => callback(action); ipcRenderer.on('tray:action', handler); return () => ipcRenderer.removeListener('tray:action', handler); }, }); contextBridge.exposeInMainWorld('sessionPersistence', { save: (data) => ipcRenderer.invoke('save-session', data), load: () => ipcRenderer.invoke('load-session'), clear: () => ipcRenderer.invoke('clear-session'), }); contextBridge.exposeInMainWorld('searchStorage', { load: (userId) => ipcRenderer.invoke('search-db-load', userId), save: (userId, data) => ipcRenderer.invoke('search-db-save', userId, data), clear: (userId) => ipcRenderer.invoke('search-db-clear', userId), }); contextBridge.exposeInMainWorld('idleAPI', { onIdleStateChanged: (callback) => ipcRenderer.on('idle-state-changed', (_event, data) => callback(data)), removeIdleStateListener: () => ipcRenderer.removeAllListeners('idle-state-changed'), getSystemIdleTime: () => ipcRenderer.invoke('get-system-idle-time'), }); // Voice recording — per-participant audio capture that writes // append-only WebM files to the user's chosen folder. See // apps/electron/main.cjs for the main-process implementation. // Discord backup importer — exposes a native file picker for the // backup SQLite file plus file-read helpers the renderer uses to // load the db bytes (parsed in-renderer with sql.js) and to read // individual attachment files off disk during import. contextBridge.exposeInMainWorld('importerAPI', { pickDatabase: () => ipcRenderer.invoke('importer:pick-database'), readDatabase: (dbPath) => ipcRenderer.invoke('importer:read-database', dbPath), readAttachment: (payload) => ipcRenderer.invoke('importer:read-attachment', payload), }); contextBridge.exposeInMainWorld('recordingAPI', { getDefaultFolder: () => ipcRenderer.invoke('recording-get-default-folder'), pickFolder: () => ipcRenderer.invoke('recording-pick-folder'), validateFolder: (dir) => ipcRenderer.invoke('recording-validate-folder', dir), openFolder: (dir) => ipcRenderer.invoke('recording-open-folder', dir), openSessionFolder: (payload) => ipcRenderer.invoke('recording-open-session-folder', payload), startSession: (payload) => ipcRenderer.invoke('recording-start-session', payload), openTrack: (payload) => ipcRenderer.invoke('recording-open-track', payload), append: (payload) => ipcRenderer.invoke('recording-append', payload), closeTrack: (payload) => ipcRenderer.invoke('recording-close-track', payload), finalize: (payload) => ipcRenderer.invoke('recording-finalize', payload), listRecoverable: (payload) => ipcRenderer.invoke('recording-list-recoverable', payload), recoverSession: (payload) => ipcRenderer.invoke('recording-recover-session', payload), });