feat: add wrapper version update checks for PyPI and npm

Check for newer wrapper versions on startup (once per process).
Python queries PyPI, JS queries npm registry. Respects
CLOAKBROWSER_AUTO_UPDATE=false and CLOAKBROWSER_DOWNLOAD_URL
(custom mirror mode skips external registry calls).

Includes unit tests for both languages covering: update detection,
env var gating, network error handling, and once-per-process guard.
This commit is contained in:
CloakHQ
2026-03-03 01:21:41 +01:00
parent 1ad2b8d3a9
commit d8960447a0
8 changed files with 245 additions and 5 deletions
+14
View File
@@ -6,6 +6,20 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
// Read wrapper version from package.json (single source of truth)
let WRAPPER_VERSION = "0.0.0";
try {
const _configDir = path.dirname(fileURLToPath(import.meta.url));
const _pkgPath = path.resolve(_configDir, "..", "package.json");
const _pkg = JSON.parse(fs.readFileSync(_pkgPath, "utf-8")) as { version: string };
WRAPPER_VERSION = _pkg.version;
} catch {
// Fallback — package.json not found (bundled or unusual layout).
// Wrapper update check will compare against 0.0.0 and always suggest updating.
}
export { WRAPPER_VERSION };
// ---------------------------------------------------------------------------
// Chromium version shipped with this release.
+37 -1
View File
@@ -17,6 +17,7 @@ import {
DOWNLOAD_BASE_URL,
GITHUB_API_URL,
GITHUB_DOWNLOAD_BASE_URL,
WRAPPER_VERSION,
checkPlatformAvailable,
getBinaryDir,
getBinaryPath,
@@ -477,6 +478,36 @@ function writeVersionMarker(version: string): void {
fs.renameSync(tmp, marker);
}
let wrapperUpdateChecked = false;
/** @internal Exported for testing only. */
export function resetWrapperUpdateChecked(): void {
wrapperUpdateChecked = false;
}
/** @internal Exported for testing only. */
export async function checkWrapperUpdate(): Promise<void> {
if (wrapperUpdateChecked) return;
wrapperUpdateChecked = true;
if (process.env.CLOAKBROWSER_AUTO_UPDATE?.toLowerCase() === "false") return;
if (process.env.CLOAKBROWSER_DOWNLOAD_URL) return;
try {
const resp = await fetch("https://registry.npmjs.org/cloakbrowser/latest", {
signal: AbortSignal.timeout(5_000),
});
if (!resp.ok) return;
const data = (await resp.json()) as { version: string };
if (data.version && versionNewer(data.version, WRAPPER_VERSION)) {
console.warn(
`[cloakbrowser] Update available: ${WRAPPER_VERSION}${data.version}. ` +
`Run: npm install cloakbrowser@latest`
);
}
} catch {
// Non-fatal — never block binary update check
}
}
async function checkAndDownloadUpdate(): Promise<void> {
try {
// Record check timestamp first (rate limiting)
@@ -514,7 +545,12 @@ async function checkAndDownloadUpdate(): Promise<void> {
}
function maybeTriggerUpdateCheck(): void {
// Wrapper update: once per process, not rate-limited
if (!wrapperUpdateChecked) {
checkWrapperUpdate().catch(() => {});
}
// Binary update: rate-limited to once per hour
if (!shouldCheckForUpdate()) return;
// Fire-and-forget — don't await
checkAndDownloadUpdate().catch(() => {});
}