// Minimal, safe bridge into the web UI. Runs with contextIsolation, so it only exposes a // frozen marker the web app can feature-detect against (e.g. to hide the PWA install prompt // or prefer native push). No Node APIs are exposed to page JS. const { contextBridge, ipcRenderer } = require('electron'); // Pull the stable install id + version/os from main (synchronous, one-time at preload). let info = { installId: '', appVersion: '', os: '' }; try { info = ipcRenderer.sendSync('get-install-info') || info; } catch (_) {} contextBridge.exposeInMainWorld('__NATIVE__', 'desktop'); contextBridge.exposeInMainWorld('bizConnectNative', Object.freeze({ platform: 'desktop', version: info.appVersion || '0.1.0', installId: info.installId || '', os: info.os || '', // Bring the app window to the foreground (e.g. when a notification is clicked) — the web // Notification's window.focus() can't raise an Electron window; the main process must. focusApp: () => ipcRenderer.send('focus-window'), // Show the unread-chat count as a badge on the taskbar icon. count=number of chats with // unread; dataUrl=a small PNG badge the renderer drew (null to clear). setUnread: (count, dataUrl) => ipcRenderer.send('set-unread', { count, dataUrl }), // Native Windows toast with an inline reply box. Resolves to {text} (replied), {open} (clicked) // or null. Lets the user reply to a chat straight from the notification. replyNotify: (payload) => ipcRenderer.invoke('reply-notification', payload), // Pre-warm the notification DP cache with contact photo URLs (called after chats load) so the first // toast from anyone already has their photo — no per-notification download lag. precacheAvatars: (urls) => { try { return ipcRenderer.invoke('precache-avatars', urls); } catch (_) { return Promise.resolve(false); } }, // Ask the shell to show native spelling suggestions for the word at page coords (x,y) — used to bring // up corrections on a LEFT click in the message box (not just right-click). spellSuggestAt: (x, y) => { try { ipcRenderer.send('spell-suggest', { x, y }); } catch (_) {} }, // Remote control (screen the local user is sharing): whether OS injection is possible on this machine, // arm/disarm the consent gate, and forward a viewer's input event for injection. Injection only happens // while armed (the user granted control) — see main.js rcArmed. rcAvailable: () => { try { return !!ipcRenderer.sendSync('rc-available'); } catch (_) { return false; } }, rcArm: (on) => { try { ipcRenderer.send('rc-arm', !!on); } catch (_) {} }, rcInput: (evt) => { try { ipcRenderer.send('rc-input', evt); } catch (_) {} }, // Force the newest web build: clears the shell's HTTP cache and reloads ignoring cache. The app closes // to tray and can run for weeks, so without this it would keep serving the page it first loaded. hardReload: () => { try { return ipcRenderer.invoke('hard-reload'); } catch (_) { return Promise.resolve(false); } }, // Manual "Check for updates" from Settings. Resolves {status:'available'|'current'|'dev'|'error', version?}. // On 'available' the shell downloads in the background and prompts to restart when ready. checkForUpdates: () => ipcRenderer.invoke('check-updates'), // #3: subscribe to auto-update lifecycle events so the UI can show download progress + "ready". // cb receives {phase:'checking'|'available'|'downloading'|'ready'|'current'|'error', percent?, version?}. onUpdateEvent: (cb) => { try { ipcRenderer.on('update-event', (_e, data) => { try { cb(data); } catch (_) {} }); } catch (_) {} }, restartToUpdate: () => ipcRenderer.invoke('restart-to-update'), }));