clearcote 0.47.0 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pacing.js","sourceRoot":"","sources":["../src/pacing.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,40 @@
1
+ /** 1/true/yes/on forces Widevine on, 0/false/no/off off; an explicit `widevine` option wins over it. */
2
+ export declare const WIDEVINE_ENV = "CLEARCOTE_WIDEVINE";
3
+ /** How long a DEFAULT (not requested) launch waits for a first download, in seconds (clamped to 1-600). */
4
+ export declare const TIMEOUT_ENV = "CLEARCOTE_WIDEVINE_TIMEOUT_SECONDS";
5
+ export declare const DEFAULT_TIMEOUT_SECONDS = 15;
6
+ export declare const MIN_TIMEOUT_SECONDS = 1;
7
+ export declare const MAX_TIMEOUT_SECONDS = 600;
8
+ /** Ask Google for a newer CDM at most once a day. */
9
+ export declare const CHECK_INTERVAL_MS: number;
10
+ /** A failed first download is not retried for 10 minutes in this process. */
11
+ export declare const RETRY_AFTER_MS = 600000;
12
+ /** How long a background refresh waits for Google's update check (it may hold a finishing process that long). */
13
+ export declare const REFRESH_CHECK_TIMEOUT_MS = 10000;
14
+ /** In the cache root: its mtime is the last update check (a failed one counts). */
15
+ export declare const STAMP_FILE = ".last-check";
16
+ /** What a CDM version looks like; anything else never becomes a path. */
17
+ export declare const VERSION_RE: RegExp;
18
+ /** How long a requested install waits for another process installing the CDM. */
19
+ export declare const LOCK_WAIT_MS = 300000;
20
+ export declare const WARN_CODE = "widevine-default";
21
+ /** What one launch does about Widevine: requested, the licensed default, or nothing. */
22
+ export type WidevineMode = "explicit" | "default" | "off";
1
23
  /** Chrome's component-updater app id for the Widevine CDM + Google's Omaha JSON endpoint. */
2
24
  export declare const WIDEVINE_APP_ID = "oimompecagnajdejgnnjijobebaeigek";
3
25
  export declare const OMAHA_URL = "https://update.googleapis.com/service/update2/json";
4
26
  export declare const HINT_FILE = "latest-component-updated-widevine-cdm";
5
27
  export interface WidevineOptions {
6
- /** Destination root (default ~/.clearcote/WidevineCdm). */
28
+ /** Destination root (default CLEARCOTE_WIDEVINE_DIR, else ~/.clearcote/WidevineCdm). */
7
29
  dest?: string;
8
30
  /** Suppress progress logging. */
9
31
  quiet?: boolean;
32
+ /** Abort the update check and the download (the default launch path bounds its wait with it). */
33
+ signal?: AbortSignal;
34
+ /** Abort only the update check (a background refresh bounds it). */
35
+ checkSignal?: AbortSignal;
36
+ /** How long to wait for another process installing the CDM (default 5 minutes). */
37
+ lockTimeoutMs?: number;
10
38
  }
11
39
  /** Minimal Omaha v3.1 update check for the current-OS x64 Widevine CDM (version 0.0.0.0 -> latest). */
12
40
  export declare function omahaRequestBody(): unknown;
@@ -14,15 +42,60 @@ export declare function omahaRequestBody(): unknown;
14
42
  export declare function parseUpdate(resp: any): [string, string, string];
15
43
  /** A CRX3 file is 'Cr24' + u32 version + u32 headerLen + header + zip. Return the zip bytes. */
16
44
  export declare function crx3ToZip(buf: Buffer): Buffer;
45
+ /** The newest complete CDM in the cache (`root`, default the cache root), or undefined. No network. */
46
+ export declare function cachedCdm(root?: string): string | undefined;
17
47
  /**
18
- * Download + verify the current-OS x64 Widevine CDM into `dest` (default ~/.clearcote/WidevineCdm/
19
- * <version>). Returns the versioned CDM directory (manifest.json + _platform_specific/<subdir>/
20
- * <filename>). Re-fetch is skipped if already present.
48
+ * Download + verify the current-OS x64 Widevine CDM into `dest` (default CLEARCOTE_WIDEVINE_DIR, else
49
+ * ~/.clearcote/WidevineCdm) as <version>/. Returns the versioned CDM directory (manifest.json +
50
+ * _platform_specific/<subdir>/<filename>).
51
+ *
52
+ * Always asks Google's update server for the latest version; the download is skipped when that version is
53
+ * already installed. Installs take the install lock and land with a rename. A version that does not look
54
+ * like one is refused.
21
55
  */
22
56
  export declare function fetchWidevine(opts?: WidevineOptions): Promise<string>;
