mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-06-23 11:41:46 +02:00
feat(docker): opt-in Widevine CDM auto-fetch for persistent contexts
Add bin/fetch-widevine.py — a stdlib-only fetcher that pulls the Widevine CDM from Google's component server (arch-aware, sha256-verified, atomic, cached). The Docker entrypoint runs it when CLOAKBROWSER_FETCH_WIDEVINE is set (off by default), exporting CLOAKBROWSER_WIDEVINE_CDM so persistent profiles get a working CDM without a local Chrome to copy from. Fail-soft; skips when a CDM is already set or CLOAKBROWSER_WIDEVINE=0. Bare-metal Linux users can run the script directly. README: document the flag, drop the outdated storage-quota note.
This commit is contained in:
+24
-9
@@ -17,6 +17,8 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import { getCacheDir } from "./config.js";
|
||||
|
||||
const HINT_FILENAME = "latest-component-updated-widevine-cdm";
|
||||
|
||||
/** True if `file` exists and is a regular file (mirrors Python's Path.is_file()). */
|
||||
@@ -45,11 +47,14 @@ function seedingDisabled(): boolean {
|
||||
/**
|
||||
* Locate a sideloaded Widevine CDM directory, or null if absent.
|
||||
*
|
||||
* Resolution:
|
||||
* - If CLOAKBROWSER_WIDEVINE_CDM is set, it is used exclusively (overrides
|
||||
* auto-detection). An invalid value (no `manifest.json`) skips seeding.
|
||||
* - Otherwise, `<dir of the chrome binary>/WidevineCdm` — where a user naturally
|
||||
* drops it, and where it lives for both downloaded and CLOAKBROWSER_BINARY_PATH binaries.
|
||||
* Resolution order:
|
||||
* 1. If CLOAKBROWSER_WIDEVINE_CDM is set, it is used exclusively (overrides
|
||||
* auto-detection). An invalid value (no `manifest.json`) skips seeding.
|
||||
* 2. `<dir of the chrome binary>/WidevineCdm` — a manual sideload, per version.
|
||||
* 3. `<cache dir>/WidevineCdm` (`~/.cloakbrowser/WidevineCdm`) — the
|
||||
* version-independent location the Docker auto-fetch and fetch-widevine.py
|
||||
* write to. This fallback lets one fetched CDM serve any binary (free or
|
||||
* Pro, any version) with no env var — the CDM `.so` is arch- not version-specific.
|
||||
*
|
||||
* A directory counts only if it contains `manifest.json`. The returned path is
|
||||
* absolute and symlink-resolved (mirrors Python's Path.resolve()).
|
||||
@@ -57,10 +62,20 @@ function seedingDisabled(): boolean {
|
||||
*/
|
||||
export function resolveWidevineCdmDir(binaryPath: string): string | null {
|
||||
const custom = process.env.CLOAKBROWSER_WIDEVINE_CDM;
|
||||
// `!== undefined` (not truthiness): a present-but-empty env var is "set" and
|
||||
// used exclusively — it resolves to an invalid path and skips seeding.
|
||||
const cdmDir = custom !== undefined ? custom : path.join(path.dirname(binaryPath), "WidevineCdm");
|
||||
return isFile(path.join(cdmDir, "manifest.json")) ? realPath(cdmDir) : null;
|
||||
if (custom !== undefined) {
|
||||
// Set exclusively (overrides auto-detection). An empty/whitespace value is
|
||||
// invalid — return null rather than let path.join("", ...) match a stray
|
||||
// manifest.json in the working directory.
|
||||
if (custom.trim() === "") return null;
|
||||
return isFile(path.join(custom, "manifest.json")) ? realPath(custom) : null;
|
||||
}
|
||||
for (const cdmDir of [
|
||||
path.join(path.dirname(binaryPath), "WidevineCdm"),
|
||||
path.join(getCacheDir(), "WidevineCdm"),
|
||||
]) {
|
||||
if (isFile(path.join(cdmDir, "manifest.json"))) return realPath(cdmDir);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,6 +35,8 @@ beforeEach(() => {
|
||||
setPlatform("linux"); // seeding is Linux-only; default to Linux in tests
|
||||
delete process.env.CLOAKBROWSER_WIDEVINE;
|
||||
delete process.env.CLOAKBROWSER_WIDEVINE_CDM;
|
||||
// Isolate the cache-root fallback from any real ~/.cloakbrowser on the host.
|
||||
process.env.CLOAKBROWSER_CACHE_DIR = tmpDir("cloak-cache-");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -42,6 +44,7 @@ afterEach(() => {
|
||||
Object.defineProperty(process, "platform", { value: origPlatform, configurable: true });
|
||||
delete process.env.CLOAKBROWSER_WIDEVINE;
|
||||
delete process.env.CLOAKBROWSER_WIDEVINE_CDM;
|
||||
delete process.env.CLOAKBROWSER_CACHE_DIR;
|
||||
for (const dir of tempDirs.splice(0)) fs.rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
@@ -66,6 +69,25 @@ describe("resolveWidevineCdmDir", () => {
|
||||
expect(resolveWidevineCdmDir(binary)).toBe(fs.realpathSync(cdm));
|
||||
});
|
||||
|
||||
it("falls back to <cache dir>/WidevineCdm when none next to the binary (Pro case)", () => {
|
||||
const cache = tmpDir("cloak-cacheroot-");
|
||||
process.env.CLOAKBROWSER_CACHE_DIR = cache;
|
||||
const cdm = makeCdm(path.join(cache, "WidevineCdm"));
|
||||
// Pro binary in its own dir with no adjacent CDM.
|
||||
const proBin = path.join(tmpDir("cloak-pro-"), "chromium-148.0-pro");
|
||||
fs.mkdirSync(proBin, { recursive: true });
|
||||
expect(resolveWidevineCdmDir(path.join(proBin, "chrome"))).toBe(fs.realpathSync(cdm));
|
||||
});
|
||||
|
||||
it("binary-dir CDM wins over the cache-root fallback", () => {
|
||||
const cache = tmpDir("cloak-cacheroot-");
|
||||
process.env.CLOAKBROWSER_CACHE_DIR = cache;
|
||||
makeCdm(path.join(cache, "WidevineCdm")); // cache-root CDM present...
|
||||
const binary = fakeBinary();
|
||||
const nextTo = makeCdm(path.join(path.dirname(binary), "WidevineCdm")); // ...sideload wins
|
||||
expect(resolveWidevineCdmDir(binary)).toBe(fs.realpathSync(nextTo));
|
||||
});
|
||||
|
||||
it("env var is exclusive — invalid env skips, no fallback to binary dir", () => {
|
||||
const binary = fakeBinary();
|
||||
makeCdm(path.join(path.dirname(binary), "WidevineCdm")); // valid CDM next to binary
|
||||
@@ -75,7 +97,10 @@ describe("resolveWidevineCdmDir", () => {
|
||||
expect(resolveWidevineCdmDir(binary)).toBeNull();
|
||||
});
|
||||
|
||||
it("empty env var is exclusive — no fallback to binary dir", () => {
|
||||
it("empty env var resolves to null (exclusive, never scans the working dir)", () => {
|
||||
// The empty check returns null before any path.join/isFile, so a stray
|
||||
// ./manifest.json can't be matched. (The CWD-ignore case is proven in the
|
||||
// Python suite; vitest workers don't allow process.chdir to simulate it here.)
|
||||
const binary = fakeBinary();
|
||||
makeCdm(path.join(path.dirname(binary), "WidevineCdm")); // valid CDM next to binary
|
||||
process.env.CLOAKBROWSER_WIDEVINE_CDM = ""; // set but empty
|
||||
|
||||
Reference in New Issue
Block a user