|
22 | 22 | from sentience._extension_loader import find_extension_path |
23 | 23 | from sentience.constants import SENTIENCE_API_URL |
24 | 24 | from sentience.models import ProxyConfig, StorageState, Viewport |
| 25 | +from sentience.permissions import PermissionPolicy |
25 | 26 |
|
26 | 27 | logger = logging.getLogger(__name__) |
27 | 28 |
|
@@ -97,6 +98,7 @@ def __init__( |
97 | 98 | allowed_domains: list[str] | None = None, |
98 | 99 | prohibited_domains: list[str] | None = None, |
99 | 100 | keep_alive: bool = False, |
| 101 | + permission_policy: PermissionPolicy | dict | None = None, |
100 | 102 | ): |
101 | 103 | """ |
102 | 104 | Initialize Sentience browser |
@@ -134,6 +136,7 @@ def __init__( |
134 | 136 | Viewport(width=1920, height=1080) (Full HD) |
135 | 137 | {"width": 1280, "height": 800} (dict also supported) |
136 | 138 | If None, defaults to Viewport(width=1280, height=800). |
| 139 | + permission_policy: Optional permission policy to apply on context creation. |
137 | 140 | """ |
138 | 141 | self.api_key = api_key |
139 | 142 | # Only set api_url if api_key is provided, otherwise None (free tier) |
@@ -165,6 +168,7 @@ def __init__( |
165 | 168 | self.allowed_domains = allowed_domains or [] |
166 | 169 | self.prohibited_domains = prohibited_domains or [] |
167 | 170 | self.keep_alive = keep_alive |
| 171 | + self.permission_policy = self._coerce_permission_policy(permission_policy) |
168 | 172 |
|
169 | 173 | # Viewport configuration - convert dict to Viewport if needed |
170 | 174 | if viewport is None: |
@@ -231,6 +235,28 @@ def _parse_proxy(self, proxy_string: str) -> ProxyConfig | None: |
231 | 235 | ) |
232 | 236 | return None |
233 | 237 |
|
| 238 | + def _coerce_permission_policy( |
| 239 | + self, policy: PermissionPolicy | dict | None |
| 240 | + ) -> PermissionPolicy | None: |
| 241 | + if policy is None: |
| 242 | + return None |
| 243 | + if isinstance(policy, PermissionPolicy): |
| 244 | + return policy |
| 245 | + if isinstance(policy, dict): |
| 246 | + return PermissionPolicy(**policy) |
| 247 | + raise TypeError("permission_policy must be PermissionPolicy, dict, or None") |
| 248 | + |
| 249 | + def apply_permission_policy(self, context: BrowserContext) -> None: |
| 250 | + policy = self.permission_policy |
| 251 | + if policy is None: |
| 252 | + return |
| 253 | + if policy.default in ("clear", "deny"): |
| 254 | + context.clear_permissions() |
| 255 | + if policy.geolocation: |
| 256 | + context.set_geolocation(policy.geolocation) |
| 257 | + if policy.auto_grant: |
| 258 | + context.grant_permissions(policy.auto_grant, origin=policy.origin) |
| 259 | + |
234 | 260 | def start(self) -> None: |
235 | 261 | """Launch browser with extension loaded""" |
236 | 262 | # Get extension source path using shared utility |
@@ -338,6 +364,9 @@ def start(self) -> None: |
338 | 364 | # headless mode via the --headless=new arg above. This is a Playwright workaround. |
339 | 365 | self.context = self.playwright.chromium.launch_persistent_context(**launch_params) |
340 | 366 |
|
| 367 | + if self.context is not None: |
| 368 | + self.apply_permission_policy(self.context) |
| 369 | + |
341 | 370 | self.page = self.context.pages[0] if self.context.pages else self.context.new_page() |
342 | 371 |
|
343 | 372 | # Inject storage state if provided (must be after context creation) |
@@ -712,6 +741,7 @@ def __init__( |
712 | 741 | allowed_domains: list[str] | None = None, |
713 | 742 | prohibited_domains: list[str] | None = None, |
714 | 743 | keep_alive: bool = False, |
| 744 | + permission_policy: PermissionPolicy | dict | None = None, |
715 | 745 | ): |
716 | 746 | """ |
717 | 747 | Initialize Async Sentience browser |
@@ -740,6 +770,7 @@ def __init__( |
740 | 770 | this specific browser binary instead of Playwright's managed browser. |
741 | 771 | Useful to guarantee Chromium (not Chrome for Testing) on macOS. |
742 | 772 | Example: "/path/to/playwright/chromium-1234/chrome-mac/Chromium.app/Contents/MacOS/Chromium" |
| 773 | + permission_policy: Optional permission policy to apply on context creation. |
743 | 774 | """ |
744 | 775 | self.api_key = api_key |
745 | 776 | # Only set api_url if api_key is provided, otherwise None (free tier) |
@@ -770,6 +801,7 @@ def __init__( |
770 | 801 | self.allowed_domains = allowed_domains or [] |
771 | 802 | self.prohibited_domains = prohibited_domains or [] |
772 | 803 | self.keep_alive = keep_alive |
| 804 | + self.permission_policy = self._coerce_permission_policy(permission_policy) |
773 | 805 |
|
774 | 806 | # Viewport configuration - convert dict to Viewport if needed |
775 | 807 | if viewport is None: |
@@ -836,6 +868,28 @@ def _parse_proxy(self, proxy_string: str) -> ProxyConfig | None: |
836 | 868 | ) |
837 | 869 | return None |
838 | 870 |
|
| 871 | + def _coerce_permission_policy( |
| 872 | + self, policy: PermissionPolicy | dict | None |
| 873 | + ) -> PermissionPolicy | None: |
| 874 | + if policy is None: |
| 875 | + return None |
| 876 | + if isinstance(policy, PermissionPolicy): |
| 877 | + return policy |
| 878 | + if isinstance(policy, dict): |
| 879 | + return PermissionPolicy(**policy) |
| 880 | + raise TypeError("permission_policy must be PermissionPolicy, dict, or None") |
| 881 | + |
| 882 | + async def apply_permission_policy(self, context: AsyncBrowserContext) -> None: |
| 883 | + policy = self.permission_policy |
| 884 | + if policy is None: |
| 885 | + return |
| 886 | + if policy.default in ("clear", "deny"): |
| 887 | + await context.clear_permissions() |
| 888 | + if policy.geolocation: |
| 889 | + await context.set_geolocation(policy.geolocation) |
| 890 | + if policy.auto_grant: |
| 891 | + await context.grant_permissions(policy.auto_grant, origin=policy.origin) |
| 892 | + |
839 | 893 | async def start(self) -> None: |
840 | 894 | """Launch browser with extension loaded (async)""" |
841 | 895 | # Get extension source path using shared utility |
@@ -939,6 +993,9 @@ async def start(self) -> None: |
939 | 993 | # Launch persistent context |
940 | 994 | self.context = await self.playwright.chromium.launch_persistent_context(**launch_params) |
941 | 995 |
|
| 996 | + if self.context is not None: |
| 997 | + await self.apply_permission_policy(self.context) |
| 998 | + |
942 | 999 | self.page = self.context.pages[0] if self.context.pages else await self.context.new_page() |
943 | 1000 |
|
944 | 1001 | # Inject storage state if provided |
|
0 commit comments