diff --git a/.gitignore b/.gitignore index 00df4bd..0fae3f3 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,10 @@ out/ mobile/android/ mobile/ios/ +# Keep the desktop app icon (a build RESOURCE), even though the global build/ rule ignores it +!desktop/build/ +!desktop/build/** + # Runtime media (created at startup by config.js) server/recordings/ server/transcripts/ diff --git a/desktop/PACKAGING.md b/desktop/PACKAGING.md new file mode 100644 index 0000000..97fd9ed --- /dev/null +++ b/desktop/PACKAGING.md @@ -0,0 +1,51 @@ +# Biz Connect Desktop — packaging & updates + +## How updates reach installed apps (two kinds) + +1. **Web / UI / feature / bug-fix changes → instant, no app update.** + The app loads the live UI from `https://remote.bizgaze.com`. Deploy the server + (`./deploy.sh`) and every installed desktop app has it on next open/reload. This is ~95% + of all changes. +2. **Native shell changes (`main.js` / `preload.js`) → auto-update.** + Baked into the `.exe`. Shipped via **electron-updater** against a **self-hosted feed** on + `https://remote.bizgaze.com/downloads/`. On launch (and every 6h) the app checks + `latest.yml`, downloads a newer version in the background, and installs on next restart. + +## Build an installer +```bash +cd desktop +npm install +npm run dist # electron-builder → dist/ (Win: NSIS .exe + latest.yml + .blockmap) +``` +Output in `desktop/dist/`: +- `Biz Connect Setup .exe` — the installer +- `latest.yml` — the update manifest electron-updater reads +- `*.blockmap` — enables delta downloads + +The release build points at production (`SERVER_URL` defaults to `https://remote.bizgaze.com` +in `main.js`). + +## Publish a release (self-hosted feed) +1. Bump `version` in `desktop/package.json` (semver — electron-updater compares this). +2. `npm run dist`. +3. Upload **all** of `dist/` (the `.exe`, `latest.yml`, `.blockmap`) to whatever the server + serves at `https://remote.bizgaze.com/downloads/`. + - Behind Nginx Proxy Manager: point `/downloads/` at a static folder, or add a static + route in the app. The files are large binaries — host them on disk/volume, **not** git. +4. Installed apps pick it up within 6h (or on next launch). + +> First release: users install the `.exe` manually (download link on your site). Every +> release after that updates automatically. + +## Code signing (add when the cert is ready) +Unsigned installers work but trip Windows SmartScreen ("More info → Run anyway"). To sign: +- **Azure Trusted Signing** (recommended): set `win.azureSignOptions` (or use the + `@electron/windows-sign` path) with the Trusted Signing account/endpoint. Cloud, no token. +- **EV/OV cert (.pfx or token)**: set env `CSC_LINK` (path to .pfx) + `CSC_KEY_PASSWORD`, + or configure a hardware-token signing tool. electron-builder signs automatically. +Once signing is on, auto-updates are silent (no SmartScreen). + +## App identity +`appId` = `com.bizgaze.connect.desktop`; the NSIS installer registers this as the +AppUserModelID and creates a Start-menu shortcut — which is also the prerequisite for the +**Phase D inline-reply Windows Toast notifications** (see ../CLIENTS.md). diff --git a/desktop/build/icon.ico b/desktop/build/icon.ico new file mode 100644 index 0000000..10744bd Binary files /dev/null and b/desktop/build/icon.ico differ diff --git a/desktop/main.js b/desktop/main.js index 62476b4..f4b9f61 100644 --- a/desktop/main.js +++ b/desktop/main.js @@ -10,6 +10,12 @@ // Server origin is configurable so the same build works against prod or a dev server. const { app, BrowserWindow, session, desktopCapturer, shell, Menu, ipcMain, nativeImage } = require('electron'); const path = require('path'); +// Auto-update: only NATIVE shell changes (this .exe) need this — all web/UI changes arrive +// live from the server. Checks the self-hosted feed (publish config in package.json → +// https://remote.bizgaze.com/downloads/latest.yml), downloads in the background, and installs +// on the next restart. No-op in dev (unpackaged). +let autoUpdater = null; +try { ({ autoUpdater } = require('electron-updater')); } catch (_) { /* not installed in dev */ } // Renderer asks (via preload) to raise the window — e.g. when an OS notification is clicked. ipcMain.on('focus-window', () => { @@ -93,6 +99,12 @@ app.whenReady().then(() => { createWindow(); Menu.setApplicationMenu(null); // hide the default menu bar; the web UI is the chrome 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) { + const check = () => autoUpdater.checkForUpdatesAndNotify().catch(() => {}); + check(); + setInterval(check, 6 * 60 * 60 * 1000); + } }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); }); diff --git a/desktop/package-lock.json b/desktop/package-lock.json index bd4c041..0c1f118 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "biz-connect-desktop", "version": "0.1.0", + "dependencies": { + "electron-updater": "^6.3.9" + }, "devDependencies": { "electron": "^31.0.0", "electron-builder": "^24.13.3" @@ -906,7 +909,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/assert-plus": { @@ -1515,7 +1517,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1990,6 +1991,82 @@ "node": ">= 10.0.0" } }, + "node_modules/electron-updater": { + "version": "6.8.9", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.9.tgz", + "integrity": "sha512-ZhVxM9iGONUpZGI1FxdMRgJjUFXi7AYGVa5PwKlO1tV1/4zDxQmfKpXOHVztKrd6L9rLcFjERvi1Mf2vxyTkig==", + "license": "MIT", + "dependencies": { + "builder-util-runtime": "9.7.0", + "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "lodash.escaperegexp": "^4.1.2", + "lodash.isequal": "^4.5.0", + "semver": "~7.7.3", + "tiny-typed-emitter": "^2.1.0" + } + }, + "node_modules/electron-updater/node_modules/builder-util-runtime": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz", + "integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/electron-updater/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-updater/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-updater/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-updater/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -2476,7 +2553,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/has-flag": { @@ -2768,7 +2844,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", - "dev": true, "funding": [ { "type": "github", @@ -2846,7 +2921,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", - "dev": true, "license": "MIT" }, "node_modules/lazystream": { @@ -2922,6 +2996,12 @@ "license": "MIT", "peer": true }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, "node_modules/lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", @@ -2930,6 +3010,13 @@ "license": "MIT", "peer": true }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", @@ -3116,7 +3203,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/node-addon-api": { @@ -3475,7 +3561,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", - "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=11.0.0" @@ -3826,6 +3911,12 @@ "node": ">= 10.0.0" } }, + "node_modules/tiny-typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz", + "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==", + "license": "MIT" + }, "node_modules/tmp": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", diff --git a/desktop/package.json b/desktop/package.json index be16b6f..7a5d242 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -7,6 +7,9 @@ "start": "electron .", "dist": "electron-builder" }, + "dependencies": { + "electron-updater": "^6.3.9" + }, "devDependencies": { "electron": "^31.0.0", "electron-builder": "^24.13.3" @@ -14,9 +17,20 @@ "build": { "appId": "com.bizgaze.connect.desktop", "productName": "Biz Connect", - "files": ["main.js", "preload.js", "assets/**"], - "win": { "target": "nsis" }, - "mac": { "target": "dmg", "category": "public.app-category.business" }, - "linux": { "target": "AppImage" } + "directories": { "buildResources": "build", "output": "dist" }, + "publish": [ + { "provider": "generic", "url": "https://remote.bizgaze.com/downloads/" } + ], + "win": { "target": "nsis", "icon": "build/icon.ico" }, + "nsis": { + "oneClick": false, + "perMachine": false, + "allowToChangeInstallationDirectory": true, + "createDesktopShortcut": true, + "createStartMenuShortcut": true, + "shortcutName": "Biz Connect" + }, + "mac": { "target": "dmg", "category": "public.app-category.business", "icon": "build/icon.ico" }, + "linux": { "target": "AppImage", "category": "Network" } } }