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:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
c924e85a29
commit
a23f5e7545
@@ -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 =
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
downloadDesktopUpdate,
|
||||
restartToApplyUpdate,
|
||||
isDesktopLocalOriginActive,
|
||||
isElectronShell,
|
||||
isTauriShell,
|
||||
isVSCodeRuntime,
|
||||
isWebRuntime,
|
||||
@@ -46,6 +47,12 @@ function detectDeviceClass(): 'mobile' | 'tablet' | 'desktop' | 'unknown' {
|
||||
}
|
||||
|
||||
function detectArch(): 'arm64' | 'x64' | 'unknown' {
|
||||
const vscodeArch = typeof window !== 'undefined'
|
||||
? (window as { __VSCODE_CONFIG__?: { arch?: string } }).__VSCODE_CONFIG__?.arch?.toLowerCase?.()
|
||||
: undefined;
|
||||
if (vscodeArch === 'arm64' || vscodeArch === 'aarch64') return 'arm64';
|
||||
if (vscodeArch === 'x64' || vscodeArch === 'amd64' || vscodeArch === 'x86_64') return 'x64';
|
||||
|
||||
const nav = typeof navigator !== 'undefined' ? (navigator as Navigator & { userAgentData?: { architecture?: string } }).userAgentData : undefined;
|
||||
const fromUAData = nav?.architecture?.toLowerCase?.();
|
||||
if (fromUAData === 'arm' || fromUAData === 'arm64' || fromUAData === 'aarch64') return 'arm64';
|
||||
@@ -75,7 +82,7 @@ function mapRuntimeParams(runtime: ClientRuntime): URLSearchParams {
|
||||
params.set('arch', detectArch());
|
||||
params.set('platform', detectPlatform());
|
||||
if (runtime === 'desktop') {
|
||||
params.set('appType', 'desktop-tauri');
|
||||
params.set('appType', isElectronShell() ? 'desktop-electron' : 'desktop-tauri');
|
||||
params.set('instanceMode', isDesktopLocalOriginActive() ? 'local' : 'remote');
|
||||
return params;
|
||||
}
|
||||
@@ -94,7 +101,11 @@ function mapRuntimeParams(runtime: ClientRuntime): URLSearchParams {
|
||||
async function checkForWebUpdates(runtime: ClientRuntime, currentVersion?: string): Promise<UpdateInfo | null> {
|
||||
try {
|
||||
const params = mapRuntimeParams(runtime);
|
||||
const vscodeVersion = typeof window !== 'undefined'
|
||||
? (window as { __VSCODE_CONFIG__?: { extensionVersion?: string } }).__VSCODE_CONFIG__?.extensionVersion
|
||||
: undefined;
|
||||
if (currentVersion) params.set('currentVersion', currentVersion);
|
||||
else if (runtime === 'vscode' && vscodeVersion) params.set('currentVersion', vscodeVersion);
|
||||
const response = await fetch(`/api/openchamber/update-check?${params.toString()}`, {
|
||||
method: 'GET',
|
||||
headers: { Accept: 'application/json' },
|
||||
|
||||
Reference in New Issue
Block a user