0
0

feat(clients): scaffold mobile (Capacitor) + desktop (Electron) shells

Plan + decisions in CLIENTS.md (parallel mobile+desktop; desktop = technician
client + existing remote-control agent host; mobile = Capacitor wrap).

- desktop/: Electron technician client — loads the live Connect UI, native
  screen capture via setDisplayMediaRequestHandler, persisted session, external
  links to browser; electron-builder config for Win/Mac/Linux installers.
- mobile/: Capacitor project — server.url loads Connect UI, push/camera/status-bar
  plugins declared, www splash fallback; iOS/Android added via `cap add`.
- Reuses the existing /api/v1 + Bearer auth backend; no web-code changes.
- .gitignore: ignore generated mobile/android, mobile/ios platform dirs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
2026-06-30 17:49:41 +05:30
Ursprung f517c153c1
Commit 593a4677b6
10 geänderte Dateien mit 332 neuen und 0 gelöschten Zeilen
+4
Datei anzeigen
@@ -29,6 +29,10 @@ dist/
build/
out/
# Native client generated/build artifacts (Capacitor adds platform dirs; Electron builds to dist)
mobile/android/
mobile/ios/
# Runtime media (created at startup by config.js)
server/recordings/
server/transcripts/
+107
Datei anzeigen
@@ -0,0 +1,107 @@
# Biz Connect — Mobile & Desktop clients
Native clients for Biz Connect. **The web app is the single source of truth for the UI**;
each native client is a thin shell that loads that UI and adds the capabilities a browser
can't provide (background push, native screen capture, OS input injection, store presence).
Decisions (set by the user 2026-06-30):
- **Build mobile and desktop in parallel.**
- **Desktop = both pieces:** the remote-control **host** (existing `agent/`) *and* a
**technician desktop client** (`desktop/` — Connect in a window).
- **Mobile = Capacitor wrap** of the existing web UI (one codebase), not a native rewrite.
Backend is already client-ready (see [ARCHITECTURE.md](ARCHITECTURE.md) Phase 2): `/api/v1`,
`Authorization: Bearer` + refresh tokens, API keys, per-tenant webhooks. The one remaining
backend gap is **APNs/FCM push** for native mobile (needs Apple/Google credentials).
---
## Components
| Dir | Client | Tech | What it adds over the browser |
|-----|--------|------|-------------------------------|
| `mobile/` | iOS + Android app | **Capacitor** loading the Connect UI | Native push (FCM/APNs), camera/mic perms, store distribution, screen capture (ReplayKit / MediaProjection) |
| `desktop/` | Technician client | **Electron** loading the Connect UI | Native full-screen capture for screen-share; windowed app; later: tray, auto-update |
| `agent/` | Remote-control **host** (customer) | **Electron + nut-js** *(exists, v0.2.0)* | Screen capture + **OS input injection** so a technician can control the machine |
All three authenticate through the same `/api/v1` access layer (Bearer token for mobile/desktop,
the existing consent/enroll flow for the agent).
---
## Why "wrap the web UI" (not rewrite)
The Connect UI is already an installable PWA built from server-rendered single-file HTML.
Pointing a native webview at the server origin means **every relative `/api` and `/ws` URL
keeps working unchanged** — zero web-code changes — while native plugins augment it through
the JS bridge. One codebase, three shells.
Trade-off: a server-URL shell needs network at launch (fine for a remote-support tool) and
some stores scrutinise "just a website". We mitigate by shipping real native capabilities
(push, screen capture, deep links), and can later switch to **bundled** web assets + an
absolute API base if offline-launch or store policy requires it.
---
## Phased plan
### Phase A — Shells that load the live app ← start here
- [ ] `desktop/` Electron client: `loadURL(server)`, `setDisplayMediaRequestHandler` so
screen-share works natively, external links → browser, persisted session.
- [ ] `mobile/` Capacitor project: `server.url` → Connect, app icons/splash, status bar.
- [ ] Inject `window.__NATIVE__ = 'desktop' | 'ios' | 'android'` so the web UI can adapt
(e.g. hide the PWA "install" prompt, enable native push instead of Web Push).
### Phase B — Native capabilities
- [ ] **Push:** server device-token registration (`/api/v1/devices`) + a pluggable
`push` sender (FCM/APNs), config-gated. Mobile uses the Capacitor push plugin;
desktop keeps Web Push / in-app. *Needs Apple/Google creds to test end-to-end.*
- [ ] **Mobile screen capture** for "Share Screen" from a phone (ReplayKit / MediaProjection plugin).
- [ ] **Deep links / universal links** so a session/meeting link opens the app.
### Phase C — Packaging & distribution *(needs external accounts)*
- [ ] Desktop installers via **electron-builder** (Win NSIS + Mac dmg); **code-signing**
(Win EV cert, Apple Developer ID + notarization).
- [ ] Mobile store builds: **Apple Developer** ($99/yr) + **Google Play** ($25 once);
signing keys; store listings & privacy disclosures.
- [ ] Agent host installer (signed) for customers.
- [ ] Auto-update channels.
---
## Build & run
### Desktop (technician client)
```bash
cd desktop
npm install
SERVER_URL=https://remote.bizgaze.com npm start # or http://localhost:8090 in dev
npm run dist # build installers (needs electron-builder + certs)
```
### Mobile (Capacitor)
```bash
cd mobile
npm install
npx cap add android # needs Android Studio + SDK
npx cap add ios # needs macOS + Xcode
npx cap sync
npx cap open android # build/run from Android Studio
npx cap open ios # build/run from Xcode
```
Set the server origin in `capacitor.config.json` (`server.url`).
### Agent host (existing)
```bash
cd agent
npm install
SERVER_URL=https://remote.bizgaze.com AGENT_ENROLL_TOKEN=<token> npm start
```
---
## What's gated on you (external, can't be done from code alone)
- **Apple Developer** + **Google Play** accounts (mobile store builds & push).
- **Code-signing certificates** (Windows EV, Apple Developer ID) for trusted installers.
- **FCM/APNs credentials** for native push.
Everything else — the shells, the device/push backend, native plugin wiring — is built here.
+26
Datei anzeigen
@@ -0,0 +1,26 @@
# Biz Connect — Desktop client
Electron shell that loads the live Connect web UI and adds native screen capture. See the
overall plan in [../CLIENTS.md](../CLIENTS.md).
## Run (dev)
```bash
npm install
SERVER_URL=http://localhost:8090 npm start # default is https://remote.bizgaze.com
```
## Build installers
```bash
npm run dist # electron-builder → Win NSIS / Mac dmg / Linux AppImage
```
Signed, trusted installers need certificates:
- **Windows:** an EV (or OV) code-signing certificate.
- **macOS:** Apple Developer ID cert + notarization (`CSC_LINK`, `APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`).
## Notes
- The window loads `${SERVER_URL}/home`; relative `/api` and `/ws` URLs work because the
origin is the server itself — no web-code changes.
- `setDisplayMediaRequestHandler` in `main.js` is what makes "Share Screen" work in Electron;
it currently defaults to the primary display. Swap in a source-picker for production.
- The session is persisted (`persist:bizconnect`) so login survives restarts.
- `window.__NATIVE__ === 'desktop'` is exposed for the web UI to feature-detect.
+62
Datei anzeigen
@@ -0,0 +1,62 @@
// Biz Connect — technician desktop client (Electron main process).
//
// This is a thin shell: it loads the live Connect web UI from the server origin, so every
// relative /api and /ws URL in the web app keeps working unchanged. What it adds over a
// browser tab:
// - native full-screen capture for "Share Screen" (setDisplayMediaRequestHandler)
// - a real desktop window (no browser chrome), persisted login session
// - 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 path = require('path');
const SERVER_URL = (process.env.SERVER_URL || 'https://remote.bizgaze.com').replace(/\/+$/, '');
let win;
function createWindow() {
win = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 880,
minHeight: 600,
title: 'Biz Connect',
backgroundColor: '#0f1830',
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
// Persist cookies/localStorage so the technician stays logged in between launches.
partition: 'persist:bizconnect',
},
});
win.loadURL(SERVER_URL + '/home');
// Open target=_blank / external links in the system browser instead of a new Electron window.
win.webContents.setWindowOpenHandler(({ url }) => {
if (!url.startsWith(SERVER_URL)) { shell.openExternal(url); return { action: 'deny' }; }
return { action: 'allow' };
});
}
// 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) => {
desktopCapturer.getSources({ types: ['screen', 'window'] }).then((sources) => {
callback(sources.length ? { video: sources[0], audio: 'loopback' } : {});
}).catch(() => callback({}));
});
}
app.whenReady().then(() => {
registerDisplayMediaHandler();
createWindow();
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('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); });
+22
Datei anzeigen
@@ -0,0 +1,22 @@
{
"name": "biz-connect-desktop",
"version": "0.1.0",
"description": "Biz Connect technician desktop client — loads the Connect web UI with native screen capture",
"main": "main.js",
"scripts": {
"start": "electron .",
"dist": "electron-builder"
},
"devDependencies": {
"electron": "^31.0.0",
"electron-builder": "^24.13.3"
},
"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" }
}
}
+10
Datei anzeigen
@@ -0,0 +1,10 @@
// 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');
contextBridge.exposeInMainWorld('__NATIVE__', 'desktop');
contextBridge.exposeInMainWorld('bizConnectNative', Object.freeze({
platform: 'desktop',
version: process.env.npm_package_version || '0.1.0',
}));
+39
Datei anzeigen
@@ -0,0 +1,39 @@
# Biz Connect — Mobile app (Capacitor)
A Capacitor shell that loads the live Connect web UI (`server.url` in
`capacitor.config.json`) and adds native push, camera/mic, and store distribution. See the
overall plan in [../CLIENTS.md](../CLIENTS.md).
## Prerequisites
- Node + `npm install` here.
- **Android:** Android Studio + SDK.
- **iOS:** macOS + Xcode (+ an Apple Developer account to run on device / ship).
## Setup
```bash
npm install
npx cap add android
npx cap add ios # macOS only
npx cap sync
```
## Run / build
```bash
npx cap open android # build & run from Android Studio
npx cap open ios # build & run from Xcode
```
## Server origin
The app loads `server.url` from `capacitor.config.json` (default
`https://remote.bizgaze.com`). For a local device test against a dev server, set it to your
machine's LAN URL (and allow cleartext for plain http).
## Native push (next step)
Native push uses the Capacitor Push Notifications plugin (FCM on Android, APNs on iOS) and a
server endpoint to register device tokens — tracked in [../CLIENTS.md](../CLIENTS.md) Phase B.
This is separate from the existing Web Push (VAPID) the PWA already uses. Needs Google/Apple
credentials to test end-to-end.
## Shipping (gated on accounts)
- **Google Play:** one-time $25; upload an AAB; signing key.
- **App Store:** Apple Developer $99/yr; archive via Xcode; App Store Connect listing.
+15
Datei anzeigen
@@ -0,0 +1,15 @@
{
"appId": "com.bizgaze.connect",
"appName": "Biz Connect",
"webDir": "www",
"server": {
"url": "https://remote.bizgaze.com",
"cleartext": false,
"androidScheme": "https"
},
"plugins": {
"PushNotifications": {
"presentationOptions": ["badge", "sound", "alert"]
}
}
}
+22
Datei anzeigen
@@ -0,0 +1,22 @@
{
"name": "biz-connect-mobile",
"version": "0.1.0",
"description": "Biz Connect mobile app — Capacitor shell loading the Connect web UI",
"scripts": {
"sync": "cap sync",
"android": "cap open android",
"ios": "cap open ios"
},
"dependencies": {
"@capacitor/android": "^6.1.0",
"@capacitor/app": "^6.0.0",
"@capacitor/camera": "^6.0.0",
"@capacitor/core": "^6.1.0",
"@capacitor/ios": "^6.1.0",
"@capacitor/push-notifications": "^6.0.0",
"@capacitor/status-bar": "^6.0.0"
},
"devDependencies": {
"@capacitor/cli": "^6.1.0"
}
}
+25
Datei anzeigen
@@ -0,0 +1,25 @@
<!doctype html>
<!-- Capacitor requires a webDir with an index. At runtime the app loads the live Connect UI
via server.url in capacitor.config.json, so this is only a launch splash / offline
fallback. To ship fully-bundled (offline-launch) later, copy ../server/public here and
drop server.url. -->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Biz Connect</title>
<style>
html,body{height:100%;margin:0;background:#0f1830;color:#fff;font-family:system-ui,Segoe UI,Roboto,sans-serif;}
.wrap{height:100%;display:grid;place-items:center;text-align:center;padding:2rem;}
h1{font-size:1.4rem;margin:.4rem 0;} p{opacity:.7;font-size:.9rem;}
</style>
</head>
<body>
<div class="wrap">
<div>
<h1>Biz <span style="color:#f5b301">Connect</span></h1>
<p>Connecting…</p>
</div>
</div>
</body>
</html>