57
+ /**
58
+ * The CDM directory a launch should use, network only when needed: the cached CDM when Google was asked
59
+ * within the last day (no network), otherwise {@link fetchWidevine}. When that fails (offline, a firewall,
60
+ * Google refusing the request) the CDM already on disk still works and is used, and the failed check is
61
+ * recorded (the stamp is touched), so no launch asks again for a day. Rejects only when there is no CDM.
62
+ */
63
+ export declare function resolveCdm(opts?: WidevineOptions): Promise<string>;
64
+ /**
65
+ * `"explicit"`, `"default"` or `"off"` for a launch. `option` (the `widevine` launch option) wins: true ->
66
+ * explicit, false -> off. Unset follows CLEARCOTE_WIDEVINE (1/true/yes/on -> explicit, 0/false/no/off ->
67
+ * off). Otherwise a licensed launch (a licence key resolved, which selects the PRO build) gets the default
68
+ * and a free launch stays off. Same rules in the Python and .NET SDKs.
69
+ */
70
+ export declare function widevineMode(option: boolean | undefined | null, licensed: boolean, env?: NodeJS.ProcessEnv): WidevineMode;
71
+ /** How long a default launch waits for a first download: CLEARCOTE_WIDEVINE_TIMEOUT_SECONDS clamped to 1-600, else 15
72
+ * (an unparsable or non-positive value too). */
73
+ export declare function timeoutSeconds(env?: NodeJS.ProcessEnv): number;
74
+ /** Tests: forget the in-flight fetch, the refresh and the last failure (and stop what is running). */
75
+ export declare function resetWidevineDefaultState(): void;
76
+ /** Tests: the background refresh in flight, if any. */
77
+ export declare function widevineRefreshInFlight(): Promise<void> | undefined;
78
+ /** Tests: pretend the last failure was this long ago. */
79
+ export declare function ageLastWidevineFailure(byMs: number): void;
80
+ /**
81
+ * The CDM for a launch that did not ask for Widevine (the licensed default), or undefined.
82
+ *
83
+ * A CDM in the cache is used AT ONCE, with no network on the launch path; when its last update check is over a
84
+ * day old, a background refresh asks Google for a newer one (used from the next launch on). Only with nothing
85
+ * cached does the launch wait for a download, at most `timeoutMs` (default {@link timeoutSeconds}). FAIL-SOFT:
86
+ * never rejects. When there is no CDM to use (the download failed or did not finish in time) it warns once per
87
+ * process (unless quiet) and answers undefined, so the launch goes on without DRM; a download still running at
88
+ * the deadline is cancelled (it cannot keep the process alive), and a failed one is not retried for
89
+ * RETRY_AFTER_MS in this process. The fetches here are quiet: the default path prints nothing else.
90
+ */
91
+ export declare function defaultCdm(opts?: {
92
+ quiet?: boolean;
93
+ timeoutMs?: number;
94
+ }): Promise<string | undefined>;
23
95
  /**
24
96
  * Make a persistent profile load the fetched CDM: copy it under <userDataDir>/WidevineCdm/<version>/
25
- * and write the component hint file the engine reads. Fetches the CDM first if needed.
97
+ * and write the component hint file the engine reads. Fetches the CDM first if needed
98
+ * ({@link resolveCdm}: the cached one when it is current).
26
99
  */
27
100
  export declare function seedWidevine(userDataDir: string, opts?: WidevineOptions): Promise<string>;
28
101
  /**
@@ -51,4 +124,16 @@ export declare function widevineArgs(ignoreDefaultArgs: string[] | boolean | und
51
124
  export declare function widevineCdmArgs(exe: string | undefined, opts?: {
52
125
  quiet?: boolean;
53
126
  }): Promise<string[]>;
127
+ /**
128
+ * The Widevine switch for one launch, by {@link widevineMode}. explicit: {@link widevineCdmArgs} (the
129
+ * behaviour `widevine: true` always had). default: the same switch, only on an engine that implements it
130
+ * (r32+; an older engine keeps the old default, off), with the CDM from {@link defaultCdm} (bounded,
131
+ * fail-soft, warns once). off: nothing. A `--widevine-cdm-path` the caller put in `args` is theirs:
132
+ * nothing is added then. Works for every launch path: such an engine loads the CDM without a profile,
133
+ * incognito included.
134
+ */
135
+ export declare function widevineLaunchArgs(exe: string | undefined, option: boolean | undefined | null, licensed: boolean, userArgs: readonly string[] | undefined, opts?: {
136
+ quiet?: boolean;
137
+ timeoutMs?: number;
138
+ }): Promise<string[]>;
54
139
  //# sourceMappingURL=widevine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"widevine.d.ts","sourceRoot":"","sources":["../src/widevine.ts"],"names":[],"mappings":"AAoBA,6FAA6F;AAC7F,eAAO,MAAM,eAAe,qCAAqC,CAAC;AAClE,eAAO,MAAM,SAAS,uDAAuD,CAAC;AAC9E,eAAO,MAAM,SAAS,0CAA0C,CAAC;AAwBjE,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAUD,uGAAuG;AACvG,wBAAgB,gBAAgB,IAAI,OAAO,CAiB1C;AAgBD,oGAAoG;AACpG,wBAAgB,WAAW,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAe/D;AAED,gGAAgG;AAChG,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM7C;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgC/E;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiBnG;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,iBAAiB,EAAE,MAAM,EAAE,GAAG,OAAO,GAAG,SAAS,EACjD,QAAQ,EAAE,MAAM,EAAE,GACjB;IAAE,iBAAiB,EAAE,MAAM,EAAE,GAAG,OAAO,GAAG,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAevE;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAOhH"}
