capiumbrowser 1.2.4__tar.gz → 1.3.1__tar.gz

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.
Files changed (45) hide show
  1. {capiumbrowser-1.2.4/capiumbrowser.egg-info → capiumbrowser-1.3.1}/PKG-INFO +2 -2
  2. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/README.md +1 -1
  3. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/_version.py +1 -1
  4. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/android-phones.json +64 -10
  5. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/browser.py +36 -10
  6. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/browser_async.py +16 -10
  7. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/channels.json +13 -13
  8. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/config.py +7 -6
  9. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/mobile.py +1 -1
  10. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1/capiumbrowser.egg-info}/PKG-INFO +2 -2
  11. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/pyproject.toml +1 -1
  12. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_geoip_flags.py +17 -6
  13. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/LICENSE +0 -0
  14. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/MANIFEST.in +0 -0
  15. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/__init__.py +0 -0
  16. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/__main__.py +0 -0
  17. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/_driver.py +0 -0
  18. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/browser-brands.json +0 -0
  19. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/desktop.py +0 -0
  20. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/errors.py +0 -0
  21. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/human/__init__.py +0 -0
  22. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/human/behavior.py +0 -0
  23. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/ios-phones.json +0 -0
  24. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/licensing/__init__.py +0 -0
  25. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/licensing/client.py +0 -0
  26. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/licensing/download.py +0 -0
  27. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/licensing/readiness.py +0 -0
  28. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/network/__init__.py +0 -0
  29. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser/network/proxy.py +0 -0
  30. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser.egg-info/SOURCES.txt +0 -0
  31. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser.egg-info/dependency_links.txt +0 -0
  32. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser.egg-info/entry_points.txt +0 -0
  33. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser.egg-info/requires.txt +0 -0
  34. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/capiumbrowser.egg-info/top_level.txt +0 -0
  35. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/setup.cfg +0 -0
  36. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_channels_manifest.py +0 -0
  37. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_config_args.py +0 -0
  38. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_desktop_brands.py +0 -0
  39. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_download_contract.py +0 -0
  40. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_ios_profiles.py +0 -0
  41. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_launch_viewport.py +0 -0
  42. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_license_readiness.py +0 -0
  43. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_mobile_profiles.py +0 -0
  44. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_proxy.py +0 -0
  45. {capiumbrowser-1.2.4 → capiumbrowser-1.3.1}/tests/test_version_tables_consistent.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: capiumbrowser
3
- Version: 1.2.4
3
+ Version: 1.3.1
4
4
  Summary: Drop-in stealth browser SDK on top of Playwright (Capium Chromium).
5
5
  Author: Capzy
6
6
  License: Capzy capiumbrowser SDK License
@@ -408,7 +408,7 @@ and `launch_persistent_context` forward everything to `launch`.
408
408
  | Parameter | Type | Default | Description |
409
409
  |---|---|---|---|
410
410
  | `proxy` | `str` \| `dict` \| `"env"`/`True` \| `None` | `None` | HTTP/HTTPS/SOCKS5 URL with inline creds, a `{"server","username","password"}` dict, `"env"`/`True` (reads `CAPIUM_PROXY`), or `None`/`"none"`/`""`. |
411
- | `geoip` | `bool` \| `None` | `None` | Geo coherence, resolved inside the binary. **`None` (default) — OFF**: no lookup runs, even with a proxy (a proxy alone doesn't trigger it, so proxied launches aren't slowed). **`True`** — ON: pins timezone/geo/WebRTC/`navigator.languages` to the egress (proxy exit, or the host IP if proxyless). **`False`** — explicit opt-out. No SDK-side probe. |
411
+ | `geoip` | `bool` \| `None` | `None` | Geo coherence, resolved inside the binary. **`None` (default) — ON whenever a proxy is set, OFF otherwise**: an uncorrected host timezone under a proxied identity (UTC on a server) is a far stronger detection signal than the lookup's one-off launch latency. **`True`** — ON even proxyless (pins timezone/geo/WebRTC/`navigator.languages` to the host IP). **`False`** — explicit opt-out. No SDK-side probe. |
412
412
  | `timezone` | `str` \| `None` | `None` | IANA tz (e.g. `"America/New_York"`) → `--timezone`. **Overrides** `geoip`. |
