feat(widevine): auto-seed CDM hint file for persistent contexts (Linux)

Sideloaded Widevine works on the first launch of a persistent context
instead of needing a manual two-launch hint-file workaround. The wrapper
writes Chromium's CDM hint file into the profile before launch when a
WidevineCdm directory is present next to the binary.

- New cloakbrowser/widevine.py and js/src/widevine.ts: resolve a sideloaded
  CDM (CLOAKBROWSER_WIDEVINE_CDM env var, else next to the binary) and seed
  the hint file. Linux only; no-op elsewhere. CLOAKBROWSER_WIDEVINE=0 disables.
- Never bundles/downloads/copies the CDM (proprietary); seeds only when the
  user-provided CDM is already present.
- Wired into launch_persistent_context[_async] and launchPersistentContext.
- README + js/README: Widevine / DRM section, env vars, FPJS tradeoff note.
- Tests: tests/test_widevine.py, js/tests/widevine.test.ts, persistent-context
  integration assertions.
This commit is contained in:
CloakHQ
2026-05-29 22:59:59 +02:00
parent 14ec2ebf5f
commit dcf9ba55d6
11 changed files with 676 additions and 1 deletions
+29
View File
@@ -258,3 +258,32 @@ async def test_persistent_context_async_timezone_id_alias(_mock_bin):
call_kwargs = pw.chromium.launch_persistent_context.call_args[1]
assert "--fingerprint-timezone=Europe/Paris" in call_kwargs["args"]
assert "timezone_id" not in call_kwargs
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
@patch("cloakbrowser.browser.maybe_resolve_geoip", return_value=(None, None, None))
@patch("cloakbrowser.browser.seed_widevine_hint")
def test_persistent_context_seeds_widevine(_mock_seed, _mock_geoip, _mock_bin):
"""Sync persistent launch seeds the Widevine hint with the profile path."""
pw_cm, pw, context = _make_mock_pw_and_context()
with patch("playwright.sync_api.sync_playwright", return_value=pw_cm):
from cloakbrowser.browser import launch_persistent_context
launch_persistent_context("/tmp/profile")
_mock_seed.assert_called_once_with("/tmp/profile", "/fake/chrome")
@pytest.mark.asyncio
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
@patch("cloakbrowser.browser.maybe_resolve_geoip", return_value=(None, None, None))
@patch("cloakbrowser.browser.seed_widevine_hint")
async def test_persistent_context_async_seeds_widevine(_mock_seed, _mock_geoip, _mock_bin):
"""Async persistent launch seeds the Widevine hint with the profile path."""
pw_cm, pw, context = _make_mock_async_pw_and_context()
with patch("playwright.async_api.async_playwright", return_value=pw_cm):
from cloakbrowser.browser import launch_persistent_context_async
await launch_persistent_context_async("/tmp/profile")
_mock_seed.assert_called_once_with("/tmp/profile", "/fake/chrome")