capiumbrowser 1.1.6 → 1.1.8
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.
- package/lib/channels.json +32 -32
- package/lib/config.js +48 -4
- package/lib/launch-common.js +10 -2
- package/package.json +1 -1
- package/playwright.js +14 -8
- package/puppeteer.js +13 -10
package/lib/channels.json
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
2
|
+
"_comment": "SINGLE SOURCE OF TRUTH for the current stable Capium browser build per OS/arch. Each OS reaches stable independently -- edit one entry without touching the others. This file ships inside the wheel (the SDK reads it to pick each host's stable build) AND is published to the download server by the release pipeline. `version` is the Chromium version (what the browser reports to the customer -- unchanged by the revision); `revision` is our packaging counter -- the served R2 folder is chromium-v<version>.<revision>/, ALWAYS dotted-suffixed starting at 1 (chromium-v<version>.1/ is the first build, .2/ a same-engine re-spin), so publishing a re-spin never overwrites the artifact older SDKs still fetch. `published: false` means the tag is declared but no artifact exists yet -- the SDK raises a clear 'not published yet' error instead of a raw 404.",
|
|
3
|
+
"schema": 1,
|
|
4
|
+
"updated": "2026-10-08",
|
|
5
|
+
"channels": {
|
|
6
|
+
"stable": {
|
|
7
|
+
"windows-x64": {
|
|
8
|
+
"version": "153.0.8010.53",
|
|
9
|
+
"engine_version": "153.0.8010.52",
|
|
10
|
+
"revision": 5,
|
|
11
|
+
"sha256": "3f53b86f80a3b145cb75995615f19c957a74d6a1a0ae1151ff64517b3a931d67",
|
|
12
|
+
"published": true
|
|
13
|
+
},
|
|
14
|
+
"macos-arm64": {
|
|
15
|
+
"version": "153.0.8010.53",
|
|
16
|
+
"engine_version": "153.0.8010.52",
|
|
17
|
+
"revision": 6,
|
|
18
|
+
"sha256": "eca400433d74e5ac3b59fa1764bba783674a05cfc5a34598c8c1d2f02c708e18",
|
|
19
|
+
"published": true
|
|
20
|
+
},
|
|
21
|
+
"linux-x64": {
|
|
22
|
+
"version": "153.0.8010.52",
|
|
23
|
+
"revision": 5,
|
|
24
|
+
"sha256": "f65f11a2b15dea523ee95e35cadbbd43c77aba4751cdc0f96f8c138143f041bb",
|
|
25
|
+
"published": true
|
|
26
|
+
},
|
|
27
|
+
"linux-arm64": {
|
|
28
|
+
"version": "153.0.8010.52",
|
|
29
|
+
"revision": 1,
|
|
30
|
+
"sha256": null,
|
|
31
|
+
"published": false
|
|
32
|
+
}
|
|
33
|
+
}
|
|
33
34
|
}
|
|
34
|
-
}
|
|
35
35
|
}
|
package/lib/config.js
CHANGED
|
@@ -17,16 +17,55 @@ const fs = require('fs');
|
|
|
17
17
|
const os = require('os');
|
|
18
18
|
const path = require('path');
|
|
19
19
|
|
|
20
|
+
// Playwright/Puppeteer default args to SUPPRESS (ignoreDefaultArgs): these leak automation
|
|
21
|
+
// or force a software render.
|
|
22
|
+
// --enable-automation : exposes navigator.webdriver=true + an automation banner.
|
|
23
|
+
// --enable-unsafe-swiftshader : the driver adds this so headless WebGL "works", but it ENABLES
|
|
24
|
+
// the (for us, broken-ICD) bundled SwiftShader path -> getContext
|
|
25
|
+
// ('webgl')=null headless / a software-raster tell. Stripping it
|
|
26
|
+
// forces Chromium onto the real GPU (GPU host) or system Mesa.
|
|
27
|
+
const IGNORE_DEFAULT_ARGS = ['--enable-automation', '--enable-unsafe-swiftshader'];
|
|
28
|
+
|
|
29
|
+
// Headless default screen/viewport. Headless has no real display, so the per-seed device-pool
|
|
30
|
+
// screen (e.g. 2560x1440) has no window manager to size a matching window against -- the
|
|
31
|
+
// framebuffer would stay at Chromium's small headless default and screen != viewport (a
|
|
32
|
+
// browserscan VM tell). Instead model the single MOST COMMON real setup: a maximized Chrome
|
|
33
|
+
// window on a 1080p Windows monitor -- screen 1920x1080, viewport 1920x947 (1040 avail height
|
|
34
|
+
// minus ~93px of tab/omnibox/bookmark chrome). Dominant real-world viewport, so a headless
|
|
35
|
+
// instance blends in, and screen > viewport stays coherent. Applied only in headless and only
|
|
36
|
+
// when the caller pins no screen (an explicit --fingerprint-screen-* / --window-size opts out).
|
|
37
|
+
const HEADLESS_SCREEN = [1920, 1080];
|
|
38
|
+
const HEADLESS_VIEWPORT = [1920, 947];
|
|
39
|
+
|
|
40
|
+
function getHeadlessDefaultArgs() {
|
|
41
|
+
const [sw, sh] = HEADLESS_SCREEN;
|
|
42
|
+
const [vw, vh] = HEADLESS_VIEWPORT;
|
|
43
|
+
return [
|
|
44
|
+
`--fingerprint-screen-width=${sw}`,
|
|
45
|
+
`--fingerprint-screen-height=${sh}`,
|
|
46
|
+
`--window-size=${vw},${vh}`,
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function hasScreenOverride(extra) {
|
|
51
|
+
if (!Array.isArray(extra)) return false;
|
|
52
|
+
const keys = ['--window-size', '--fingerprint-screen-width', '--fingerprint-screen-height'];
|
|
53
|
+
return extra.some((a) => typeof a === 'string' && keys.some((k) => a.startsWith(k)));
|
|
54
|
+
}
|
|
55
|
+
|
|
20
56
|
/**
|
|
21
57
|
* Return the default per-launch fingerprint flags.
|
|
22
58
|
*
|
|
23
59
|
* seed: int identity seed (a stable, coherent device per seed). Random if null.
|
|
24
60
|
* platform: "windows" | "macos" | "linux" -- the spoofed OS persona.
|
|
25
|
-
* screen:
|
|
26
|
-
*
|
|
27
|
-
*
|
|
61
|
+
* screen: DEFAULT null -> use the seed's COHERENT per-seed device screen from the binary's
|
|
62
|
+
* device pool (GPU-matched resolution + realistic dpr); fitWindow then sizes the window
|
|
63
|
+
* to it so viewport ~= screen. Passing an explicit [w,h] FORCES that resolution on every
|
|
64
|
+
* seed (all instances share one screen AND can pair e.g. an RTX 5080 with 1440x900) --
|
|
65
|
+
* only for a fixed-kiosk scenario. (Previously defaulted to [1440,900], a cross-instance
|
|
66
|
+
* correlation + GPU-incoherence tell -- now matches the python SDK's screen=None.)
|
|
28
67
|
*/
|
|
29
|
-
function getDefaultStealthArgs(seed = null, platform = 'windows', screen =
|
|
68
|
+
function getDefaultStealthArgs(seed = null, platform = 'windows', screen = null) {
|
|
30
69
|
const args = [];
|
|
31
70
|
if (seed !== null && seed !== undefined) {
|
|
32
71
|
const s = Math.trunc(Number(seed));
|
|
@@ -282,6 +321,11 @@ function findBinary(binary = null) {
|
|
|
282
321
|
|
|
283
322
|
module.exports = {
|
|
284
323
|
getDefaultStealthArgs,
|
|
324
|
+
IGNORE_DEFAULT_ARGS,
|
|
325
|
+
HEADLESS_SCREEN,
|
|
326
|
+
HEADLESS_VIEWPORT,
|
|
327
|
+
getHeadlessDefaultArgs,
|
|
328
|
+
hasScreenOverride,
|
|
285
329
|
newSeed,
|
|
286
330
|
findBinary,
|
|
287
331
|
searchBases,
|
package/lib/launch-common.js
CHANGED
|
@@ -39,9 +39,17 @@ function proxyAndGeoArgs(spec, geoipOn) {
|
|
|
39
39
|
return { launchOptions, args: out };
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
function buildArgs({ seed, platform, stealthArgs, timezone, locale, extensionPaths, extra
|
|
42
|
+
function buildArgs({ seed, platform, stealthArgs, timezone, locale, extensionPaths, extra,
|
|
43
|
+
headless = false }) {
|
|
43
44
|
let args = [];
|
|
44
|
-
if (stealthArgs)
|
|
45
|
+
if (stealthArgs) {
|
|
46
|
+
args = args.concat(config.getDefaultStealthArgs(seed, platform));
|
|
47
|
+
// Headless: pin the maximized-1080p-Windows screen/viewport (see config), unless the
|
|
48
|
+
// caller already set their own screen/window via extra args.
|
|
49
|
+
if (headless && !config.hasScreenOverride(extra)) {
|
|
50
|
+
args = args.concat(config.getHeadlessDefaultArgs());
|
|
51
|
+
}
|
|
52
|
+
}
|
|
45
53
|
if (timezone) args.push(`--timezone=${timezone}`); // capium in-binary tz spoof
|
|
46
54
|
if (locale) {
|
|
47
55
|
args.push(`--lang=${locale}`);
|
package/package.json
CHANGED
package/playwright.js
CHANGED
|
@@ -78,7 +78,7 @@ async function prepare(opts) {
|
|
|
78
78
|
seed = null, platform = 'windows', proxy = null, geoip = null, args = null,
|
|
79
79
|
stealthArgs = true, timezone = null, locale = null, extensionPaths = null,
|
|
80
80
|
binary = null, licenseKey = null, licenseServer = null,
|
|
81
|
-
licenseThroughProxy = false, licensePreflight = true,
|
|
81
|
+
licenseThroughProxy = false, licensePreflight = true, headless = false,
|
|
82
82
|
} = opts;
|
|
83
83
|
if (licensePreflight) license.preflight(licenseKey, licenseServer);
|
|
84
84
|
const { key } = license.effective(licenseKey, licenseServer);
|
|
@@ -87,6 +87,7 @@ async function prepare(opts) {
|
|
|
87
87
|
const { launchOptions, args: proxyArgs } = proxyAndGeoArgs(proxy, geoip);
|
|
88
88
|
const launchArgs = proxyArgs.concat(buildArgs({
|
|
89
89
|
seed: finalSeed, platform, stealthArgs, timezone, locale, extensionPaths, extra: args,
|
|
90
|
+
headless,
|
|
90
91
|
}));
|
|
91
92
|
if (licenseThroughProxy) launchArgs.push('--license-through-proxy');
|
|
92
93
|
const env = license.childEnv(licenseKey, licenseServer);
|
|
@@ -121,10 +122,11 @@ async function buildLaunchOptions(opts = {}) {
|
|
|
121
122
|
} = opts;
|
|
122
123
|
const prep = await prepare({
|
|
123
124
|
seed, platform, proxy, geoip, args, stealthArgs, timezone, locale, extensionPaths,
|
|
124
|
-
binary, licenseKey, licenseServer, licenseThroughProxy, licensePreflight,
|
|
125
|
+
binary, licenseKey, licenseServer, licenseThroughProxy, licensePreflight, headless,
|
|
125
126
|
});
|
|
126
127
|
return {
|
|
127
128
|
executablePath: prep.binPath, headless, args: prep.launchArgs, env: prep.env,
|
|
129
|
+
ignoreDefaultArgs: config.IGNORE_DEFAULT_ARGS, // strip automation + forced-software WebGL defaults
|
|
128
130
|
...prep.launchOptions, ...rest,
|
|
129
131
|
};
|
|
130
132
|
}
|
|
@@ -174,13 +176,14 @@ async function launch(opts = {}) {
|
|
|
174
176
|
} = opts;
|
|
175
177
|
const prep = await prepare({
|
|
176
178
|
seed, platform, proxy, geoip, args, stealthArgs, timezone, locale, extensionPaths,
|
|
177
|
-
binary, licenseKey, licenseServer, licenseThroughProxy, licensePreflight,
|
|
179
|
+
binary, licenseKey, licenseServer, licenseThroughProxy, licensePreflight, headless,
|
|
178
180
|
});
|
|
179
181
|
const pw = requireDriver();
|
|
180
182
|
let browser;
|
|
181
183
|
try {
|
|
182
184
|
browser = await pw.chromium.launch({
|
|
183
185
|
executablePath: prep.binPath, headless, args: prep.launchArgs, env: prep.env,
|
|
186
|
+
ignoreDefaultArgs: config.IGNORE_DEFAULT_ARGS, // strip automation + forced-software WebGL defaults
|
|
184
187
|
...prep.launchOptions, ...rest,
|
|
185
188
|
});
|
|
186
189
|
} catch (e) {
|
|
@@ -204,7 +207,7 @@ async function launchPersistentContext(userDataDir, opts = {}) {
|
|
|
204
207
|
} = opts;
|
|
205
208
|
const prep = await prepare({
|
|
206
209
|
seed, platform, proxy, geoip, args, stealthArgs, timezone, locale, extensionPaths,
|
|
207
|
-
binary, licenseKey, licenseServer, licenseThroughProxy, licensePreflight,
|
|
210
|
+
binary, licenseKey, licenseServer, licenseThroughProxy, licensePreflight, headless,
|
|
208
211
|
});
|
|
209
212
|
// Default to the page filling the window (see launchContext) unless the caller pinned a
|
|
210
213
|
// viewport themselves. viewport: null is Playwright-JS for "no fixed viewport".
|
|
@@ -214,6 +217,7 @@ async function launchPersistentContext(userDataDir, opts = {}) {
|
|
|
214
217
|
try {
|
|
215
218
|
ctx = await pw.chromium.launchPersistentContext(userDataDir, {
|
|
216
219
|
executablePath: prep.binPath, headless, args: prep.launchArgs, env: prep.env,
|
|
220
|
+
ignoreDefaultArgs: config.IGNORE_DEFAULT_ARGS, // strip automation + forced-software WebGL defaults
|
|
217
221
|
...prep.launchOptions, ...rest,
|
|
218
222
|
});
|
|
219
223
|
} catch (e) {
|
|
@@ -236,10 +240,12 @@ async function fitWindow(browser, context, page) {
|
|
|
236
240
|
if (!scr || !scr.w || !scr.h) return;
|
|
237
241
|
const aw = Math.trunc(scr.w);
|
|
238
242
|
const ah = Math.trunc(scr.h);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
const
|
|
243
|
+
// Size the window to the FULL per-seed screen (match python _fit_window_to_screen) so
|
|
244
|
+
// screen ~= viewport -- avoids the browserscan "screen != viewport" VM tell. NOT a forced 1440x900.
|
|
245
|
+
const w = aw;
|
|
246
|
+
const h = ah;
|
|
247
|
+
const left = 0;
|
|
248
|
+
const top = 0;
|
|
243
249
|
const pcdp = await context.newCDPSession(page);
|
|
244
250
|
const { targetInfo } = await pcdp.send('Target.getTargetInfo');
|
|
245
251
|
const bcdp = await browser.newBrowserCDPSession();
|
package/puppeteer.js
CHANGED
|
@@ -78,7 +78,7 @@ async function prepare(opts) {
|
|
|
78
78
|
seed = null, platform = 'windows', proxy = null, geoip = null, args = null,
|
|
79
79
|
stealthArgs = true, timezone = null, locale = null, extensionPaths = null,
|
|
80
80
|
binary = null, licenseKey = null, licenseServer = null,
|
|
81
|
-
licenseThroughProxy = false, licensePreflight = true,
|
|
81
|
+
licenseThroughProxy = false, licensePreflight = true, headless = false,
|
|
82
82
|
} = opts;
|
|
83
83
|
if (licensePreflight) license.preflight(licenseKey, licenseServer);
|
|
84
84
|
const { key } = license.effective(licenseKey, licenseServer);
|
|
@@ -87,6 +87,7 @@ async function prepare(opts) {
|
|
|
87
87
|
const { launchOptions, args: proxyArgs } = proxyAndGeoArgs(proxy, geoip);
|
|
88
88
|
const launchArgs = proxyArgs.concat(buildArgs({
|
|
89
89
|
seed: finalSeed, platform, stealthArgs, timezone, locale, extensionPaths, extra: args,
|
|
90
|
+
headless,
|
|
90
91
|
}));
|
|
91
92
|
if (licenseThroughProxy) launchArgs.push('--license-through-proxy');
|
|
92
93
|
const env = license.childEnv(licenseKey, licenseServer);
|
|
@@ -135,7 +136,7 @@ async function buildLaunchOptions(opts = {}) {
|
|
|
135
136
|
} = opts;
|
|
136
137
|
const prep = await prepare({
|
|
137
138
|
seed, platform, proxy, geoip, args, stealthArgs, timezone, locale, extensionPaths,
|
|
138
|
-
binary, licenseKey, licenseServer, licenseThroughProxy, licensePreflight,
|
|
139
|
+
binary, licenseKey, licenseServer, licenseThroughProxy, licensePreflight, headless,
|
|
139
140
|
});
|
|
140
141
|
const proxyOption = prep.launchOptions.proxy || null;
|
|
141
142
|
const launchArgs = [...prep.launchArgs];
|
|
@@ -149,7 +150,7 @@ async function buildLaunchOptions(opts = {}) {
|
|
|
149
150
|
args: launchArgs,
|
|
150
151
|
env: prep.env,
|
|
151
152
|
defaultViewport: 'defaultViewport' in rest ? rest.defaultViewport : null,
|
|
152
|
-
ignoreDefaultArgs: ignoreDefaultArgs === undefined ?
|
|
153
|
+
ignoreDefaultArgs: ignoreDefaultArgs === undefined ? config.IGNORE_DEFAULT_ARGS : ignoreDefaultArgs,
|
|
153
154
|
...rest,
|
|
154
155
|
};
|
|
155
156
|
}
|
|
@@ -167,7 +168,7 @@ async function launch(opts = {}) {
|
|
|
167
168
|
} = opts;
|
|
168
169
|
const prep = await prepare({
|
|
169
170
|
seed, platform, proxy, geoip, args, stealthArgs, timezone, locale, extensionPaths,
|
|
170
|
-
binary, licenseKey, licenseServer, licenseThroughProxy, licensePreflight,
|
|
171
|
+
binary, licenseKey, licenseServer, licenseThroughProxy, licensePreflight, headless,
|
|
171
172
|
});
|
|
172
173
|
const puppeteer = requireDriver();
|
|
173
174
|
// The CDP escape hatch puts {proxy: {server, username?, ...}} in launchOptions; Puppeteer
|
|
@@ -178,8 +179,8 @@ async function launch(opts = {}) {
|
|
|
178
179
|
launchArgs.push(`--proxy-server=${proxyOption.server}`);
|
|
179
180
|
if (proxyOption.bypass) launchArgs.push(`--proxy-bypass-list=${proxyOption.bypass}`);
|
|
180
181
|
}
|
|
181
|
-
// Strip
|
|
182
|
-
const ida = ignoreDefaultArgs === undefined ?
|
|
182
|
+
// Strip automation + forced-software WebGL defaults unless the caller took over ignoreDefaultArgs.
|
|
183
|
+
const ida = ignoreDefaultArgs === undefined ? config.IGNORE_DEFAULT_ARGS : ignoreDefaultArgs;
|
|
183
184
|
let browser;
|
|
184
185
|
try {
|
|
185
186
|
browser = await puppeteer.launch({
|
|
@@ -216,10 +217,12 @@ async function fitWindow(browser, page) {
|
|
|
216
217
|
if (!scr || !scr.w || !scr.h) return;
|
|
217
218
|
const aw = Math.trunc(scr.w);
|
|
218
219
|
const ah = Math.trunc(scr.h);
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
const
|
|
220
|
+
// Size the window to the FULL per-seed screen (match python _fit_window_to_screen) so
|
|
221
|
+
// screen ~= viewport -- avoids the browserscan "screen != viewport" VM tell. NOT a forced 1440x900.
|
|
222
|
+
const w = aw;
|
|
223
|
+
const h = ah;
|
|
224
|
+
const left = 0;
|
|
225
|
+
const top = 0;
|
|
223
226
|
const cdp = await page.createCDPSession();
|
|
224
227
|
const { targetInfo } = await cdp.send('Target.getTargetInfo');
|
|
225
228
|
const { windowId } = await cdp.send('Browser.getWindowForTarget',
|