413
413
  | `locale` | `str` \| `None` | `None` | BCP-47 (e.g. `"en-US"`) → `--lang` + `--accept-lang`. **Overrides** `geoip`. |
414
414
 
@@ -345,7 +345,7 @@ and `launch_persistent_context` forward everything to `launch`.
345
345
  | Parameter | Type | Default | Description |
346
346
  |---|---|---|---|
347
347
  | `proxy` | `str` \| `dict` \| `"env"`/`True` \| `None` | `None` | HTTP/HTTPS/SOCKS5 URL with inline creds, a `{"server","username","password"}` dict, `"env"`/`True` (reads `CAPIUM_PROXY`), or `None`/`"none"`/`""`. |
348
- | `geoip` | `bool` \| `None` | `None` | Geo coherence, resolved inside the binary. **`None` (default) — OFF**: no lookup runs, even with a proxy (a proxy alone doesn't trigger it, so proxied launches aren't slowed). **`True`** — ON: pins timezone/geo/WebRTC/`navigator.languages` to the egress (proxy exit, or the host IP if proxyless). **`False`** — explicit opt-out. No SDK-side probe. |
348
+ | `geoip` | `bool` \| `None` | `None` | Geo coherence, resolved inside the binary. **`None` (default) — ON whenever a proxy is set, OFF otherwise**: an uncorrected host timezone under a proxied identity (UTC on a server) is a far stronger detection signal than the lookup's one-off launch latency. **`True`** — ON even proxyless (pins timezone/geo/WebRTC/`navigator.languages` to the host IP). **`False`** — explicit opt-out. No SDK-side probe. |
349
349
  | `timezone` | `str` \| `None` | `None` | IANA tz (e.g. `"America/New_York"`) → `--timezone`. **Overrides** `geoip`. |
350
350
  | `locale` | `str` \| `None` | `None` | BCP-47 (e.g. `"en-US"`) → `--lang` + `--accept-lang`. **Overrides** `geoip`. |
351
351
 
@@ -17,7 +17,7 @@ import json
17
17
  import os
18
18
  import platform as _platform
19
19
 
20
- __version__ = "1.2.4"
20
+ __version__ = "1.3.1"
21
21
 
22
22
  # Used only if channels.json can't be read (corrupt/removed install). ensure_binary() raises a
23
23
  # clear error long before the "unsupported platform" case matters; this keeps lookups total.
@@ -2,9 +2,9 @@
2
2
  "schema_version": 2,
3
3
  "minimum_release_year": 2022,
4
4
  "browser": "chrome",
5
- "status": "experimental-emulation",
6
- "gpu_policy": "host",
7
- "hardware_policy": "host",
5
+ "status": "catalog-emulation",
6
+ "gpu_policy": "catalog",
7
+ "hardware_policy": "catalog",
8
8
  "physical_device_validation": "pending",
9
9
  "default_profile_ids": [
10
10
  "pixel-7",
@@ -13,6 +13,20 @@
13
13
  "pixel-8a",
14
14
  "pixel-9"
15
15
  ],
16
+ "font_core": [
17
+ "Roboto",
18
+ "Noto Sans",
19
+ "Noto Serif",
20
+ "Noto Sans Mono",
21
+ "Noto Color Emoji",
22
+ "Noto Sans JP",
23
+ "Noto Sans KR",
24
+ "Noto Sans SC",
25
+ "Noto Sans TC",
26
+ "Noto Naskh Arabic",
27
+ "Noto Sans Devanagari",
28
+ "Noto Sans Thai"
29
+ ],
16
30
  "sources": [
17
31
  "https://support.google.com/pixelphone/answer/7158570?hl=en",
18
32
  "https://news.samsung.com/global/samsung-galaxy-a55-5g-and-galaxy-a35-5g-awesome-innovations-and-security-engineered-for-everyone",
@@ -27,7 +41,12 @@
27
41
  "platform_version": "13",
28
42
  "width": 412,
29
43
  "height": 915,
30
- "dpr": 2.625
44
+ "dpr": 2.625,
45
+ "soc": "Tensor G2",
46
+ "cores": 8,
47
+ "memory_gb": 8,
48
+ "gpu_vendor": "Google Inc. (ARM)",
49
+ "gpu_renderer": "ANGLE (ARM, Mali-G710, OpenGL ES 3.2)"
31
50
  },
