Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 27 additions & 34 deletions aimlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,34 +297,29 @@ def _validated_footprint_identity(value: object, applied_footprint: str) -> Opti
}


def _device_metrics_override(identity: Optional[dict]) -> Optional[dict]:
"""Convert physical display pixels into the target's portrait CSS viewport."""
def _expected_device_metrics(identity: Optional[dict]) -> Optional[dict]:
"""Convert the OS-applied physical display into expected portrait CSS metrics."""
if identity is None:
return None
ratio = identity["device_pixel_ratio"]
css_width = math.ceil(identity["screen_width"] / ratio)
css_height = math.ceil(identity["screen_height"] / ratio)
return {
"width": css_width,
"height": css_height,
"deviceScaleFactor": ratio,
"mobile": True,
"screenWidth": css_width,
"screenHeight": css_height,
"positionX": 0,
"positionY": 0,
"screenOrientation": {"type": "portraitPrimary", "angle": 0},
"devicePixelRatio": ratio,
}


def _identity_verification(identity: Optional[dict], metrics: Optional[dict]) -> Optional[dict]:
def _identity_verification(identity: Optional[dict]) -> Optional[dict]:
metrics = _expected_device_metrics(identity)
if identity is None or metrics is None:
return None
return {
"model": identity["model"],
"screenWidth": metrics["screenWidth"],
"screenHeight": metrics["screenHeight"],
"devicePixelRatio": metrics["deviceScaleFactor"],
"devicePixelRatio": metrics["devicePixelRatio"],
}


Expand Down Expand Up @@ -373,10 +368,14 @@ def _identity_verification(identity: Optional[dict], metrics: Optional[dict]) ->
const branded = data.brands.some(row => row.brand === 'Google Chrome') &&
high.fullVersionList.some(row => row.brand === 'Google Chrome');
if (!branded || !expected) return branded;
const headfulInsets = screen.availHeight < screen.height &&
innerHeight < screen.availHeight &&
visualViewport && visualViewport.height > 0 && visualViewport.height <= innerHeight;
return high.model === expected.model &&
screen.width === expected.screenWidth &&
screen.height === expected.screenHeight &&
Math.abs(devicePixelRatio - expected.devicePixelRatio) < 0.001;
Math.abs(devicePixelRatio - expected.devicePixelRatio) < 0.001 &&
headfulInsets;
}"""


Expand Down Expand Up @@ -746,24 +745,23 @@ async def _apply_page_identity(self, page):
identity,
footprint_identity["model"] if footprint_identity else None,
)
metrics = _device_metrics_override(footprint_identity)
if override is not None or metrics is not None:
expected = _identity_verification(footprint_identity)
if override is not None:
cdp_session = await page.context.new_cdp_session(page)
if override is not None:
await cdp_session.send("Emulation.setUserAgentOverride", override)
if metrics is not None:
await cdp_session.send("Emulation.setDeviceMetricsOverride", metrics)
await cdp_session.send("Emulation.setUserAgentOverride", override)
if override is not None or expected is not None:
verified = await page.evaluate(
_VERIFY_MANAGED_BROWSER_IDENTITY,
_identity_verification(footprint_identity, metrics),
expected,
)
# Internal/about:blank startup pages cannot expose UA-CH. The override has already
# been validated on the secure bootstrap tab below; a definitive false result on a
# page that does expose UA-CH still fails closed.
if verified is False:
raise BrowserPolicyError("managed browser identity did not apply")
self._identity_cdp_sessions.append(cdp_session)
cdp_session = None
if cdp_session is not None:
self._identity_cdp_sessions.append(cdp_session)
cdp_session = None
self._identity_pages.add(page_key)
except BrowserPolicyError:
raise
Expand Down Expand Up @@ -796,24 +794,19 @@ async def _native_identity_for_page(self, page, footprint_identity: Optional[dic
identity,
footprint_identity["model"] if footprint_identity else None,
)
metrics = _device_metrics_override(footprint_identity)
if override is not None or metrics is not None:
expected = _identity_verification(footprint_identity)
if override is not None:
bootstrap_cdp_session = await page.context.new_cdp_session(
bootstrap_page
)
if override is not None:
await bootstrap_cdp_session.send(
"Emulation.setUserAgentOverride",
override,
)
if metrics is not None:
await bootstrap_cdp_session.send(
"Emulation.setDeviceMetricsOverride",
metrics,
)
await bootstrap_cdp_session.send(
"Emulation.setUserAgentOverride",
override,
)
if override is not None or expected is not None:
verified = await bootstrap_page.evaluate(
_VERIFY_MANAGED_BROWSER_IDENTITY,
_identity_verification(footprint_identity, metrics),
expected,
)
if verified is not True:
raise BrowserPolicyError(
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "aimlib"
version = "0.4.3"
version = "0.4.4"
description = "Python SDK for aimlib mobile proxies and remote-browser sessions"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
41 changes: 13 additions & 28 deletions tests/test_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
_Operations,
_apply_browser_driver_policy,
_async_playwright,
_device_metrics_override,
_expected_device_metrics,
_google_chrome_user_agent_override,
_raise_for_typed,
_ttl_seconds,
_validated_footprint_identity,
_VERIFY_MANAGED_BROWSER_IDENTITY,
)


Expand Down Expand Up @@ -222,7 +223,7 @@ def test_rewrites_model_even_when_google_chrome_is_already_branded(self):
self.assertEqual(override["userAgentMetadata"]["model"], "Pixel 6 Pro")
self.assertEqual(override["userAgent"], identity["userAgent"])

def test_validates_public_identity_and_builds_pixel_6_pro_metrics(self):
def test_validates_public_identity_and_builds_pixel_6_pro_expectations(self):
profile = _validated_footprint_identity(
{
"name": "pixel-6-pro",
Expand All @@ -235,20 +236,19 @@ def test_validates_public_identity_and_builds_pixel_6_pro_metrics(self):
)

self.assertEqual(
_device_metrics_override(profile),
_expected_device_metrics(profile),
{
"width": 412,
"height": 892,
"deviceScaleFactor": 3.5,
"mobile": True,
"screenWidth": 412,
"screenHeight": 892,
"positionX": 0,
"positionY": 0,
"screenOrientation": {"type": "portraitPrimary", "angle": 0},
"devicePixelRatio": 3.5,
},
)

def test_footprint_verification_requires_headful_browser_insets(self):
self.assertIn("screen.availHeight < screen.height", _VERIFY_MANAGED_BROWSER_IDENTITY)
self.assertIn("innerHeight < screen.availHeight", _VERIFY_MANAGED_BROWSER_IDENTITY)
self.assertIn("visualViewport.height <= innerHeight", _VERIFY_MANAGED_BROWSER_IDENTITY)

def test_rejects_missing_or_mismatched_selected_identity(self):
for profile in ({}, {"name": "pixel-6a"}):
with self.subTest(profile=profile), self.assertRaises(BrowserPolicyError):
Expand Down Expand Up @@ -373,7 +373,7 @@ async def test_does_not_open_a_cdp_session_for_official_chrome(self):

page.context.new_cdp_session.assert_not_awaited()

async def test_applies_selected_model_and_display_metrics_to_the_target(self):
async def test_applies_selected_model_and_validates_os_display_metrics(self):
session = session_for(MagicMock())
session.applied_footprint = "pixel-6-pro"
session.fingerprint = {
Expand All @@ -394,28 +394,13 @@ async def test_applies_selected_model_and_display_metrics_to_the_target(self):

await session._apply_page_identity(page)

self.assertEqual(cdp_session.send.await_count, 2)
ua_call, metrics_call = cdp_session.send.await_args_list
self.assertEqual(cdp_session.send.await_count, 1)
ua_call = cdp_session.send.await_args
self.assertEqual(ua_call.args[0], "Emulation.setUserAgentOverride")
self.assertEqual(
ua_call.args[1]["userAgentMetadata"]["model"],
"Pixel 6 Pro",
)
self.assertEqual(metrics_call.args[0], "Emulation.setDeviceMetricsOverride")
self.assertEqual(
metrics_call.args[1],
{
"width": 412,
"height": 892,
"deviceScaleFactor": 3.5,
"mobile": True,
"screenWidth": 412,
"screenHeight": 892,
"positionX": 0,
"positionY": 0,
"screenOrientation": {"type": "portraitPrimary", "angle": 0},
},
)
_, expected = page.evaluate.await_args.args
self.assertEqual(
expected,
Expand Down