diff --git a/js/src/download.ts b/js/src/download.ts index 578a811..8d6a89d 100644 --- a/js/src/download.ts +++ b/js/src/download.ts @@ -93,7 +93,7 @@ export async function ensureBinary( `CLOAKBROWSER_BINARY_PATH set to '${localOverride}' but file does not exist` ); } - console.log(`[cloakbrowser] Using local binary override: ${localOverride}`); + console.error(`[cloakbrowser] Using local binary override: ${localOverride}`); return localOverride; } @@ -159,7 +159,7 @@ export async function ensureBinary( return binaryPath; } - console.log( + console.error( `[cloakbrowser] Stealth Chromium ${requestedVersion} not found. Downloading for ${getPlatformTag()}...` ); await downloadAndExtract(requestedVersion); @@ -196,7 +196,7 @@ export async function ensureBinary( } // Download platform's hardcoded version - console.log( + console.error( `[cloakbrowser] Stealth Chromium ${platformVersion} not found. Downloading for ${getPlatformTag()}...` ); await downloadAndExtract(); @@ -219,7 +219,7 @@ export function clearCache(): void { const cacheDir = getCacheDir(); if (fs.existsSync(cacheDir)) { fs.rmSync(cacheDir, { recursive: true, force: true }); - console.log(`[cloakbrowser] Cache cleared: ${cacheDir}`); + console.error(`[cloakbrowser] Cache cleared: ${cacheDir}`); } } @@ -270,7 +270,7 @@ export async function checkForUpdate(): Promise { return latest; } - console.log(`[cloakbrowser] Downloading Chromium ${latest}...`); + console.error(`[cloakbrowser] Downloading Chromium ${latest}...`); await downloadAndExtract(latest); writeVersionMarker(latest); return latest; @@ -296,7 +296,7 @@ export async function checkForProUpdate( } if (!proBinaryReady(latest)) { - console.log(`[cloakbrowser] Downloading Pro Chromium ${latest}...`); + console.error(`[cloakbrowser] Downloading Pro Chromium ${latest}...`); await downloadProBinary(latest, licenseKey); if (!fs.existsSync(getBinaryPath(latest, true))) { throw new Error( @@ -611,7 +611,7 @@ export function verifySignature(manifestBytes: Uint8Array, sigB64Bytes: Uint8Arr } try { if (cryptoVerify(null, manifestBytes, keyObject, signature)) { - console.log("[cloakbrowser] SHA256SUMS signature verified: Ed25519 OK"); + console.error("[cloakbrowser] SHA256SUMS signature verified: Ed25519 OK"); return; } } catch { @@ -684,11 +684,11 @@ async function verifyChecksum(filePath: string, expectedHash: string): Promise): Promise { - console.log(`[cloakbrowser] Downloading from ${url}`); + console.error(`[cloakbrowser] Downloading from ${url}`); const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT_MS); @@ -731,7 +731,7 @@ async function downloadFile(url: string, dest: string, headers?: Record { - console.log(`[cloakbrowser] Extracting to ${destDir}`); + console.error(`[cloakbrowser] Extracting to ${destDir}`); const bp = binaryPath || getBinaryPath(); // Extract into a private sibling dir, then rename it into place. Concurrent @@ -1100,7 +1100,7 @@ async function extractArchive( } if (fs.existsSync(bp)) { - console.log(`[cloakbrowser] Binary ready: ${bp}`); + console.error(`[cloakbrowser] Binary ready: ${bp}`); } } @@ -1299,12 +1299,12 @@ async function checkAndDownloadUpdate(): Promise { return; } - console.log( + console.error( `[cloakbrowser] Newer Chromium available: ${latest} (current: ${platformVersion}). Downloading in background...` ); await downloadAndExtract(latest); writeVersionMarker(latest); - console.log( + console.error( `[cloakbrowser] Background update complete: Chromium ${latest} ready. Will use on next launch.` ); } catch (err) { diff --git a/js/src/geoip.ts b/js/src/geoip.ts index 78dbd3e..0a13d0f 100644 --- a/js/src/geoip.ts +++ b/js/src/geoip.ts @@ -369,7 +369,7 @@ async function ensureGeoipDb(): Promise { async function downloadGeoipDb(dest: string): Promise { const dir = path.dirname(dest); fs.mkdirSync(dir, { recursive: true }); - console.log("[cloakbrowser] Downloading GeoIP database (~70 MB)…"); + console.error("[cloakbrowser] Downloading GeoIP database (~70 MB)…"); const tmpPath = `${dest}.tmp.${Date.now()}`; try { @@ -395,7 +395,7 @@ async function downloadGeoipDb(dest: string): Promise { }); fs.renameSync(tmpPath, dest); - console.log(`[cloakbrowser] GeoIP database ready: ${dest}`); + console.error(`[cloakbrowser] GeoIP database ready: ${dest}`); } catch (err) { if (fs.existsSync(tmpPath)) fs.unlinkSync(tmpPath); throw err; diff --git a/js/tests/geoip.test.ts b/js/tests/geoip.test.ts index 780a490..c5b6b45 100644 --- a/js/tests/geoip.test.ts +++ b/js/tests/geoip.test.ts @@ -127,6 +127,32 @@ describe("maybeResolveGeoip", () => { expect(fetchSpy.mock.calls[0][1]).toEqual({ redirect: "follow" }); }); + it("GeoIP database download writes nothing to stdout", async () => { + const cacheDir = fs.mkdtempSync(path.join(os.tmpdir(), "cloak-geoip-stdout-")); + tempDirs.push(cacheDir); + process.env.CLOAKBROWSER_CACHE_DIR = cacheDir; + process.env.CLOAKBROWSER_GEOIP_TIMEOUT_SECONDS = "0.001"; + + vi.spyOn(globalThis, "fetch").mockResolvedValue({ + ok: true, + body: new ReadableStream({ + start(controller) { + controller.enqueue(new Uint8Array([1, 2, 3])); + controller.close(); + }, + }), + } as Response); + vi.spyOn(console, "error").mockImplementation(() => {}); + const consoleLog = vi.spyOn(console, "log").mockImplementation(() => {}); + const stdoutWrite = vi.spyOn(process.stdout, "write"); + + await expect(resolveProxyGeo("http://203.0.113.10:8080")).rejects.toThrow(); + + expect(fs.existsSync(path.join(cacheDir, "geoip", "GeoLite2-City.mmdb"))).toBe(true); + expect(consoleLog).not.toHaveBeenCalled(); + expect(stdoutWrite).not.toHaveBeenCalled(); + }); + it("throws when the GeoIP database is unavailable", async () => { const cacheDir = fs.mkdtempSync(path.join(os.tmpdir(), "cloak-geoip-db-failure-")); tempDirs.push(cacheDir); diff --git a/js/tests/update.test.ts b/js/tests/update.test.ts index 8723031..4e8fd58 100644 --- a/js/tests/update.test.ts +++ b/js/tests/update.test.ts @@ -770,7 +770,7 @@ describe("concurrent first-run download", () => { "replaces a partial install left by an interrupted extraction", async () => { vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response(archiveBytes)); - vi.spyOn(console, "log").mockImplementation(() => {}); + vi.spyOn(console, "error").mockImplementation(() => {}); fs.mkdirSync(path.join(getBinaryDir(), "lib"), { recursive: true }); fs.writeFileSync(path.join(getBinaryDir(), "lib", "part-0.bin"), "truncated"); @@ -779,6 +779,22 @@ describe("concurrent first-run download", () => { }, ); + // MCP stdio servers use stdout as their JSON-RPC channel; status lines there break them. + it.skipIf(process.platform === "win32")( + "first-run download writes nothing to stdout", + async () => { + vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response(archiveBytes)); + vi.spyOn(console, "error").mockImplementation(() => {}); + const consoleLog = vi.spyOn(console, "log").mockImplementation(() => {}); + const stdoutWrite = vi.spyOn(process.stdout, "write"); + + expect(await ensureBinary()).toBe(getBinaryPath()); + expect(inspectInstall()).toBe("complete"); + expect(consoleLog).not.toHaveBeenCalled(); + expect(stdoutWrite).not.toHaveBeenCalled(); + }, + ); + it.skipIf(process.platform === "win32")( "concurrent processes replace a partial install left by an interrupted extraction", async () => {