fix: cross-verify update API claims against npm registry (#1082)

* fix: cross-verify update API claims against npm registry

* Update packages/web/server/lib/package-manager.js

Signed-off-by: Islam Nofl <islamnofl.official@gmail.com>

* fix: show live server version in AboutDialog instead of stale build-time constant

* fix: add comment to empty catch block to satisfy lint no-empty rule

* fix: preserve live about dialog version in electron

* fix: scope update checks by runtime

---------

Signed-off-by: Islam Nofl <islamnofl.official@gmail.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
Islam Nofl
2026-05-01 12:23:58 +03:00
committed by GitHub
co-authored by Bohdan Triapitsyn
parent c924e85a29
commit a23f5e7545
11 changed files with 340 additions and 23 deletions
@@ -24,6 +24,7 @@ import { UsageProgressBar } from '@/components/sections/usage/UsageProgressBar';
import { PaceIndicator } from '@/components/sections/usage/PaceIndicator';
import { formatPercent, formatWindowLabel, QUOTA_PROVIDERS, calculatePace, calculateExpectedUsagePercent } from '@/lib/quota';
import { useQuotaAutoRefresh, useQuotaStore } from '@/stores/useQuotaStore';
import { useUpdateStore } from '@/stores/useUpdateStore';
import { updateDesktopSettings } from '@/lib/persistence';
import { lazyWithChunkRecovery } from '@/lib/chunkLoadRecovery';
import type { UsageWindow } from '@/types';
@@ -58,6 +59,41 @@ type VSCodeView = 'sessions' | 'chat' | 'settings';
export const VSCodeLayout: React.FC = () => {
const { t } = useI18n();
const runtimeApis = useRuntimeAPIs();
const checkForUpdates = useUpdateStore((state) => state.checkForUpdates);
React.useEffect(() => {
const initialDelayMs = 3000;
const defaultIntervalMs = 60 * 60 * 1000;
const minIntervalMs = 5 * 60 * 1000;
const maxIntervalMs = 24 * 60 * 60 * 1000;
let disposed = false;
let timer: number | null = null;
const clampIntervalMs = (seconds: number): number => {
const ms = Math.round(seconds * 1000);
return Math.max(minIntervalMs, Math.min(maxIntervalMs, ms));
};
const scheduleNext = (delayMs: number) => {
if (disposed) return;
timer = window.setTimeout(async () => {
const suggestedSec = await checkForUpdates();
const nextDelay = typeof suggestedSec === 'number' && Number.isFinite(suggestedSec)
? clampIntervalMs(suggestedSec)
: defaultIntervalMs;
scheduleNext(nextDelay);
}, delayMs);
};
scheduleNext(initialDelayMs);
return () => {
disposed = true;
if (timer !== null) {
window.clearTimeout(timer);
}
};
}, [checkForUpdates]);
const viewMode = React.useMemo<'sidebar' | 'editor'>(() => {
const configured =
+18 -17
View File
@@ -9,8 +9,7 @@ import { debugUtils } from '@/lib/debug';
import { cn } from '@/lib/utils';
import { toast } from '@/components/ui';
import { useI18n } from '@/lib/i18n';
declare const __APP_VERSION__: string | undefined;
import { getDesktopAppVersion } from '@/lib/desktopNative';
interface AboutDialogProps {
open: boolean;
@@ -60,22 +59,24 @@ export const AboutDialog: React.FC<AboutDialogProps> = ({
React.useEffect(() => {
if (!open) return;
const isDesktop = typeof window !== 'undefined' && Boolean((window as unknown as { __TAURI__?: unknown }).__TAURI__);
if (isDesktop) {
const fetchVersion = async () => {
try {
const { getVersion } = await import('@tauri-apps/api/app');
const v = await getVersion();
setVersion(v);
} catch {
setVersion(typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : null);
const fetchVersion = async () => {
try {
const response = await fetch('/api/system/info');
if (response.ok) {
const data = await response.json();
if (typeof data.openchamberVersion === 'string' && data.openchamberVersion.trim()) {
setVersion(data.openchamberVersion);
return;
}
}
};
fetchVersion();
} else {
setVersion(typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : null);
}
} catch {
// Fall back to the native shell version when the web server is unavailable.
}
setVersion(await getDesktopAppVersion());
};
void fetchVersion();
}, [open]);
React.useEffect(() => {