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 CHANGED
@@ -1,35 +1,35 @@
1
1
  {
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-06",
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": 5,
18
- "sha256": "4e65cc764eda17a5ea9a70eafdda214fccac78b8ddf4de41a05addf036006f43",
19
- "published": true
20
- },
21
- "linux-x64": {
22
- "version": "153.0.8010.52",
23
- "revision": 4,
24
- "sha256": "5968821210bb54c7003637444dbf52ccfc93e06d229d21da240b8fc3cfd35d98",
25
- "published": true
26
- },
27
- "linux-arm64": {
28
- "version": "153.0.8010.52",
29
- "revision": 1,
30
- "sha256": null,
31
- "published": false
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: [width, height] for the spoofed screen AND the browser window, kept
26
- * equal so screen == window == viewport. Pass null to skip (rely on the
27
- * seed's device screen + fitWindow; only coherent on a real display).
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 = [1440, 900]) {
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,
@@ -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) args = args.concat(config.getDefaultStealthArgs(seed, platform));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capiumbrowser",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
4
4
  "description": "Drop-in stealth browser SDK on top of Playwright or Puppeteer (Capium Chromium).",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Capzy (https://capzy.ai)",
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
- const w = Math.min(aw, 1440);
240
- const h = Math.min(ah, 900);
241
- const left = Math.max(0, Math.floor((aw - w) / 5));
242
- const top = Math.max(0, Math.floor((ah - h) / 6));
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 ? ['--enable-automation'] : ignoreDefaultArgs,
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 Puppeteer's --enable-automation unless the caller took over ignoreDefaultArgs.
182
- const ida = ignoreDefaultArgs === undefined ? ['--enable-automation'] : ignoreDefaultArgs;
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
- const w = Math.min(aw, 1440);
220
- const h = Math.min(ah, 900);
221
- const left = Math.max(0, Math.floor((aw - w) / 5));
222
- const top = Math.max(0, Math.floor((ah - h) / 6));
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',