feat: close-to-tray, meeting lobby, speaker select, link expiry, mobile RC touch (0.1.14/batch72)
General #1/#2 (closed-app notifications): the desktop app now CLOSES TO TRAY instead of quitting, keeping its chat WebSocket alive so calls/messages still notify. Tray icon + menu (Open / Quit), single-instance lock, first-close hint. Guest #4 (lobby/admit): meetings can require the host to admit guests joining by link. Setting on the schedule form ("Guests must be admitted by the host", default on) + ad-hoc default. Guests wait on a "waiting to be let in" screen; the host gets an Admit/Deny prompt; auto-cleanup on leave. Logged-in members always join directly. Guest #5 (speaker): headphones/speaker output picker in the meeting (setSinkId), remembered and applied to every tile. Guest #3 (link expiry): guest link/token dies ~2h after a scheduled meeting's end (HTTP 410) with a clear message; live-room links expire when the room empties. RC #4 (mobile): touch→mouse mapping so a phone/tablet viewer can control (tap=click, drag=move). Uses the same letterbox-correct coordinate mapping. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+51
-3
@@ -8,7 +8,7 @@
|
||||
// - external links open in the user's browser, not inside the app
|
||||
//
|
||||
// Server origin is configurable so the same build works against prod or a dev server.
|
||||
const { app, BrowserWindow, session, desktopCapturer, shell, Menu, MenuItem, ipcMain, nativeImage, Notification } = require('electron');
|
||||
const { app, BrowserWindow, session, desktopCapturer, shell, Menu, MenuItem, Tray, ipcMain, nativeImage, Notification } = require('electron');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
@@ -190,6 +190,30 @@ const SERVER_URL = (process.env.SERVER_URL || (app.isPackaged ? 'https://remote.
|
||||
|
||||
let win;
|
||||
let splash;
|
||||
let tray = null;
|
||||
let isQuitting = false; // true only during a real Quit (tray menu / before-quit) — otherwise close = hide to tray
|
||||
|
||||
// Close-to-tray: closing the window HIDES it instead of quitting, so the app keeps running in the
|
||||
// background with its chat WebSocket alive. That's what lets call/message notifications still fire when
|
||||
// the window is "closed" (General #1/#2) — a fully-quit Electron app gets no push. The tray icon + menu
|
||||
// bring it back or quit for real.
|
||||
function createTray() {
|
||||
if (tray) return;
|
||||
try {
|
||||
let img = nativeImage.createFromPath(path.join(__dirname, 'tray.ico'));
|
||||
if (img.isEmpty()) img = nativeImage.createFromPath(path.join(process.resourcesPath || __dirname, 'tray.ico'));
|
||||
tray = new Tray(img.isEmpty() ? nativeImage.createEmpty() : img);
|
||||
tray.setToolTip('Biz Connect');
|
||||
const showApp = () => { if (!win) return createWindow(); if (win.isMinimized()) win.restore(); win.show(); win.focus(); };
|
||||
tray.setContextMenu(Menu.buildFromTemplate([
|
||||
{ label: 'Open Biz Connect', click: showApp },
|
||||
{ type: 'separator' },
|
||||
{ label: 'Quit', click: () => { isQuitting = true; app.quit(); } },
|
||||
]));
|
||||
tray.on('click', showApp); // single-click (Windows)
|
||||
tray.on('double-click', showApp);
|
||||
} catch (_) { tray = null; }
|
||||
}
|
||||
|
||||
// A tiny brand-blue splash (splash.html) shown while the web UI loads, so launch feels instant
|
||||
// and on-brand instead of a blank window. Closed as soon as the main window is ready to show.
|
||||
@@ -233,6 +257,18 @@ function createWindow() {
|
||||
win.once('ready-to-show', reveal);
|
||||
setTimeout(reveal, 12000);
|
||||
|
||||
// Close = hide to tray (keep running for notifications). First time, tell the user where it went.
|
||||
let toldTray = false;
|
||||
win.on('close', (e) => {
|
||||
if (isQuitting) return; // real quit → let it close
|
||||
e.preventDefault();
|
||||
win.hide();
|
||||
if (!toldTray && Notification.isSupported()) {
|
||||
toldTray = true;
|
||||
try { const n = new Notification({ title: 'Biz Connect is still running', body: 'It stays in the system tray so you keep getting calls & messages. Quit from the tray icon.' }); n.show(); } catch (_) {}
|
||||
}
|
||||
});
|
||||
|
||||
// Open the landing page (same entry as the website): the "before login" screen with the
|
||||
// no-login "Share my screen" option + sign-in. It redirects logged-in users straight to /home.
|
||||
win.loadURL(SERVER_URL + '/');
|
||||
@@ -376,12 +412,22 @@ function configureSession() {
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// Single-instance: a tray app must not spawn a second copy. If another launch happens, focus the
|
||||
// existing window (restoring it from the tray) instead.
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.quit();
|
||||
} else {
|
||||
app.on('second-instance', () => { if (win) { if (win.isMinimized()) win.restore(); win.show(); win.focus(); } });
|
||||
}
|
||||
app.on('before-quit', () => { isQuitting = true; });
|
||||
|
||||
app.whenReady().then(() => {
|
||||
configureSession();
|
||||
createSplash();
|
||||
createWindow();
|
||||
createTray(); // keep the app reachable while its window is hidden to tray
|
||||
Menu.setApplicationMenu(null); // hide the default menu bar; the web UI is the chrome
|
||||
app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); });
|
||||
app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); else if (win) { win.show(); win.focus(); } });
|
||||
// Check for shell updates on launch, then every 6 hours. Only in packaged builds.
|
||||
if (app.isPackaged && autoUpdater) {
|
||||
// #3: surface update progress to the web UI so the user can SEE an update is downloading /
|
||||
@@ -405,4 +451,6 @@ app.whenReady().then(() => {
|
||||
}
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); });
|
||||
// With close-to-tray the window is HIDDEN, not destroyed, so this normally won't fire while the app is
|
||||
// meant to keep running. Only quit here if we're actually quitting (belt-and-braces).
|
||||
app.on('window-all-closed', () => { if (isQuitting && process.platform !== 'darwin') app.quit(); });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "biz-connect-desktop",
|
||||
"version": "0.1.13",
|
||||
"version": "0.1.14",
|
||||
"description": "Biz Connect technician desktop client — loads the Connect web UI with native screen capture",
|
||||
"author": {
|
||||
"name": "BizGaze",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Reference in New Issue
Block a user