32
51
  {
33
52
  "id": "pixel-8",
@@ -36,7 +55,12 @@
36
55
  "platform_version": "14",
37
56
  "width": 412,
38
57
  "height": 915,
39
- "dpr": 2.625
58
+ "dpr": 2.625,
59
+ "soc": "Tensor G3",
60
+ "cores": 9,
61
+ "memory_gb": 8,
62
+ "gpu_vendor": "Google Inc. (ARM)",
63
+ "gpu_renderer": "ANGLE (ARM, Mali-G715, OpenGL ES 3.2)"
40
64
  },
41
65
  {
42
66
  "id": "pixel-8-pro",
@@ -45,7 +69,12 @@
45
69
  "platform_version": "14",
46
70
  "width": 448,
47
71
  "height": 997,
48
- "dpr": 3.0
72
+ "dpr": 3.0,
73
+ "soc": "Tensor G3",
74
+ "cores": 9,
75
+ "memory_gb": 12,
76
+ "gpu_vendor": "Google Inc. (ARM)",
77
+ "gpu_renderer": "ANGLE (ARM, Mali-G715, OpenGL ES 3.2)"
49
78
  },
50
79
  {
51
80
  "id": "pixel-8a",
@@ -54,7 +83,12 @@
54
83
  "platform_version": "14",
55
84
  "width": 412,
56
85
  "height": 915,
57
- "dpr": 2.625
86
+ "dpr": 2.625,
87
+ "soc": "Tensor G3",
88
+ "cores": 9,
89
+ "memory_gb": 8,
90
+ "gpu_vendor": "Google Inc. (ARM)",
91
+ "gpu_renderer": "ANGLE (ARM, Mali-G715, OpenGL ES 3.2)"
58
92
  },
59
93
  {
60
94
  "id": "pixel-9",
@@ -63,7 +97,12 @@
63
97
  "platform_version": "14",
64
98
  "width": 412,
65
99
  "height": 924,
66
- "dpr": 2.625
100
+ "dpr": 2.625,
101
+ "soc": "Tensor G4",
102
+ "cores": 8,
103
+ "memory_gb": 12,
104
+ "gpu_vendor": "Google Inc. (ARM)",
105
+ "gpu_renderer": "ANGLE (ARM, Mali-G715, OpenGL ES 3.2)"
67
106
  },
68
107
  {
69
108
  "id": "samsung-galaxy-a55",
@@ -74,6 +113,11 @@
74
113
  "width": 360,
75
114
  "height": 800,
76
115
  "dpr": 2.25,
116
+ "soc": "Exynos 1480",
117
+ "cores": 8,
118
+ "memory_gb": 8,
119
+ "gpu_vendor": "Google Inc. (Samsung Electronics Co., Ltd.)",
120
+ "gpu_renderer": "ANGLE (Samsung Electronics Co., Ltd., Xclipse 530, OpenGL ES 3.2)",
77
121
  "geometry_status": "devtools-emulation-only",
78
122
  "geometry_note": "These Chromium preset dimensions/DPR do not directly map to the published 1080x2340 panel; physical Chrome capture required."
79
123
  },
@@ -84,7 +128,12 @@
84
128
  "platform_version": "14",
85
129
  "width": 427,
86
130
  "height": 952,
87
- "dpr": 3.0
131
+ "dpr": 3.0,
132
+ "soc": "Tensor G4",
133
+ "cores": 8,
134
+ "memory_gb": 16,
135
+ "gpu_vendor": "Google Inc. (ARM)",
136
+ "gpu_renderer": "ANGLE (ARM, Mali-G715, OpenGL ES 3.2)"
88
137
  },
