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
+9 -1
View File
@@ -731,7 +731,7 @@
</head>
<body>
<script src="/icons.js?v=4"></script>
<script>window.__BUILD='2026-07-01-batch19';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);</script>
<script>window.__BUILD='2026-07-01-batch20';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);</script>
<div class="loading" id="loading">Loading…</div>
<header>
@@ -1831,6 +1831,13 @@ function urlB64ToUint8(base64){ const pad='='.repeat((4-base64.length%4)%4); con
// FCM/APNs (see server/push.js). No bundler needed — plugins live on Capacitor.Plugins.
function capPlugin(name){ const C=window.Capacitor; return (C && C.isNativePlatform && C.isNativePlatform() && C.Plugins && C.Plugins[name]) ? C.Plugins[name] : null; }
function nativePlatform(){ const C=window.Capacitor; if(C && C.getPlatform){ const p=C.getPlatform(); if(p==='ios'||p==='android') return p; } if(window.__NATIVE__==='desktop') return 'desktop'; return ''; }
// Record this install against the signed-in user (desktop shell exposes a stable installId).
// Fires post-login; the request carries the session so admins can see who installed the app.
function reportInstall(){
try{ const n=window.bizConnectNative; if(!n||!n.installId) return;
postJSON('/api/v1/telemetry/install',{ installId:n.installId, platform:n.platform||'desktop', appVersion:n.version||'', os:n.os||'' }).catch(()=>{});
}catch(_){}
}
let _nativeTok=null;
async function setupNativePush(){
const PN=capPlugin('PushNotifications'); const plat=nativePlatform();
@@ -2756,6 +2763,7 @@ async function doRegister(){
loadNotifs(); wireBell(); wireProfile();
placeHdrRight(); window.addEventListener('resize', placeHdrRight); // mobile: bell+profile live in the chat-list header (no top bar)
connectChatWs();
reportInstall(); // desktop/mobile shell: record this install against the signed-in user
setupPush(); // register the notification service worker + subscribe to Web Push (if granted)
{ const cl=document.getElementById('chatlist'); if(cl) enablePullRefresh(cl, loadSidebar); } // pull-to-refresh the chat list
setTimeout(maybeNotifPrompt, 1500); // gentle "enable notifications" prompt if still undecided (key for iOS PWA)