a0017f2036
Root cause of "the fix works on mobile but not on desktop/web": nothing was wrong with the code — the desktop app now CLOSES TO TRAY, so it can run for weeks on the page it loaded on day one and never re-fetch after a deploy. There was also no way to force it off a stale page (no menu bar → no reload accelerator). - Server: GET /api/build returns home.html's __BUILD marker. - Client: polls it (boot, on focus/visibility, every 10 min); when the server's build differs from the running one, shows a branded "A new version is available — Refresh" banner. Settings gains an always-available "Refresh app" with the current build shown. - hardReloadApp(): in the browser it unregisters service workers + clears CacheStorage then reloads cache-busted; in the shell it calls the native hard reload. - Desktop: hard-reload IPC (clears the session HTTP cache + reloadIgnoringCache), wired to Ctrl+R (reload), Ctrl+Shift+R / F5 (hard reload), and a "Refresh app (get latest)" tray item. Previously there was literally no way to clear the cache from the app. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
48 lines
3.6 KiB
JavaScript
48 lines
3.6 KiB
JavaScript
// 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'),
|
|
}));
|