89
138
  {
90
139
  "id": "pixel-9-pro-xl",
@@ -93,7 +142,12 @@
93
142
  "platform_version": "14",
94
143
  "width": 448,
95
144
  "height": 997,
96
- "dpr": 3.0
145
+ "dpr": 3.0,
146
+ "soc": "Tensor G4",
147
+ "cores": 8,
148
+ "memory_gb": 16,
149
+ "gpu_vendor": "Google Inc. (ARM)",
150
+ "gpu_renderer": "ANGLE (ARM, Mali-G715, OpenGL ES 3.2)"
97
151
  }
98
152
  ]
99
153
  }
@@ -90,19 +90,22 @@ def _proxy_and_geo_args(spec, binpath, geoip_on, display):
90
90
  would otherwise add up to 2.5s to every proxied launch. When it does run it resolves the
91
91
  egress IP (proxy exit, else the host's real public IP) and pins WebRTC / timezone /
92
92
  geolocation / language -- so the SDK adds no probe of its own. geoip_on is tri-state:
93
- * None -> default: add no flag, so NO geo lookup runs (no launch latency) even with a
94
- proxy. Pass geoip=True to opt into proxy-exit coherence.
93
+ * None -> default: ON whenever a proxy is set, OFF otherwise. An uncorrected host
94
+ timezone under a proxied identity (UTC on a server, or the operator's home
95
+ zone against a foreign exit) is a far stronger detection signal than the
96
+ one-off launch-latency cost of the lookup. Pass geoip=False to opt out.
95
97
  * True -> ON with --geoip (resolves the egress -- proxy exit, or the host IP if proxyless).
96
98
  * False -> OFF with --geoip=false, which the binary honors FIRST (explicit opt-out).
97
99
  Returns (proxy_kwargs, [flags]).
98
100
  """
99
101
  proxy_kwargs, out = _proxy.resolve_proxy_config(spec)
100
102
  out = list(out)
103
+ explicit_off = geoip_on is False
101
104
  if geoip_on is None:
102
- pass
103
- elif geoip_on:
105
+ geoip_on = bool(spec)
106
+ if geoip_on:
104
107
  out.append("--geoip")
105
- else:
108
+ elif explicit_off:
106
109
  out.append("--geoip=false")
107
110
  return proxy_kwargs, out
108
111
 
@@ -163,14 +166,34 @@ def fit_window(browser, context, page):
163
166
  pass
164
167
 
165
168
 
169
+ def _window_bounds_for_seed(seed, aw, ah):
170
+ """Deterministic per-seed window geometry inside the seed's available screen area.
171
+
172
+ Every profile previously got the identical maximized-like window (exactly availWidth x
173
+ availHeight at 0,0), so all profiles sharing a pool screen were geometry-identical -- a
174
+ clusterable signature. Real users are a mix: most keep the browser maximized, a minority
175
+ run a windowed browser at an arbitrary size/position. Mirror that mix deterministically
176
+ per seed (the geometry is part of the profile's stable identity, like its screen)."""
177
+ import hashlib
178
+ d = hashlib.sha256(b"capium-window:%d" % int(seed)).digest()
179
+ if d[0] % 10 < 6: # most seeds: maximized-like (previous behavior)
180
+ return 0, 0, aw, ah
181
+ w = max(800, int(aw * (0.78 + (d[1] / 255.0) * 0.16))) # 78-94% of avail width
182
+ h = max(600, int(ah * (0.80 + (d[2] / 255.0) * 0.15))) # 80-95% of avail height
183
+ left = (aw - w) * d[3] // 255
184
+ top = (ah - h) * d[4] // 255
185
+ return int(left), int(top), int(w), int(h)
186
+
187
+
166
188
  def _fit_window_to_screen(context):
167
189
  """Size the window to the seed's (coherent, per-seed) spoofed screen so viewport ~= screen.
168
190
 
169
191
  With screen=None (the default), each persona uses its device-pool screen/dpr. Here we size the
170
- window to that screen's available area (a maximized-like window), via the page's CDP session
171
- (works for persistent contexts, which have no browser() object). This keeps screen~=viewport in
172
- headless/container (avoids the browserscan 'screen != viewport' VM tell) AND stays coherent in
173
- headed (Chrome clamps to the real display -> a normal viewport<screen window). Best-effort."""
192
+ window within that screen's available area -- maximized-like for most seeds, realistically
193
+ windowed for the rest (see _window_bounds_for_seed) -- via the page's CDP session
194
+ (works for persistent contexts, which have no browser() object). This keeps viewport<=screen
195
+ coherent in headless/container (avoids the browserscan 'screen != viewport' VM tell) AND stays
196
+ coherent in headed (Chrome clamps to the real display). Best-effort."""
174
197
  s = None
175
198
  try:
176
199
  page = context.pages[0] if context.pages else None
@@ -180,10 +203,12 @@ def _fit_window_to_screen(context):
180
203
  if not (scr and scr.get("w") and scr.get("h")):
181
204
  return
182
205
  aw, ah = int(scr["w"]), int(scr["h"])
206
+ left, top, w, h = _window_bounds_for_seed(
207
+ getattr(context, "_capium_seed", 0) or 0, aw, ah)
183
208
  s = context.new_cdp_session(page)
184
209
  wid = s.send("Browser.getWindowForTarget")["windowId"]
185
210
  s.send("Browser.setWindowBounds", {"windowId": wid, "bounds": {
186
- "left": 0, "top": 0, "width": aw, "height": ah, "windowState": "normal"}})
211
+ "left": left, "top": top, "width": w, "height": h, "windowState": "normal"}})
187
212
  except Exception:
188
213
  pass
189
214
  finally:
@@ -470,6 +495,7 @@ def launch_context(seed=None, platform="windows", headless=False, url=None,
470
495
  _human.humanize(context, preset=human_preset)
471
496
  page = context.pages[0] if context.pages else context.new_page()
472
497
  if not getattr(browser, "_capium_mobile", False):
498
+ context._capium_seed = getattr(browser, "_capium_seed", 0)
473
499
  if headless:
474
500
  _fit_window_to_screen(context) # per-seed screen fit (coherent viewport, not the tiny startup window)
475
501
  else:
@@ -33,17 +33,18 @@ async def _license_preflight_async(license_key, license_server, do_preflight):
33
33
  async def _proxy_and_geo_args_async(spec, binpath, geoip_on, display):
34
34
  """Return (proxy_kwargs, chrome_args). proxy_kwargs for Playwright auth; chrome_args for
35
35
  native --proxy-server + geo flags. The binary's CapiumGeoipResolver runs at PreBrowserStart
36
- ONLY when --geoip is present (a proxy alone does NOT trigger it -- the lookup is a blocking
37
- round-trip we don't pay unasked). geoip_on is tri-state: None = default OFF (no lookup, no
38
- launch latency, even with a proxy), True = ON (--geoip), False = explicit opt-out
39
- (--geoip=false, honored first)."""
36
+ ONLY when --geoip is present. geoip_on is tri-state: None = default ON whenever a proxy is
37
+ set and OFF otherwise (an uncorrected host timezone under a proxied identity -- UTC on a
38
+ server -- is a far stronger detection signal than the lookup's launch latency), True = ON
39
+ (--geoip), False = explicit opt-out (--geoip=false, honored first)."""
40
40
  proxy_kwargs, out = _proxy.resolve_proxy_config(spec)
41
41
  out = list(out)
42
+ explicit_off = geoip_on is False
42
43
  if geoip_on is None:
43
- pass
44
- elif geoip_on:
44
+ geoip_on = bool(spec)
45
+ if geoip_on:
45
46
  out.append("--geoip")
46
- else:
47
+ elif explicit_off:
47
48
  out.append("--geoip=false")
48
49
  return proxy_kwargs, out
49
50
 
@@ -164,18 +165,22 @@ async def _require_mobile_engine_async(context, mobile_device=None, seed=0, plat
164
165
 
165
166
 
166
167
  async def _fit_window_to_screen_async(context):
167
- """Fit a headed desktop window to its available screen without emulation."""
168
+ """Fit a desktop window within its available screen (per-seed geometry, no emulation)."""
169
+ from .browser import _window_bounds_for_seed
168
170
  session = None
169
171
  try:
170
172
  page = context.pages[0] if context.pages else await context.new_page()
171
173
  screen = await page.evaluate("()=>({w:screen.availWidth,h:screen.availHeight})")
172
174
  if not (screen and screen.get("w") and screen.get("h")):
173
175
  return
176
+ left, top, w, h = _window_bounds_for_seed(
177
+ getattr(context, "_capium_seed", 0) or 0,
178
+ int(screen["w"]), int(screen["h"]))
174
179
  session = await context.new_cdp_session(page)
175
180
  window_id = (await session.send("Browser.getWindowForTarget"))["windowId"]
176
181
  await session.send("Browser.setWindowBounds", {"windowId": window_id, "bounds": {
177
- "left": 0, "top": 0, "width": int(screen["w"]),
178
- "height": int(screen["h"]), "windowState": "normal"}})
182
+ "left": left, "top": top, "width": w,
183
+ "height": h, "windowState": "normal"}})
179
184
  except Exception:
