mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-10-18 23:07:13 -05:00
fix(js): write download and GeoIP status lines to stderr, not stdout
Stdout status lines corrupted MCP stdio servers and any caller parsing stdout on first run. Matches the banner, Python, and .NET. Tests assert nothing reaches stdout.
This commit is contained in:
4 files changed
+63
-21
No files matched your search
+18
-18
@@ -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<string | null> {
|
||||
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<v
|
||||
`Please retry or report at https://github.com/CloakHQ/cloakbrowser/issues`
|
||||
);
|
||||
}
|
||||
console.log("[cloakbrowser] Checksum verified: SHA-256 OK");
|
||||
console.error("[cloakbrowser] Checksum verified: SHA-256 OK");
|
||||
}
|
||||
|
||||
async function downloadFile(url: string, dest: string, headers?: Record<string, string>): Promise<void> {
|
||||
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<string,
|
||||
lastLoggedPct = pct;
|
||||
const dlMB = Math.floor(downloaded / (1024 * 1024));
|
||||
const totalMB = Math.floor(total / (1024 * 1024));
|
||||
console.log(
|
||||
console.error(
|
||||
`[cloakbrowser] Download progress: ${pct}% (${dlMB}/${totalMB} MB)`
|
||||
);
|
||||
}
|
||||
@@ -746,7 +746,7 @@ async function downloadFile(url: string, dest: string, headers?: Record<string,
|
||||
});
|
||||
|
||||
const sizeMB = Math.floor(fs.statSync(dest).size / (1024 * 1024));
|
||||
console.log(`[cloakbrowser] Download complete: ${sizeMB} MB`);
|
||||
console.error(`[cloakbrowser] Download complete: ${sizeMB} MB`);
|
||||
} catch (err) {
|
||||
// Ensure file stream is destroyed on error to release the handle
|
||||
if (!fileStream.destroyed) {
|
||||
@@ -808,7 +808,7 @@ async function ensureProBinary(
|
||||
showWelcome(welcomeTier);
|
||||
return getBinaryPath(requestedVersion, true);
|
||||
}
|
||||
console.log(
|
||||
console.error(
|
||||
`[cloakbrowser] Downloading Pro Chromium ${requestedVersion} for ${getPlatformTag()}...`
|
||||
);
|
||||
await downloadProBinary(requestedVersion, licenseKey);
|
||||
@@ -884,7 +884,7 @@ async function ensureProBinary(
|
||||
// `version` (the server latest) needs downloading. On failure, fall back to a
|
||||
// cached Pro build if we have one — never the free binary.
|
||||
try {
|
||||
console.log(
|
||||
console.error(
|
||||
`[cloakbrowser] Downloading Pro Chromium ${version} for ${getPlatformTag()}...`
|
||||
);
|
||||
await downloadProBinary(version, licenseKey);
|
||||
@@ -893,7 +893,7 @@ async function ensureProBinary(
|
||||
// cached-Pro fallback, which is only for transient download failures.
|
||||
if (err instanceof BinaryVerificationError) throw err;
|
||||
if (proBinaryReady(effective)) {
|
||||
console.log(
|
||||
console.error(
|
||||
`[cloakbrowser] Pro update to ${version} failed; launching cached Pro binary ${effective}`
|
||||
);
|
||||
showWelcome(welcomeTier);
|
||||
@@ -1036,7 +1036,7 @@ async function extractArchive(
|
||||
destDir: string,
|
||||
binaryPath?: string
|
||||
): Promise<void> {
|
||||
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<void> {
|
||||
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) {
|
||||
|
||||
+2
-2
@@ -369,7 +369,7 @@ async function ensureGeoipDb(): Promise<string | null> {
|
||||
async function downloadGeoipDb(dest: string): Promise<void> {
|
||||
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<void> {
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
+17
-1
@@ -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 () => {
|
||||
|
||||
Reference in new issue
Block a user