1
+ {"version":3,"file":"widevine.d.ts","sourceRoot":"","sources":["../src/widevine.ts"],"names":[],"mappings":"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"}
package/dist/widevine.js CHANGED
@@ -1,21 +1,63 @@
1
- // Opt-in Widevine CDM fetch + persistent-profile seeding (Windows + Linux).
1
+ // Widevine CDM fetch + persistent-profile seeding (Windows + Linux).
2
2
  //
3
3
  // clearcote is a 100%-open-source build: it ships the EME/Widevine *plumbing* (enable_widevine=true)
4
- // but NOT Google's proprietary CDM binary (that blob can't live in a FOSS package). This module lets
5
- // a user *opt in* to fetching the CDM from Google's component server at runtime — exactly how a real
6
- // Chrome receives it via the component updater — so requestMediaKeySystemAccess('com.widevine.alpha')
7
- // resolves (DRM plays, and the EME surface matches a real Chrome instead of a no-Widevine tell).
4
+ // but NOT Google's proprietary CDM binary (that blob can't live in a FOSS package). This module fetches
5
+ // the CDM from Google's component server at runtime — exactly how a real Chrome receives it via the
6
+ // component updater — so requestMediaKeySystemAccess('com.widevine.alpha') resolves (DRM plays, and the
7
+ // EME surface matches a real Chrome instead of a no-Widevine tell).
8
8
  //
9
9
  // import { fetchWidevine, launchPersistentContext } from "clearcote";
10
10
  // await fetchWidevine(); // download + verify into ~/.clearcote
11
11
  // const ctx = await launchPersistentContext("profile", { widevine: true }); // seeds + enables it
12
- import { createHash } from "node:crypto";
13
- import { cpSync, existsSync, mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
14
- import { rm } from "node:fs/promises";
12
+ //
13
+ // WHEN IT IS ON (since 0.48.0). A licensed launch (a licence key resolved: the launch that selects the
14
+ // PRO build, which claims the Google Chrome brand) on an engine with --widevine-cdm-path (r32+) loads the
15
+ // CDM by default; a free launch keeps it opt-in. `widevine: false` or CLEARCOTE_WIDEVINE=0 turns it off,
16
+ // `widevine: true` or CLEARCOTE_WIDEVINE=1 on for any launch; an explicit option wins over the
17
+ // environment (see widevineMode). The default is FAIL-SOFT and never waits on the network when a CDM is
18
+ // cached: a cached CDM is used at once (a stale one is refreshed in the background). Only the very first
19
+ // launch, with nothing cached, waits for the download, at most CLEARCOTE_WIDEVINE_TIMEOUT_SECONDS (default
20
+ // 15, clamped to 1-600); when that fails the launch goes on without Widevine and says so once per process.
21
+ // The default path prints nothing else. A launch that ASKED for Widevine keeps its old behaviour (a note,
22
+ // unless quiet, and no DRM).
23
+ //
24
+ // The CDM is cached (CLEARCOTE_WIDEVINE_DIR, else ~/.clearcote/WidevineCdm); Google is asked for a newer
25
+ // one at most once a day, and a failed check counts as one (the cached CDM keeps working). Installs take
26
+ // the SDK's install lock in the cache directory and land with download.ts's rename (retried on Windows
27
+ // while a scanner holds the fresh tree), so concurrent first launches never see a half-extracted CDM. The
28
+ // version Google names must look like one before it is used as a directory name. Same rules, names and
29
+ // warning as the Python and .NET SDKs.
30
+ import { createHash, randomBytes } from "node:crypto";
31
+ import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
15
32
  import os from "node:os";
16
33
  import path from "node:path";
34
+ import { performance } from "node:perf_hooks";
17
35
  import extract from "extract-zip";
36
+ import { acquireInstallLock, renameDir } from "./download.js";
18
37
  import { engineSupportsSwitch } from "./launchopts.js";
38
+ import { warnOnce } from "./warnings.js";
39
+ /** 1/true/yes/on forces Widevine on, 0/false/no/off off; an explicit `widevine` option wins over it. */
40
+ export const WIDEVINE_ENV = "CLEARCOTE_WIDEVINE";
41
+ /** How long a DEFAULT (not requested) launch waits for a first download, in seconds (clamped to 1-600). */
42
+ export const TIMEOUT_ENV = "CLEARCOTE_WIDEVINE_TIMEOUT_SECONDS";
43
+ export const DEFAULT_TIMEOUT_SECONDS = 15;
44
+ export const MIN_TIMEOUT_SECONDS = 1;
45
+ export const MAX_TIMEOUT_SECONDS = 600;
46
+ /** Ask Google for a newer CDM at most once a day. */
47
+ export const CHECK_INTERVAL_MS = 24 * 3600_000;
48
+ /** A failed first download is not retried for 10 minutes in this process. */
49
+ export const RETRY_AFTER_MS = 600_000;
50
+ /** How long a background refresh waits for Google's update check (it may hold a finishing process that long). */
51
+ export const REFRESH_CHECK_TIMEOUT_MS = 10_000;
52
+ /** In the cache root: its mtime is the last update check (a failed one counts). */
53
+ export const STAMP_FILE = ".last-check";
54
+ /** What a CDM version looks like; anything else never becomes a path. */
55
+ export const VERSION_RE = /^[0-9]+(\.[0-9]+){1,3}$/;
56
+ /** How long a requested install waits for another process installing the CDM. */
57
+ export const LOCK_WAIT_MS = 300_000;
58
+ export const WARN_CODE = "widevine-default";
59
+ const TRUTHY = ["1", "true", "yes", "on"];
60
+ const FALSY = ["0", "false", "no", "off"];
19
61
  /** Chrome's component-updater app id for the Widevine CDM + Google's Omaha JSON endpoint. */
20
62
  export const WIDEVINE_APP_ID = "oimompecagnajdejgnnjijobebaeigek";
21
63
  export const OMAHA_URL = "https://update.googleapis.com/service/update2/json";
@@ -59,13 +101,28 @@ export function omahaRequestBody() {
59
101
  },
60
102
  };
61
103
  }
62
- async function postJson(url, body) {
104
+ /** `AbortSignal.timeout(ms)`, also aborted with `signal` (AbortSignal.any is Node 20.3+). */
105
+ function withTimeout(ms, signal) {
106
+ const timeout = AbortSignal.timeout(ms);
107
+ if (!signal)
108
+ return timeout;
109
+ const both = new AbortController();
110
+ const stop = (s) => () => both.abort(s.reason);
111
+ if (signal.aborted)
112
+ both.abort(signal.reason);
113
+ else {
114
+ signal.addEventListener("abort", stop(signal), { once: true });
115
+ timeout.addEventListener("abort", stop(timeout), { once: true });
116
+ }
117
+ return both.signal;
118
+ }
119
+ async function postJson(url, body, signal) {
63
120
  const res = await fetch(url, {
64
121
  method: "POST",
65
122
  headers: { "Content-Type": "application/json", "User-Agent": UA },
66
123
  body: JSON.stringify(body),
67
124
  redirect: "follow",
68
- signal: AbortSignal.timeout(60_000),
125
+ signal: withTimeout(60_000, signal),
69
126
  });
70
127
  if (!res.ok)
71
128
  throw new Error(`Widevine update check HTTP ${res.status}`);
@@ -105,54 +162,326 @@ export function crx3ToZip(buf) {
105
162
  throw new Error("malformed CRX3 (header overruns buffer)");
106
163
  return buf.subarray(12 + headerLen);
107
164
  }
165
+ function cdmBinary(verDir) {
166
+ const { subdir, filename } = cdmPlatform();
167
+ return path.join(verDir, "_platform_specific", subdir, filename);
168
+ }
169
+ /** A complete CDM directory: its manifest and this OS's CDM binary are both there. */
170
+ function validCdm(verDir) {
171
+ return !!verDir && existsSync(cdmBinary(verDir)) && existsSync(path.join(verDir, "manifest.json"));
172
+ }
173
+ function versionKey(name) {
174
+ // "current" (no version in the update response) sorts below any real version
175
+ return /^\d+(\.\d+)*$/.test(name) ? [1, ...name.split(".").map(Number)] : [0];
176
+ }
177
+ function compareVersions(a, b) {
178
+ const ka = versionKey(a);
179
+ const kb = versionKey(b);
180
+ for (let i = 0; i < Math.max(ka.length, kb.length); i++) {
181
+ const d = (ka[i] ?? -1) - (kb[i] ?? -1);
182
+ if (d)
183
+ return d;
184
+ }
185
+ return 0;
186
+ }
187
+ /** The newest complete CDM in the cache (`root`, default the cache root), or undefined. No network. */
188
+ export function cachedCdm(root = cacheRoot()) {
189
+ let names;
190
+ try {
191
+ names = readdirSync(root).filter((n) => !n.startsWith("."));
192
+ }
193
+ catch {
194
+ return undefined;
195
+ }
196
+ for (const name of names.sort(compareVersions).reverse()) {
197
+ const dir = path.join(root, name);
198
+ if (validCdm(dir))
199
+ return dir;
200
+ }
201
+ return undefined;
202
+ }
203
+ /** Whether Google was asked for a newer CDM less than CHECK_INTERVAL_MS ago. */
204
+ function stampFresh(root) {
205
+ try {
206
+ const age = Date.now() - statSync(path.join(root, STAMP_FILE)).mtimeMs;
207
+ return age > -3600_000 && age < CHECK_INTERVAL_MS; // a stamp from the future (clock moved back) is not fresh
208
+ }
209
+ catch {
210
+ return false;
211
+ }
212
+ }
213
+ function touchStamp(root) {
214
+ try {
215
+ writeFileSync(path.join(root, STAMP_FILE), String(Math.floor(Date.now() / 1000)));
216
+ }
217
+ catch {
218
+ /* the next launch asks again */
219
+ }
220
+ }
221
+ /** Remove a tree, never throwing (a Windows scanner can hold a file in it for a moment; the next install sweeps it). */
222
+ function removeQuietly(p) {
223
+ try {
224
+ rmSync(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
225
+ }
226
+ catch {
227
+ /* swept by the next install */
228
+ }
229
+ }
230
+ /** Remove what an interrupted install left in the cache root (only under the install lock). */
231
+ function sweepLeftovers(root) {
232
+ let names = [];
233
+ try {
234
+ names = readdirSync(root);
235
+ }
236
+ catch {
237
+ return;
238
+ }
239
+ for (const n of names)
240
+ if (n.startsWith(".tmp-") || n.startsWith(".trash-"))
241
+ removeQuietly(path.join(root, n));
242
+ }
243
+ /**
244
+ * `root`/<version>, once `version` looks like a CDM version (`4.10.2830.0`). The version comes from Google's
245
+ * update response: anything else (`../victim`, an absolute path) is refused before it can name, or delete, a
246
+ * directory. No version at all means `current`.
247
+ */
248
+ function versionDir(root, version) {
249
+ if (!version)
250
+ return path.join(root, "current");
251
+ if (!VERSION_RE.test(String(version))) {
252
+ throw new Error(`Widevine update response named an unexpected version ${JSON.stringify(String(version).slice(0, 80))} — refusing to use it`);
253
+ }
254
+ return path.join(root, version);
255
+ }
108
256
  /**
109
- * Download + verify the current-OS x64 Widevine CDM into `dest` (default ~/.clearcote/WidevineCdm/
110
- * <version>). Returns the versioned CDM directory (manifest.json + _platform_specific/<subdir>/
111
- * <filename>). Re-fetch is skipped if already present.
257
+ * Download, verify and install one CDM version into `root`/<version>. Holds the SDK's install lock
258
+ * (`root`/.install-lock, the protocol in download.ts) so concurrent first launches take turns, extracts
259
+ * into `root`/.tmp-<id> and renames the finished tree into place: a reader never sees a half-extracted CDM.
260
+ * The renames are download.ts's, which retry while a Windows scanner holds a file in the fresh tree.
261
+ */
262
+ async function install(root, url, sha256, version, opts) {
263
+ const verDir = versionDir(root, version);
264
+ mkdirSync(root, { recursive: true });
265
+ const lock = await acquireInstallLock(root, { timeoutMs: opts.lockTimeoutMs ?? LOCK_WAIT_MS, quiet: true });
266
+ try {
267
+ if (validCdm(verDir))
268
+ return verDir; // another process installed it while we waited
269
+ sweepLeftovers(root);
270
+ log(opts.quiet, `fetching CDM ${version || "(latest)"}`);
271
+ const res = await fetch(url, { headers: { "User-Agent": UA }, redirect: "follow", signal: withTimeout(120_000, opts.signal) });
272
+ if (!res.ok)
273
+ throw new Error(`Widevine CDM download HTTP ${res.status}`);
274
+ const blob = Buffer.from(await res.arrayBuffer());
275
+ if (createHash("sha256").update(blob).digest("hex") !== sha256.toLowerCase()) {
276
+ throw new Error("Widevine CDM sha256 mismatch — refusing to install");
277
+ }
278
+ const tmp = path.join(root, `.tmp-${randomBytes(8).toString("hex")}`);
279
+ try {
280
+ mkdirSync(tmp);
281
+ const zipPath = path.join(tmp, ".cdm.zip");
282
+ writeFileSync(zipPath, crx3ToZip(blob));
283
+ await extract(zipPath, { dir: tmp });
284
+ rmSync(zipPath, { force: true });
285
+ // some CRX layouts nest the platform dir differently; surface a clear error
286
+ if (!validCdm(tmp))
287
+ throw new Error(`extracted CDM but ${cdmPlatform().filename} not at ${cdmBinary(verDir)}`);
288
+ if (existsSync(verDir)) { // an incomplete tree (an older SDK extracted in place): move it aside
289
+ const trash = path.join(root, `.trash-${randomBytes(8).toString("hex")}`);
290
+ renameDir(verDir, trash);
291
+ removeQuietly(trash);
292
+ }
293
+ renameDir(tmp, verDir);
294
+ }
295
+ finally {
296
+ removeQuietly(tmp);
297
+ }
298
+ }
299
+ finally {
300
+ await lock.release();
301
+ }
302
+ log(opts.quiet, `installed: ${verDir}`);
303
+ return verDir;
304
+ }
305
+ /**
306
+ * Download + verify the current-OS x64 Widevine CDM into `dest` (default CLEARCOTE_WIDEVINE_DIR, else
307
+ * ~/.clearcote/WidevineCdm) as <version>/. Returns the versioned CDM directory (manifest.json +
308
+ * _platform_specific/<subdir>/<filename>).
309
+ *
310
+ * Always asks Google's update server for the latest version; the download is skipped when that version is
311
+ * already installed. Installs take the install lock and land with a rename. A version that does not look
312
+ * like one is refused.
112
313
  */
113
314
  export async function fetchWidevine(opts = {}) {
114
- const [url, sha256, version] = parseUpdate(await postJson(OMAHA_URL, omahaRequestBody()));
315
+ const [url, sha256, version] = parseUpdate(await postJson(OMAHA_URL, omahaRequestBody(), opts.checkSignal ?? opts.signal));
115
316
  const root = opts.dest || cacheRoot();
116
- const verDir = path.join(root, version || "current");
117
- const { subdir, filename } = cdmPlatform();
118
- const dll = path.join(verDir, "_platform_specific", subdir, filename);
119
- if (existsSync(dll) && existsSync(path.join(verDir, "manifest.json"))) {
317
+ const verDir = versionDir(root, version);
318
+ if (validCdm(verDir)) {
319
+ touchStamp(root);
120
320
  log(opts.quiet, `already present: ${verDir}`);
121
321
  return verDir;
122
322
  }
123
- log(opts.quiet, `fetching CDM ${version || "(latest)"}`);
124
- const res = await fetch(url, { headers: { "User-Agent": UA }, redirect: "follow", signal: AbortSignal.timeout(120_000) });
125
- if (!res.ok)
126
- throw new Error(`Widevine CDM download HTTP ${res.status}`);
127
- const blob = Buffer.from(await res.arrayBuffer());
128
- // The CDM is a NATIVE DLL loaded into the browser process — never install it unverified. A
129
- // missing hash in the update response is a hard failure, not a skip.
323
+ // The CDM is a NATIVE library loaded into the browser process — never install it unverified. A
324
+ // missing hash in the update response is a hard failure, not a skip (checked before downloading).
130
325
  if (!sha256)
131
326
  throw new Error("Widevine update response had no sha256 — refusing to install an unverified CDM");
132
- if (createHash("sha256").update(blob).digest("hex") !== sha256.toLowerCase()) {
133
- throw new Error("Widevine CDM sha256 mismatch — refusing to install");
327
+ const dir = await install(root, url, sha256, version, opts);
328
+ touchStamp(root);
329
+ return dir;
330
+ }
331
+ /**
332
+ * The CDM directory a launch should use, network only when needed: the cached CDM when Google was asked
333
+ * within the last day (no network), otherwise {@link fetchWidevine}. When that fails (offline, a firewall,
334
+ * Google refusing the request) the CDM already on disk still works and is used, and the failed check is
335
+ * recorded (the stamp is touched), so no launch asks again for a day. Rejects only when there is no CDM.
336
+ */
337
+ export async function resolveCdm(opts = {}) {
338
+ const root = opts.dest || cacheRoot();
339
+ const cached = cachedCdm(root);
340
+ if (cached && stampFresh(root))
341
+ return cached;
342
+ try {
343
+ return await fetchWidevine({ ...opts, dest: root });
344
+ }
345
+ catch (e) {
346
+ if (cached) {
347
+ touchStamp(root);
348
+ return cached;
349
+ }
350
+ throw e;
351
+ }
352
+ }
353
+ /**
354
+ * `"explicit"`, `"default"` or `"off"` for a launch. `option` (the `widevine` launch option) wins: true ->
355
+ * explicit, false -> off. Unset follows CLEARCOTE_WIDEVINE (1/true/yes/on -> explicit, 0/false/no/off ->
356
+ * off). Otherwise a licensed launch (a licence key resolved, which selects the PRO build) gets the default
357
+ * and a free launch stays off. Same rules in the Python and .NET SDKs.
358
+ */
359
+ export function widevineMode(option, licensed, env = process.env) {
360
+ if (option !== undefined && option !== null)
361
+ return option ? "explicit" : "off";
362
+ const value = String(env[WIDEVINE_ENV] ?? "").trim().toLowerCase();
363
+ if (TRUTHY.includes(value))
364
+ return "explicit";
365
+ if (FALSY.includes(value))
366
+ return "off";
367
+ return licensed ? "default" : "off";
368
+ }
369
+ /** How long a default launch waits for a first download: CLEARCOTE_WIDEVINE_TIMEOUT_SECONDS clamped to 1-600, else 15
370
+ * (an unparsable or non-positive value too). */
371
+ export function timeoutSeconds(env = process.env) {
372
+ const raw = String(env[TIMEOUT_ENV] ?? "").trim();
373
+ const value = raw === "" ? NaN : Number(raw);
374
+ if (!(value > 0))
375
+ return DEFAULT_TIMEOUT_SECONDS; // also NaN
376
+ return Math.min(Math.max(value, MIN_TIMEOUT_SECONDS), MAX_TIMEOUT_SECONDS);
377
+ }
378
+ // The default path's process state: the first download in flight (one at a time), the background refresh of
379
+ // a stale cached CDM (one at a time), and when a first download last failed (a monotonic clock).
380
+ let defaultJob;
381
+ let refreshJob;
382
+ let defaultFailedAt;
383
+ /** Tests: forget the in-flight fetch, the refresh and the last failure (and stop what is running). */
384
+ export function resetWidevineDefaultState() {
385
+ defaultJob?.controller.abort();
386
+ refreshJob?.controller.abort();
387
+ defaultJob = undefined;
388
+ refreshJob = undefined;
389
+ defaultFailedAt = undefined;
390
+ }
391
+ /** Tests: the background refresh in flight, if any. */
392
+ export function widevineRefreshInFlight() {
393
+ return refreshJob?.promise;
394
+ }
395
+ /** Tests: pretend the last failure was this long ago. */
396
+ export function ageLastWidevineFailure(byMs) {
397
+ if (defaultFailedAt !== undefined)
398
+ defaultFailedAt -= byMs;
399
+ }
400
+ function shortReason(e) {
401
+ const err = e;
402
+ const text = String(err?.message ?? e ?? "").trim().split("\n")[0].slice(0, 160);
403
+ const name = err?.name ?? "Error";
404
+ return text ? `${name}: ${text}` : name;
405
+ }
406
+ function warnDefault(reason, quiet) {
407
+ warnOnce(WARN_CODE, `Widevine is on by default for licensed launches, but the CDM could not be loaded (${reason}); ` +
408
+ "launching without it. Set CLEARCOTE_WIDEVINE=0 (or pass widevine: false) to stop trying.", quiet);
409
+ }
410
+ /** Ask Google whether a newer CDM exists, with nobody waiting: one at a time, quiet. The update check is bounded
411
+ * (REFRESH_CHECK_TIMEOUT_MS), so a blackholed network holds a finishing process that long at most, once a day:
412
+ * {@link resolveCdm} records a failed check. */
413
+ function refreshInBackground() {
414
+ if (refreshJob)
415
+ return;
416
+ const controller = new AbortController();
417
+ const job = {
418
+ controller,
419
+ promise: resolveCdm({ quiet: true, signal: controller.signal, checkSignal: withTimeout(REFRESH_CHECK_TIMEOUT_MS, controller.signal) })
420
+ .then(() => undefined, () => undefined)
421
+ .finally(() => { if (refreshJob === job)
422
+ refreshJob = undefined; }),
423
+ };
424
+ refreshJob = job;
425
+ }
426
+ /**
427
+ * The CDM for a launch that did not ask for Widevine (the licensed default), or undefined.
428
+ *
429
+ * A CDM in the cache is used AT ONCE, with no network on the launch path; when its last update check is over a
430
+ * day old, a background refresh asks Google for a newer one (used from the next launch on). Only with nothing
431
+ * cached does the launch wait for a download, at most `timeoutMs` (default {@link timeoutSeconds}). FAIL-SOFT:
432
+ * never rejects. When there is no CDM to use (the download failed or did not finish in time) it warns once per
433
+ * process (unless quiet) and answers undefined, so the launch goes on without DRM; a download still running at
434
+ * the deadline is cancelled (it cannot keep the process alive), and a failed one is not retried for
435
+ * RETRY_AFTER_MS in this process. The fetches here are quiet: the default path prints nothing else.
436
+ */
437
+ export async function defaultCdm(opts = {}) {
438
+ const root = cacheRoot();
439
+ const cached = cachedCdm(root);
440
+ if (cached) {
441
+ if (!stampFresh(root))
442
+ refreshInBackground();
443
+ return cached;
134
444
  }
135
- mkdirSync(verDir, { recursive: true });
136
- const tmp = mkdtempSync(path.join(os.tmpdir(), "cc-wv-"));
137
- const zipPath = path.join(tmp, "cdm.zip");
445
+ if (defaultFailedAt !== undefined && performance.now() - defaultFailedAt < RETRY_AFTER_MS)
446
+ return undefined;
447
+ const waitMs = opts.timeoutMs ?? timeoutSeconds() * 1000;
448
+ if (!defaultJob || defaultJob.done) {
449
+ const controller = new AbortController();
450
+ const started = { controller, done: false, promise: Promise.resolve({ err: undefined }) };
451
+ started.promise = resolveCdm({ quiet: true, signal: controller.signal, lockTimeoutMs: waitMs })
452
+ .then((ok) => ({ ok }), (err) => ({ err }))
453
+ .finally(() => { started.done = true; });
454
+ defaultJob = started;
455
+ }
456
+ const job = defaultJob;
457
+ let timer;
458
+ const deadline = new Promise((resolve) => { timer = setTimeout(() => resolve("timeout"), waitMs); });
138
459
  try {
139
- writeFileSync(zipPath, crx3ToZip(blob));
140
- await extract(zipPath, { dir: verDir });
460
+ const r = await Promise.race([job.promise, deadline]);
461
+ if (r === "timeout") {
462
+ defaultFailedAt = performance.now();
463
+ job.controller.abort(); // never keep the process alive for a launch that went on without it
464
+ warnDefault(`not ready within ${waitMs / 1000} s`, opts.quiet);
465
+ return undefined;
466
+ }
467
+ if ("err" in r) {
468
+ defaultFailedAt = performance.now();
469
+ warnDefault(shortReason(r.err), opts.quiet);
470
+ return undefined;
471
+ }
472
+ return r.ok;
141
473
  }
142
474
  finally {
143
- await rm(tmp, { recursive: true, force: true });
475
+ clearTimeout(timer);
144
476
  }
145
- if (!existsSync(dll))
146
- throw new Error(`extracted CDM but ${filename} not at ${dll}`);
147
- log(opts.quiet, `installed: ${verDir}`);
148
- return verDir;
149
477
  }
150
478
  /**
151
479
  * Make a persistent profile load the fetched CDM: copy it under <userDataDir>/WidevineCdm/<version>/
152
- * and write the component hint file the engine reads. Fetches the CDM first if needed.
480
+ * and write the component hint file the engine reads. Fetches the CDM first if needed
481
+ * ({@link resolveCdm}: the cached one when it is current).
153
482
  */
154
483
  export async function seedWidevine(userDataDir, opts = {}) {
155
- const src = await fetchWidevine(opts);
484
+ const src = await resolveCdm(opts);
156
485
  const version = path.basename(src);
157
486
  const wvRoot = path.join(userDataDir, "WidevineCdm");
158
487
  const target = path.join(wvRoot, version);
@@ -209,10 +538,32 @@ export async function widevineCdmArgs(exe, opts = {}) {
209
538
  try {
210
539
  if (!engineSupportsSwitch(exe, "widevine-cdm-path"))
211
540
  return [];
212
- return [`--widevine-cdm-path=${await fetchWidevine({ quiet: opts.quiet })}`];
541
+ return [`--widevine-cdm-path=${await resolveCdm({ quiet: opts.quiet })}`];
213
542
  }
214
543
  catch {
215
544
  return [];
216
545
  }
217
546
  }
547
+ function callerSetCdmPath(userArgs) {
548
+ return (userArgs ?? []).some((a) => String(a).split("=", 1)[0] === "--widevine-cdm-path");
549
+ }
550
+ /**
551
+ * The Widevine switch for one launch, by {@link widevineMode}. explicit: {@link widevineCdmArgs} (the
552
+ * behaviour `widevine: true` always had). default: the same switch, only on an engine that implements it
553
+ * (r32+; an older engine keeps the old default, off), with the CDM from {@link defaultCdm} (bounded,
554
+ * fail-soft, warns once). off: nothing. A `--widevine-cdm-path` the caller put in `args` is theirs:
555
+ * nothing is added then. Works for every launch path: such an engine loads the CDM without a profile,
556
+ * incognito included.
557
+ */
558
+ export async function widevineLaunchArgs(exe, option, licensed, userArgs, opts = {}) {
559
+ const mode = widevineMode(option, licensed);
560
+ if (mode === "off" || callerSetCdmPath(userArgs))
561
+ return [];
562
+ if (mode === "explicit")
563
+ return widevineCdmArgs(exe, { quiet: opts.quiet });
564
+ if (!engineSupportsSwitch(exe, "widevine-cdm-path"))
565
+ return [];
566
+ const cdm = await defaultCdm(opts);
567
+ return cdm ? [`--widevine-cdm-path=${cdm}`] : [];
568
+ }
218
569
  //# sourceMappingURL=widevine.js.map