Compare commits

..
2 Commits
Author SHA1 Message Date
CloakHQ 7e9388e981 release: v0.4.2 — macOS Pro license falls back to free binary 2026-06-23 10:46:48 +02:00
CloakHQ 343a3e8e28 fix(download): fall back to free binary on macOS when Pro 404s
A Pro license on macOS currently has no binary to download. Rather than
hard-failing a paying customer, fall back to the free binary with a clear
notice. Scoped to the 404 case only: transient and checksum-verification
failures still hard-fail (no silent downgrade), and once the macOS Pro
build ships the 404 disappears and Pro is served automatically.

JS side adds DownloadHttpError to carry the HTTP status through fetch's
generic error path so the 404 can be distinguished from transient failures.
2026-06-23 10:41:04 +02:00
7 changed files with 76 additions and 22 deletions
+4
View File
@@ -6,6 +6,10 @@ Changes are tagged: **[wrapper]** for Python/JS wrapper, **[binary]** for Chromi
---
## [0.4.2] — 2026-06-23
- **[wrapper]** macOS Pro licenses now fall back to the free binary (macOS Pro build coming) instead of failing to launch. Transient and signature-verification failures still hard-fail. Python and JS.
## [0.4.1] — 2026-06-23
- **[wrapper]** Humanize: fix "Viewport size not available" crash on headed launches. Headed mode defaults to `no_viewport` (since 0.4.0), so `page.viewport_size` is `None` — human scroll now falls back to the live `window.innerWidth`/`window.innerHeight`. Covers Playwright (Python sync/async, JS) and Puppeteer.
+1 -1
View File
@@ -140,7 +140,7 @@ page.goto("https://example.com")
---
## Latest: v0.4.1 — CloakBrowser Pro (Chromium 148.0.7778.215.2)
## Latest: v0.4.2 — CloakBrowser Pro (Chromium 148.0.7778.215.2)
- **CloakBrowser Pro** — the latest binary (Chromium 148.0.7778.215.2, 59 source-level patches) is now available to Pro subscribers; v146 stays free forever. Set a `license_key` (`licenseKey` in JS) or the `CLOAKBROWSER_LICENSE_KEY` env var and the wrapper fetches the latest build automatically. See [CloakBrowser Pro](#cloakbrowser-pro)
- **58 fingerprint patches** — rendering consistency improvements across Linux and Windows, corrected GPU/display/graphics parameters to match stock Chrome 146 profiles
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.4.1"
__version__ = "0.4.2"
+17
View File
@@ -143,6 +143,23 @@ def ensure_binary(license_key: str | None = None) -> str:
# Authenticity could not be confirmed — surface verbatim.
raise
except Exception as e:
# macOS has no Pro binary yet. Rather than hard-failing a paying
# customer, fall back to the free binary with a clear notice.
# Scoped to the 404 (binary-not-found) case so that (a) transient
# and verification failures still hard-fail — no silent downgrade —
# and (b) the moment the macOS Pro build ships, the 404 disappears
# and Pro is served automatically with no wrapper change.
if (
get_platform_tag().startswith("darwin")
and isinstance(e, httpx.HTTPStatusError)
and e.response.status_code == 404
):
logger.warning(
"macOS Pro binary is not available yet — using the free "
"binary for now. Your license stays valid and you'll get "
"the Pro binary on macOS automatically once the build ships."
)
else:
# Transient failure with no cached Pro binary to use — surface a
# clear error rather than silently downloading the free binary.
raise RuntimeError(
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "cloakbrowser",
"version": "0.4.1",
"version": "0.4.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cloakbrowser",
"version": "0.4.1",
"version": "0.4.2",
"license": "MIT",
"dependencies": {
"tar": "^7.0.0"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "cloakbrowser",
"version": "0.4.1",
"version": "0.4.2",
"description": "Stealth Chromium that passes every bot detection test. Drop-in Playwright/Puppeteer replacement with source-level fingerprint patches.",
"type": "module",
"main": "dist/index.js",
+34 -1
View File
@@ -57,6 +57,20 @@ export class BinaryVerificationError extends Error {
}
}
/**
* A non-2xx HTTP response during a binary download. Carries the status code so
* callers can distinguish a 404 (binary not built for this platform) from
* transient failures.
*/
export class DownloadHttpError extends Error {
status: number;
constructor(status: number, statusText: string) {
super(`Download failed: HTTP ${status} ${statusText}`);
this.name = "DownloadHttpError";
this.status = status;
}
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
@@ -93,6 +107,24 @@ export async function ensureBinary(licenseKey?: string): Promise<string> {
} catch (e) {
// Authenticity could not be confirmed — surface verbatim.
if (e instanceof BinaryVerificationError) throw e;
// macOS has no Pro binary yet. Rather than hard-failing a paying
// customer, fall back to the free binary with a clear notice. Scoped to
// the 404 (binary-not-found) case so that (a) transient and verification
// failures still hard-fail — no silent downgrade — and (b) the moment the
// macOS Pro build ships, the 404 disappears and Pro is served
// automatically with no wrapper change.
if (
getPlatformTag().startsWith("darwin") &&
e instanceof DownloadHttpError &&
e.status === 404
) {
console.warn(
"[cloakbrowser] macOS Pro binary is not available yet — using the " +
"free binary for now. Your license stays valid and you'll get the " +
"Pro binary on macOS automatically once the build ships."
);
// fall through to the free-tier download below
} else {
// Transient failure with no cached Pro binary to use — surface a clear
// error rather than silently downloading the free binary.
throw new Error(
@@ -102,6 +134,7 @@ export async function ensureBinary(licenseKey?: string): Promise<string> {
{ cause: e }
);
}
}
} else if (info) {
console.log(`[cloakbrowser] License validation failed (plan=${info.plan}), using free tier`);
} else {
@@ -536,7 +569,7 @@ async function downloadFile(url: string, dest: string, headers?: Record<string,
});
if (!response.ok) {
throw new Error(`Download failed: HTTP ${response.status} ${response.statusText}`);
throw new DownloadHttpError(response.status, response.statusText);
}
if (!response.body) {