180
185
  pass
181
186
  finally:
@@ -281,6 +286,7 @@ async def launch_context_async(seed=None, platform="windows", headless=False, ur
281
286
  _human.humanize(context, preset=human_preset)
282
287
  page = context.pages[0] if context.pages else await context.new_page()
283
288
  if not getattr(browser, "_capium_mobile", False):
289
+ context._capium_seed = getattr(browser, "_capium_seed", 0)
284
290
  await _fit_window_to_screen_async(context)
285
291
  if url:
286
292
  await page.goto(url, wait_until="domcontentloaded", timeout=60000)
@@ -1,36 +1,36 @@
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 reported Chromium version (what the browser reports to the customer, names the R2 folder); `engine_version` is the actual built Chromium engine (box_package verifies the binary against it) -- it differs from `version` when a platform reports a spoofed patch (Windows reports .85 on the .84 engine). `revision` is our packaging counter -- the served R2 folder is chromium-v<version>.<revision>/, ALWAYS dotted-suffixed starting at 1. A Chromium version bump resets revision to 1. `published: false` means the tag is declared but no artifact exists yet.",
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 reported Chromium version (what the browser reports to the customer, names the R2 folder); `engine_version` is the actual built Chromium engine (box_package verifies the binary against it) -- it differs from `version` when a platform reports a spoofed patch (Windows/macOS report .48 on the .47 engine). `revision` is our packaging counter -- the served R2 folder is chromium-v<version>.<revision>/, ALWAYS dotted-suffixed starting at 1. A Chromium version bump resets revision to 1. `published: false` means the tag is declared but no artifact exists yet.",
3
3
  "schema": 1,
4
- "updated": "2026-10-21",
4
+ "updated": "2026-10-24",
5
5
  "channels": {
6
6
  "stable": {
7
7
  "windows-x64": {
8
- "version": "155.0.8059.85",
9
- "engine_version": "155.0.8059.84",
8
+ "version": "156.0.8078.48",
9
+ "engine_version": "156.0.8078.47",
10
10
  "revision": 2,
11
- "sha256": "905bb081d5234338237b6a4c3df7ef3fbb8d20d0049adde982027494b87e4ed2",
11
+ "sha256": "76fa0d609f3d3fcac05436681884b3150405aaa470730d9a9ad1453ff6ad8276",
12
12
  "published": true,
13
13
  "license_status_protocol": 1
14
14
  },
15
15
  "macos-arm64": {
16
- "version": "155.0.8059.84",
17
- "engine_version": "155.0.8059.84",
16
+ "version": "156.0.8078.48",
17
+ "engine_version": "156.0.8078.47",
18
18
  "revision": 2,
19
- "sha256": "5c4ab089d7925a3b86fd891cfbe9de95777c46fc7616990a94b15b891c89775f",
19
+ "sha256": "3b883146c0f41fcb409113d3f43764b7e7061bb6c64d58cddb26b2619887275f",
20
20
  "published": true,
21
21
  "license_status_protocol": 1
22
22
  },
23
23
  "linux-x64": {
24
- "version": "155.0.8059.84",
25
- "engine_version": "155.0.8059.84",
24
+ "version": "156.0.8078.47",
25
+ "engine_version": "156.0.8078.47",
26
26
  "revision": 2,
27
- "sha256": "e9e18dd9f23baa500e1be0a7075096f6e6c8fb3a37150c96bd96fd4e58518880",
27
+ "sha256": "5fc9954366d8052e09d8f528d1cdbcedcf6d0cb1ae43c36441ce975a1e60c749",
28
28
  "published": true,
29
29
  "license_status_protocol": 1
30
30
  },
31
31
  "linux-arm64": {
32
- "version": "155.0.8059.84",
33
- "engine_version": "155.0.8059.84",
32
+ "version": "156.0.8078.47",
33
+ "engine_version": "156.0.8078.47",
34
34
  "revision": 1,
35
35
  "sha256": null,
36
36
  "published": false
@@ -141,12 +141,13 @@ def get_default_stealth_args(seed=None, platform="windows", screen=None, mobile_
141
141
  "primaryPointerType=4,availablePointerTypes=4",
142
142
  "--no-first-run",
143
143
  "--no-default-browser-check",
144
- # Engine 153 redirects main-world evaluations by default. Development
145
- # 155 honors the requested context and keeps console masking independent:
146
- # --capium-isolate-cdp-eval opts into legacy isolation;
147
- # --capium-disable-console-mask restores page/worker console delivery;
148
- # --capium-disable-cdp-stealth disables both policies on either engine.
149
- # Published 1.2.0 selects engine 155.
144
+ # CDP stealth defaults (engine 156): inspector-frame scrub ON (hides the
145
+ # UtilityScript/eval source-URL leak from Error.stack -- Patchright-equivalent,
146
+ # does not trip anti-detect ML); console mask OFF (ON makes developer_tools=false
147
+ # -> FPJS anti_detect/tampering) -- opt in with --capium-enable-console-mask; main-
148
+ # world evaluate isolation OFF (ON breaks page-global access for solvers) -- opt in
149
+ # with --capium-isolate-cdp-eval. --capium-disable-cdp-stealth turns everything off.
150
+ # See docs/STEALTH-FLAGS.md.
150
151
  ]
151
152
  # Per-profile render farbling is ON (the engine default) so canvas/WebGL/audio are unique AND
152
153
  # stable per seed. Without it, every profile on a host renders identically -- a cross-instance
@@ -15,7 +15,7 @@ _IOS_PROFILES = {profile["id"]: profile for profile in _IOS_CATALOG["profiles"]}
15
15
  # No network request is sent. A secure origin exposes native UA Client Hints.
16
16
  ENGINE_PROBE = r"""async expected => {
17
17
  const match = navigator.userAgent.match(/Chrome\/(\d+)\.0\.0\.0 Mobile Safari\//);
18
- if (!expected || navigator.platform !== 'Linux armv81' || !match ||
18
+ if (!expected || navigator.platform !== 'Linux armv8l' || !match ||
19
19
  !navigator.userAgent.includes('(Linux; Android 10; K)') ||
20
20
  navigator.userAgent.includes('Version/4.0') || !navigator.userAgentData) return false;
21
21
  const ch = await navigator.userAgentData.getHighEntropyValues([
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: capiumbrowser
3
- Version: 1.2.4
3
+ Version: 1.3.1
4
4
  Summary: Drop-in stealth browser SDK on top of Playwright (Capium Chromium).
5
5
  Author: Capzy
6
6
  License: Capzy capiumbrowser SDK License
@@ -408,7 +408,7 @@ and `launch_persistent_context` forward everything to `launch`.
408
408
  | Parameter | Type | Default | Description |
409
409
  |---|---|---|---|
410
410
  | `proxy` | `str` \| `dict` \| `"env"`/`True` \| `None` | `None` | HTTP/HTTPS/SOCKS5 URL with inline creds, a `{"server","username","password"}` dict, `"env"`/`True` (reads `CAPIUM_PROXY`), or `None`/`"none"`/`""`. |
411
- | `geoip` | `bool` \| `None` | `None` | Geo coherence, resolved inside the binary. **`None` (default) — OFF**: no lookup runs, even with a proxy (a proxy alone doesn't trigger it, so proxied launches aren't slowed). **`True`** — ON: pins timezone/geo/WebRTC/`navigator.languages` to the egress (proxy exit, or the host IP if proxyless). **`False`** — explicit opt-out. No SDK-side probe. |
411
+ | `geoip` | `bool` \| `None` | `None` | Geo coherence, resolved inside the binary. **`None` (default) — ON whenever a proxy is set, OFF otherwise**: an uncorrected host timezone under a proxied identity (UTC on a server) is a far stronger detection signal than the lookup's one-off launch latency. **`True`** — ON even proxyless (pins timezone/geo/WebRTC/`navigator.languages` to the host IP). **`False`** — explicit opt-out. No SDK-side probe. |
412
412
  | `timezone` | `str` \| `None` | `None` | IANA tz (e.g. `"America/New_York"`) → `--timezone`. **Overrides** `geoip`. |
413
413
  | `locale` | `str` \| `None` | `None` | BCP-47 (e.g. `"en-US"`) → `--lang` + `--accept-lang`. **Overrides** `geoip`. |
414
414
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "capiumbrowser"
7
- version = "1.2.4"
7
+ version = "1.3.1"
8
8
  description = "Drop-in stealth browser SDK on top of Playwright (Capium Chromium)."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -1,7 +1,10 @@
1
1
  """Unit tests for the SDK geoip flag tri-state (capiumbrowser.browser._proxy_and_geo_args).
2
2
 
3
- Regression for the 2026-09 contract change: a proxy ALONE must NOT enable geoip (that added a
4
- blocking egress round-trip to every proxied launch). --geoip is the sole trigger now.
3
+ Contract as of 1.3.1: geoip=None (the default) enables --geoip WHENEVER A PROXY IS SET and
4
+ stays off otherwise. An uncorrected host timezone under a proxied identity (UTC on a server
5
+ against a residential exit) is a far stronger detection signal than the lookup's one-off
6
+ launch latency, which is why the 2026-09 "default always off" contract was reversed.
7
+ Explicit False still opts out with --geoip=false (honored first by the binary).
5
8
  """
6
9
  import asyncio
7
10
 
@@ -14,11 +17,12 @@ def _flags(spec, geoip):
14
17
  return out
15
18
 
16
19
 
17
- def test_default_none_adds_no_geoip_even_with_proxy():
20
+ def test_default_none_enables_geoip_with_proxy():
18
21
  out = _flags("http://u:p@h:1", geoip=None)
19
22
  assert any(a.startswith("--proxy-server=") for a in out), "proxy flag expected"
20
- assert not any(a == "--geoip" or a.startswith("--geoip=") for a in out), \
21
- "a proxy alone must NOT enable geoip (default is OFF)"
23
+ assert "--geoip" in out, \
24
+ "a proxy must enable geoip by default (exit-coherent timezone/geo/WebRTC)"
25
+ assert not any(a.startswith("--geoip=") for a in out)
22
26
 
23
27
 
24
28
  def test_true_adds_geoip():
@@ -26,9 +30,15 @@ def test_true_adds_geoip():
26
30
  assert "--geoip" in out
27
31
 
28
32
 
33
+ def test_true_without_proxy_adds_geoip():
34
+ out = _flags(None, geoip=True)
35
+ assert "--geoip" in out
36
+
37
+
29
38
  def test_false_adds_explicit_optout():
30
39
  out = _flags("http://u:p@h:1", geoip=False)
31
40
  assert "--geoip=false" in out
41
+ assert "--geoip" not in out
32
42
 
33
43
 
34
44
  def test_no_proxy_no_geoip_by_default():
@@ -42,5 +52,6 @@ def test_async_mirror_matches_sync():
42
52
  geoip_on=geoip, display=None)
43
53
  return out
44
54
  assert "--geoip" in asyncio.run(_run("http://u:p@h:1", True))
45
- assert not any(a.startswith("--geoip") for a in asyncio.run(_run("http://u:p@h:1", None)))
55
+ assert "--geoip" in asyncio.run(_run("http://u:p@h:1", None))
56
+ assert not any(a.startswith("--geoip") for a in asyncio.run(_run(None, None)))
46
57
  assert "--geoip=false" in asyncio.run(_run(None, False))
File without changes
File without changes
File without changes