feat: Windows download link + self-served update feed + install tracking

Downloads/updates:
- config: DOWNLOADS_DIR (override to a mounted volume in prod).
- static.js: serves /downloads/* (installer, latest.yml, .blockmap) with range support for
  resumable + differential auto-updates; /download/windows redirects to the current .exe
  (stable link). Landing page gets a "Download the Windows desktop app" button (hidden in-app).

Install tracking (who installed the app):
- db app_installs + repos.appInstalls (upsert by install_id, fills in the user on sign-in).
- POST /api/v1/telemetry/install (records install + user once authenticated);
  GET /api/v1/admin/installs (admin: list installs with user/version/os/last-seen).
- desktop main.js: stable per-install id in userData, exposed via preload
  (bizConnectNative.installId/version/os); home.html reportInstall() posts it after login.
- e2e: +2 checks (telemetry recorded, admin sees it). 119/119. build batch20.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 21:28:53 +05:30
parent e5c94ebf6d
commit 899770ed02
11 changed files with 143 additions and 4 deletions
+6
View File
@@ -105,6 +105,12 @@ function nextMsg(ws, type, timeout = 3000) {
const devRm = await call('/api/v1/devices/remove', { token: 'fcm-tok-123' }, cookie);
check('device token removed', devRm.status === 200);
// 3b''. App install telemetry (track who installed) + admin listing
const tel = await call('/api/v1/telemetry/install', { installId: 'inst-e2e-1', platform: 'desktop', appVersion: '0.1.1', os: 'win32' }, cookie);
check('install telemetry recorded', tel.status === 200 && tel.data.ok === true);
const insts = await get('/api/v1/admin/installs', cookie);
check('admin sees the install tied to the user', insts.status === 200 && Array.isArray(insts.data) && insts.data.some((i) => i.install_id === 'inst-e2e-1' && i.app_version === '0.1.1' && i.user_email));
// 3c. API keys (machine-to-machine integration), scoped + revocable
const mkKey = await call('/api/v1/keys', { name: 'ci', scopes: ['report:read'] }, cookie);
check('admin creates API key (bzc_ prefix)', mkKey.status === 200 && /^bzc_/.test(mkKey.data.key || ''));