From 5a98ae4d34ee7096d0a52c143f10bee9b60c23de Mon Sep 17 00:00:00 2001 From: sravan Date: Wed, 1 Jul 2026 16:16:40 +0530 Subject: [PATCH] feat(desktop): media/notification permissions + notification raises the app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - desktop/main.js: grant media (camera/mic), display-capture, notifications, clipboard, fullscreen, pointerLock for the app origin (Electron denies these by default, which silently broke meetings' camera/mic). Adds setPermissionRequestHandler + setPermissionCheckHandler on the app session. - Clicking a notification now raises + focuses the window: preload exposes bizConnectNative.focusApp(), main handles 'focus-window' IPC, and the web notify() onclick calls it when running in the desktop shell. (home.html build batch16) - CLIENTS.md: Phase D — inline-reply notifications (Windows Toast RemoteInput / Android RemoteInput / iOS UNTextInputNotificationAction) queued right after packaging. Co-Authored-By: Claude Opus 4.8 --- CLIENTS.md | 10 ++++++++++ desktop/main.js | 40 ++++++++++++++++++++++++++++++++-------- desktop/preload.js | 5 ++++- server/public/home.html | 4 ++-- 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/CLIENTS.md b/CLIENTS.md index f98e99e..398f171 100644 --- a/CLIENTS.md +++ b/CLIENTS.md @@ -76,6 +76,16 @@ absolute API base if offline-launch or store policy requires it. - [ ] Agent host installer (signed) for customers. - [ ] Auto-update channels. +### Phase D — Inline-reply notifications (Teams-style) *(right AFTER packaging)* +Reply to a chat directly from the OS notification, without opening the app. Cross-platform: +- **Desktop (Windows):** native **Windows Toast** with an `` reply box + action, a + registered **AppUserModelID** + Start-menu shortcut (why it needs the installer first), and + a toast-activation handler that POSTs the reply to `/api/messages`. (Web `Notification` and + Electron's built-in notification can't do Windows text reply; needs a native toast lib.) +- **Android:** notification action with **`RemoteInput`** (direct reply) on the FCM message. +- **iOS:** **`UNTextInputNotificationAction`** on the APNs notification category. +All three hand the typed text to the same send path. Depends on Phase B push + Phase C packaging. + --- ## Build & run diff --git a/desktop/main.js b/desktop/main.js index d4d1b9c..7d4622f 100644 --- a/desktop/main.js +++ b/desktop/main.js @@ -8,9 +8,17 @@ // - 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 } = require('electron'); +const { app, BrowserWindow, session, desktopCapturer, shell, Menu, ipcMain } = require('electron'); const path = require('path'); +// Renderer asks (via preload) to raise the window — e.g. when an OS notification is clicked. +ipcMain.on('focus-window', () => { + if (!win) return; + if (win.isMinimized()) win.restore(); + win.show(); + win.focus(); +}); + const SERVER_URL = (process.env.SERVER_URL || 'https://remote.bizgaze.com').replace(/\/+$/, ''); let win; @@ -41,19 +49,35 @@ function createWindow() { }); } -// Electron requires an explicit handler for getDisplayMedia(); without it the web UI's -// "Share Screen" silently fails. Default to the primary display with loopback audio. -// A production build can swap this for a source-picker window. -function registerDisplayMediaHandler() { - session.fromPartition('persist:bizconnect').setDisplayMediaRequestHandler((request, callback) => { +// The full Connect experience needs several web capabilities that Electron denies by +// default. We grant them for our own trusted origin: +// - media → camera + mic for meetings/calls (getUserMedia) +// - display-capture → "Share my screen" (getDisplayMedia) +// - notifications → in-app alerts +// - clipboard, fullscreen, pointerLock → chat paste + meeting UX +// Without this, meetings silently have no camera/mic and notifications never fire. +const GRANTED = new Set([ + 'media', 'display-capture', 'notifications', + 'clipboard-read', 'clipboard-sanitized-write', 'fullscreen', 'pointerLock', +]); + +function configureSession() { + const ses = session.fromPartition('persist:bizconnect'); + // getDisplayMedia needs an explicit source. Default to the primary display + loopback audio. + // A production build can swap this for a source-picker window. + ses.setDisplayMediaRequestHandler((request, callback) => { desktopCapturer.getSources({ types: ['screen', 'window'] }).then((sources) => { callback(sources.length ? { video: sources[0], audio: 'loopback' } : {}); }).catch(() => callback({})); - }); + }, { useSystemPicker: false }); + // Async grant (getUserMedia, notifications, …) + ses.setPermissionRequestHandler((_wc, permission, callback) => callback(GRANTED.has(permission))); + // Sync check (some getUserMedia paths query this before requesting) + ses.setPermissionCheckHandler((_wc, permission) => GRANTED.has(permission)); } app.whenReady().then(() => { - registerDisplayMediaHandler(); + configureSession(); createWindow(); Menu.setApplicationMenu(null); // hide the default menu bar; the web UI is the chrome app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); diff --git a/desktop/preload.js b/desktop/preload.js index 1699922..a66424e 100644 --- a/desktop/preload.js +++ b/desktop/preload.js @@ -1,10 +1,13 @@ // 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 } = require('electron'); +const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('__NATIVE__', 'desktop'); contextBridge.exposeInMainWorld('bizConnectNative', Object.freeze({ platform: 'desktop', version: process.env.npm_package_version || '0.1.0', + // 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'), })); diff --git a/server/public/home.html b/server/public/home.html index 75ff167..c1628bc 100644 --- a/server/public/home.html +++ b/server/public/home.html @@ -721,7 +721,7 @@ - +
Loading…
@@ -1863,7 +1863,7 @@ function notify(title, body, kind, id){ try{ if(!('Notification' in window) || Notification.permission!=='granted') return; const n=new Notification(title, { body, icon:'/logo.png' }); - n.onclick=()=>{ try{ window.focus(); }catch(_){} const u='/home?openKind='+encodeURIComponent(kind||'')+'&openId='+encodeURIComponent(id||''); n.close(); location.assign(u); }; + n.onclick=()=>{ try{ window.focus(); }catch(_){} try{ if(window.bizConnectNative&&window.bizConnectNative.focusApp) window.bizConnectNative.focusApp(); }catch(_){} const u='/home?openKind='+encodeURIComponent(kind||'')+'&openId='+encodeURIComponent(id||''); n.close(); location.assign(u); }; setTimeout(()=>{ try{ n.close(); }catch(_){} }, 8000); }catch(_){} }