diff --git a/desktop/main.js b/desktop/main.js index 49b572c..caaafbc 100644 --- a/desktop/main.js +++ b/desktop/main.js @@ -35,6 +35,17 @@ ipcMain.on('get-install-info', (e) => { // on the next restart. No-op in dev (unpackaged). let autoUpdater = null; try { ({ autoUpdater } = require('electron-updater')); } catch (_) { /* not installed in dev */ } +// #12: manual "Check for updates" from Settings. Returns the current status; the background updater +// (configured in app.whenReady) downloads and prompts to restart when a build is ready. +ipcMain.handle('check-updates', async () => { + const current = app.getVersion(); + if (!app.isPackaged || !autoUpdater) return { status: 'dev', current }; + try { + const r = await autoUpdater.checkForUpdates(); + const v = r && r.updateInfo && r.updateInfo.version; + return (v && v !== current) ? { status: 'available', version: v, current } : { status: 'current', current }; + } catch (e) { return { status: 'error', message: String((e && e.message) || e), current }; } +}); // Chat toast: sender/group avatar + message; clicking it raises the app and opens that chat. // Uses Electron's OWN Notification (native, no SnoreToast) whose 'click' event fires reliably. const APP_ID = 'com.bizgaze.connect.desktop'; @@ -205,6 +216,21 @@ app.whenReady().then(() => { app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); // Check for shell updates on launch, then every 6 hours. Only in packaged builds. if (app.isPackaged && autoUpdater) { + // When an update finishes downloading (auto or via the Settings "Check for updates"), offer a + // clear restart prompt instead of only the silent on-next-launch install. + let promptedForUpdate = false; + autoUpdater.on('update-downloaded', async (info) => { + if (promptedForUpdate) return; promptedForUpdate = true; + try { + const { dialog } = require('electron'); + const res = await dialog.showMessageBox(win || undefined, { + type: 'info', buttons: ['Restart now', 'Later'], defaultId: 0, cancelId: 1, + title: 'Update ready', message: 'Biz Connect ' + ((info && info.version) || '') + ' is ready.', + detail: 'Restart the app to finish updating.', + }); + if (res.response === 0) autoUpdater.quitAndInstall(); + } catch (_) { /* fall back to install-on-next-launch */ } + }); const check = () => autoUpdater.checkForUpdatesAndNotify().catch(() => {}); check(); setInterval(check, 6 * 60 * 60 * 1000); diff --git a/desktop/package.json b/desktop/package.json index a44c8b6..6b12b58 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,6 +1,6 @@ { "name": "biz-connect-desktop", - "version": "0.1.4", + "version": "0.1.5", "description": "Biz Connect technician desktop client — loads the Connect web UI with native screen capture", "author": { "name": "BizGaze", diff --git a/desktop/preload.js b/desktop/preload.js index e6f250a..48faa14 100644 --- a/desktop/preload.js +++ b/desktop/preload.js @@ -22,4 +22,7 @@ contextBridge.exposeInMainWorld('bizConnectNative', Object.freeze({ // 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), + // 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'), })); diff --git a/server/public/home.html b/server/public/home.html index 63d3676..f8cdc50 100644 --- a/server/public/home.html +++ b/server/public/home.html @@ -809,7 +809,7 @@ - @@ -986,10 +986,25 @@ function openSettings(){ +sw('setGroup','Group message notifications', notifOn('group')) +sw('setDm','Direct message notifications', notifOn('dm')) +'' + +(window.bizConnectNative?(''):'') +'
These preferences are saved on this device.
'; document.body.appendChild(ov); ov.onclick=e=>{ if(e.target===ov) ov.remove(); }; ov.querySelector('#setClose').onclick=()=>ov.remove(); + const updBtn=ov.querySelector('#setUpd'); // #12: desktop version + manual update check + if(updBtn) updBtn.onclick=async()=>{ + const ver=ov.querySelector('#setVer'); updBtn.disabled=true; updBtn.textContent='Checking…'; + let done=false; + try{ + const r=(window.bizConnectNative&&window.bizConnectNative.checkForUpdates)?await window.bizConnectNative.checkForUpdates():{status:'unsupported'}; + if(r.status==='available'){ updBtn.textContent='Update '+(r.version||'')+' found'; if(ver) ver.textContent='Downloading '+(r.version||'')+'… you’ll be asked to restart when ready.'; done=true; } + else if(r.status==='current'){ updBtn.textContent='Up to date ✓'; } + else if(r.status==='dev'){ updBtn.textContent='Dev build'; } + else if(r.status==='unsupported'){ updBtn.textContent='Update on newer app'; } + else { updBtn.textContent='Check failed'; } + }catch(_){ updBtn.textContent='Check failed'; } + setTimeout(()=>{ if(!done){ updBtn.disabled=false; updBtn.textContent='Check for updates'; } }, 3500); + }; const setPref=(k,v)=>{ try{ localStorage.setItem('notif_'+k, v?'on':'off'); }catch(_){} }; ov.querySelector('#setGroup').onchange=e=>setPref('group', e.target.checked); ov.querySelector('#setDm').onchange=e=>setPref('dm', e.target.checked);