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:
@@ -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
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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(); });
|
||||
@@ -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" }
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
}));
|
||||
@@ -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.
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren