fix: update check for desktop app

This commit is contained in:
Bohdan Triapitsyn
2026-07-15 14:02:12 +03:00
parent 00e002413d
commit 53d2dde87a
4 changed files with 44 additions and 5 deletions
+31 -2
View File
@@ -39,6 +39,23 @@ interface UpdateStore extends UpdateState {
type ClientRuntime = 'desktop' | 'web' | 'vscode' | 'mobile'; type ClientRuntime = 'desktop' | 'web' | 'vscode' | 'mobile';
const CLIENT_INSTALL_ID_KEY = 'openchamber.update-install-id';
function getClientInstallId(): string | undefined {
if (typeof window === 'undefined' || typeof crypto.randomUUID !== 'function') return undefined;
try {
const existing = window.localStorage.getItem(CLIENT_INSTALL_ID_KEY)?.trim();
if (existing) return existing;
const installId = crypto.randomUUID();
window.localStorage.setItem(CLIENT_INSTALL_ID_KEY, installId);
return installId;
} catch {
return undefined;
}
}
function detectDeviceClass(): 'mobile' | 'tablet' | 'desktop' | 'unknown' { function detectDeviceClass(): 'mobile' | 'tablet' | 'desktop' | 'unknown' {
if (typeof window === 'undefined') return 'unknown'; if (typeof window === 'undefined') return 'unknown';
try { try {
@@ -86,6 +103,10 @@ function mapRuntimeParams(runtime: ClientRuntime): URLSearchParams {
params.set('deviceClass', detectDeviceClass()); params.set('deviceClass', detectDeviceClass());
params.set('arch', detectArch()); params.set('arch', detectArch());
params.set('platform', detectPlatform()); params.set('platform', detectPlatform());
if (shouldReportUsage && (runtime === 'desktop' || runtime === 'mobile')) {
const installId = getClientInstallId();
if (installId) params.set('installId', installId);
}
if (runtime === 'desktop') { if (runtime === 'desktop') {
params.set('appType', 'desktop-electron'); params.set('appType', 'desktop-electron');
params.set('instanceMode', isDesktopLocalOriginActive() ? 'local' : 'remote'); params.set('instanceMode', isDesktopLocalOriginActive() ? 'local' : 'remote');
@@ -186,13 +207,21 @@ export const useUpdateStore = create<UpdateStore>()((set, get) => ({
let suggestedSec: number | null = null; let suggestedSec: number | null = null;
if (runtime === 'desktop') { if (runtime === 'desktop') {
const desktopInfo = await checkForDesktopUpdates(); const appVersion = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : undefined;
const [desktopResult, apiResult] = await Promise.allSettled([
checkForDesktopUpdates(),
checkForWebUpdates('desktop', appVersion),
]);
const desktopInfo = desktopResult.status === 'fulfilled' ? desktopResult.value : null;
suggestedSec = apiResult.status === 'fulfilled'
? (apiResult.value?.nextSuggestedCheckInSec ?? null)
: null;
set({ set({
checking: false, checking: false,
available: desktopInfo?.available ?? false, available: desktopInfo?.available ?? false,
info: desktopInfo, info: desktopInfo,
lastChecked: Date.now(), lastChecked: Date.now(),
nextCheckInSec: null, nextCheckInSec: suggestedSec,
}); });
return suggestedSec; return suggestedSec;
@@ -39,6 +39,7 @@ export const registerOpenChamberRoutes = (app, dependencies) => {
arch: parseString(req.query.arch), arch: parseString(req.query.arch),
instanceMode: parseString(req.query.instanceMode), instanceMode: parseString(req.query.instanceMode),
currentVersion: parseString(req.query.currentVersion), currentVersion: parseString(req.query.currentVersion),
installId: parseString(req.query.installId),
reportUsage: parseReportUsage(parseString(req.query.reportUsage)), reportUsage: parseReportUsage(parseString(req.query.reportUsage)),
}); });
res.json(updateInfo); res.json(updateInfo);
+4 -3
View File
@@ -125,9 +125,10 @@ async function checkForUpdatesFromApi(currentVersion, options = {}) {
const appType = normalizeAppType(options.appType); const appType = normalizeAppType(options.appType);
const hostPlatform = mapPlatform(process.platform); const hostPlatform = mapPlatform(process.platform);
const hostArch = mapArch(process.arch); const hostArch = mapArch(process.arch);
const shouldTrustClientPlatform = appType === 'vscode' || appType === 'mobile-capacitor'; const shouldTrustClientPlatform = appType === 'desktop-electron' || appType === 'vscode' || appType === 'mobile-capacitor';
const platform = shouldTrustClientPlatform ? normalizePlatform(options.platform) : hostPlatform; const platform = shouldTrustClientPlatform ? normalizePlatform(options.platform) : hostPlatform;
const arch = shouldTrustClientPlatform ? normalizeArch(options.arch) : hostArch; const arch = shouldTrustClientPlatform ? normalizeArch(options.arch) : hostArch;
const reportUsage = options.reportUsage !== false;
const payload = { const payload = {
appType, appType,
deviceClass: normalizeDeviceClass(options.deviceClass), deviceClass: normalizeDeviceClass(options.deviceClass),
@@ -135,9 +136,9 @@ async function checkForUpdatesFromApi(currentVersion, options = {}) {
arch, arch,
channel: 'stable', channel: 'stable',
currentVersion, currentVersion,
installId: getOrCreateInstallId(appType), installId: reportUsage ? (options.installId || getOrCreateInstallId(appType)) : undefined,
instanceMode: options.instanceMode || 'unknown', instanceMode: options.instanceMode || 'unknown',
reportUsage: options.reportUsage !== false, reportUsage,
}; };
const response = await fetch(UPDATE_CHECK_URL, { const response = await fetch(UPDATE_CHECK_URL, {
@@ -134,11 +134,19 @@ describe('checkForUpdates', () => {
const result = await checkForUpdates({ const result = await checkForUpdates({
appType: 'desktop-electron', appType: 'desktop-electron',
currentVersion: '1.9.10', currentVersion: '1.9.10',
installId: '4f4dfead-9688-4c4f-97d7-4607fbbfc3ab',
platform: 'windows',
arch: 'arm64',
}); });
expect(result.available).toBe(true); expect(result.available).toBe(true);
expect(result.version).toBe('1.10.0'); expect(result.version).toBe('1.10.0');
expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledTimes(1);
expect(JSON.parse(fetchMock.mock.calls[0][1].body)).toMatchObject({
installId: '4f4dfead-9688-4c4f-97d7-4607fbbfc3ab',
platform: 'windows',
arch: 'arm64',
});
}); });
it('resolves an Android APK asset when the update API returns an AAB', async () => { it('resolves an Android APK asset when the update API returns an AAB', async () => {