browserwright 0.7.3__tar.gz → 0.7.4__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.
- {browserwright-0.7.3 → browserwright-0.7.4}/PKG-INFO +1 -1
- {browserwright-0.7.3 → browserwright-0.7.4}/README.md +10 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/pyproject.toml +1 -1
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/cli.py +32 -2
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/config.py +22 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/server/facade.py +39 -9
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/server/listener.py +6 -1
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright.egg-info/PKG-INFO +1 -1
- {browserwright-0.7.3 → browserwright-0.7.4}/setup.cfg +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/__init__.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/__main__.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/_executor/__init__.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/_executor/__main__.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/_executor/client.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/_executor/process.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/_executor/protocol.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/api.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/cdp.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/cli.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/__init__.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/_ipc.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/_stale.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/backends/__init__.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/backends/base.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/backends/cdp.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/backends/extension.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/doctor.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/errors.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/launch_chrome.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/observability.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/platforms.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/resolver.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/server/__init__.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/server/daemon.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/server/executor_registry.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/server/extension_upstream.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/server/facade_extension.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/server/proxy.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/server/relay.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/server/state.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/server/upstream.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/server/verbs.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/userscripts.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/discovery.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/errors.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/health.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/install.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/memory/__init__.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/memory/_lock.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/memory/_md.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/memory/_yaml.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/memory/global_mem.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/memory/site_mem.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/mode_b_client.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/output_schema.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/primitives/__init__.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/primitives/discovery_api.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/primitives/http.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/primitives/site.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/repl/__init__.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/repl/_namespace.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/repl/_smart_goto.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/repl/inline.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/repl/playwright_handle.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/repl/snapshot.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/session.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/session_create.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/session_ctx.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/session_registry.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/session_runtime.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/site_skills_starter/github.com/SKILL.md +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/site_skills_starter/github.com/memory.md +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/site_skills_starter/github.com/tasks/list_issues.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/site_skills_starter/google.com/SKILL.md +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/site_skills_starter/google.com/memory.md +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/site_skills_starter/google.com/tasks/search.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/site_skills_starter/producthunt.com/SKILL.md +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/site_skills_starter/producthunt.com/memory.md +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/site_skills_starter/producthunt.com/tasks/today.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/site_skills_starter/wikipedia.org/SKILL.md +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/site_skills_starter/wikipedia.org/memory.md +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/site_skills_starter/wikipedia.org/tasks/lookup.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/site_skills_starter/ycombinator.com/SKILL.md +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/site_skills_starter/ycombinator.com/memory.md +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/site_skills_starter/ycombinator.com/tasks/front_page.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/skill_doc.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/skill_runtime.md +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/task_runner.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/version.py +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright.egg-info/SOURCES.txt +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright.egg-info/dependency_links.txt +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright.egg-info/entry_points.txt +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright.egg-info/requires.txt +0 -0
- {browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: browserwright
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.4
|
|
4
4
|
Summary: Browserwright — let AI/code agents drive a real or isolated browser and author userscripts. Single package: the agent-facing REPL/site-skills/memory layer plus the bundled browser-resolving daemon (CDP proxy + extension/cloud backends).
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Requires-Dist: websockets==15.0.1
|
|
@@ -179,6 +179,16 @@ external profiles with N isolated daemons — each with its own `XDG_RUNTIME_DIR
|
|
|
179
179
|
(distinct socket), `--facade-port`, and `BD_CDP_WS`, one `env` session apiece.
|
|
180
180
|
See [docs/session-workspaces.md](docs/session-workspaces.md) §"Env Backend".
|
|
181
181
|
|
|
182
|
+
**Reaching the facade from another machine (Tailscale/LAN):** the Playwright
|
|
183
|
+
facade binds `127.0.0.1` by default and is *never* exposed off-box unless you
|
|
184
|
+
opt in. Pass `--facade-host <tailnet-ip>` (or `BD_FACADE_HOST` / `facade_host`
|
|
185
|
+
in config.toml; `0.0.0.0` to bind all interfaces) and a remote client can
|
|
186
|
+
`connect_over_cdp("http://<tailnet-ip>:19990/cdp")` — the facade's
|
|
187
|
+
`/json/version` bootstrap rewrites the advertised `webSocketDebuggerUrl` from
|
|
188
|
+
the request's `Host` header, so the ws URL points back at the address the
|
|
189
|
+
client actually used. No auth is added, so only bind an interface you trust
|
|
190
|
+
(a Tailscale IP is private to your tailnet).
|
|
191
|
+
|
|
182
192
|
### Userscripts
|
|
183
193
|
|
|
184
194
|
Author Tampermonkey-style scripts the `extension` backend injects on matching sites:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "browserwright"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.4"
|
|
8
8
|
description = "Browserwright — let AI/code agents drive a real or isolated browser and author userscripts. Single package: the agent-facing REPL/site-skills/memory layer plus the bundled browser-resolving daemon (CDP proxy + extension/cloud backends)."
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
dependencies = [
|
|
@@ -108,6 +108,16 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
108
108
|
"(default: ON at 19990; pass 0 to disable). Lets a real "
|
|
109
109
|
"Playwright client `connect_over_cdp(ws://127.0.0.1:N/cdp)` and "
|
|
110
110
|
"the skill heredoc `page`/`context` drive the resolved Chrome."))
|
|
111
|
+
# Facade bind host. Defaults to loopback (127.0.0.1) — the facade is NEVER
|
|
112
|
+
# reachable off-box unless you opt in here. Set a Tailscale/LAN IP (or
|
|
113
|
+
# 0.0.0.0) to let a remote Playwright client connect_over_cdp. Equivalent to
|
|
114
|
+
# BD_FACADE_HOST env or `facade_host` in config.toml.
|
|
115
|
+
p_serve.add_argument(
|
|
116
|
+
"--facade-host", type=str, default=None, metavar="HOST",
|
|
117
|
+
help=("bind the Playwright-facing CDP facade on this host "
|
|
118
|
+
"(default: 127.0.0.1 / loopback). Set a Tailscale/LAN IP or "
|
|
119
|
+
"0.0.0.0 to reach it from another machine. Equivalent to "
|
|
120
|
+
"BD_FACADE_HOST env or `facade_host` in config.toml."))
|
|
111
121
|
|
|
112
122
|
# stop (v0.2)
|
|
113
123
|
p_stop = sub.add_parser("stop", help="stop the running daemon")
|
|
@@ -283,6 +293,13 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
283
293
|
help=argparse.SUPPRESS)
|
|
284
294
|
p_inst.add_argument("--extension-port", type=int, default=None, metavar="N",
|
|
285
295
|
help="override the relay ws port (default 19989)")
|
|
296
|
+
p_inst.add_argument("--facade-port", type=int, default=None, metavar="N",
|
|
297
|
+
help="override the Playwright facade port (default 19990)")
|
|
298
|
+
p_inst.add_argument(
|
|
299
|
+
"--facade-host", type=str, default=None, metavar="HOST",
|
|
300
|
+
help=("bind the Playwright facade on this host (default 127.0.0.1 / "
|
|
301
|
+
"loopback). Set a Tailscale/LAN IP or 0.0.0.0 to reach the "
|
|
302
|
+
"installed daemon's facade from another machine."))
|
|
286
303
|
p_inst.add_argument("--force", action="store_true",
|
|
287
304
|
help="replace an existing LaunchAgent with the same name")
|
|
288
305
|
|
|
@@ -329,6 +346,7 @@ def _cfg_from_args(args) -> Config:
|
|
|
329
346
|
# per subcommand, so getattr-with-default keeps non-serve calls clean.
|
|
330
347
|
cli_extension_port=getattr(args, "extension_port", None),
|
|
331
348
|
cli_facade_port=getattr(args, "facade_port", None),
|
|
349
|
+
cli_facade_host=getattr(args, "facade_host", None),
|
|
332
350
|
)
|
|
333
351
|
|
|
334
352
|
|
|
@@ -949,7 +967,9 @@ def _resolve_browserwright_daemon_bin() -> str:
|
|
|
949
967
|
)
|
|
950
968
|
|
|
951
969
|
|
|
952
|
-
def _build_plist(*, extension_port: int | None
|
|
970
|
+
def _build_plist(*, extension_port: int | None,
|
|
971
|
+
facade_host: str | None = None,
|
|
972
|
+
facade_port: int | None = None) -> str:
|
|
953
973
|
"""Emit the plist content. Kept inline (no XML lib) — the schema is
|
|
954
974
|
fixed + tiny, and we avoid a dependency. Every interpolated value passes
|
|
955
975
|
through ``xml.sax.saxutils.escape``. Pure: no side effects — the caller is
|
|
@@ -959,6 +979,10 @@ def _build_plist(*, extension_port: int | None) -> str:
|
|
|
959
979
|
args = [bin_path, "serve"]
|
|
960
980
|
if extension_port is not None:
|
|
961
981
|
args += ["--extension-port", str(extension_port)]
|
|
982
|
+
if facade_port is not None:
|
|
983
|
+
args += ["--facade-port", str(facade_port)]
|
|
984
|
+
if facade_host is not None:
|
|
985
|
+
args += ["--facade-host", str(facade_host)]
|
|
962
986
|
log_dir = os.path.expanduser("~/.cache/browserwright-daemon/logs")
|
|
963
987
|
stdout_path = f"{log_dir}/browserwright-daemon.stdout.log"
|
|
964
988
|
stderr_path = f"{log_dir}/browserwright-daemon.stderr.log"
|
|
@@ -1030,7 +1054,11 @@ def _cmd_install(args, cfg: Config) -> int:
|
|
|
1030
1054
|
# `_build_plist` so the generator stays pure / unit-testable (N-1).
|
|
1031
1055
|
log_dir = os.path.expanduser("~/.cache/browserwright-daemon/logs")
|
|
1032
1056
|
os.makedirs(log_dir, exist_ok=True)
|
|
1033
|
-
content = _build_plist(
|
|
1057
|
+
content = _build_plist(
|
|
1058
|
+
extension_port=args.extension_port,
|
|
1059
|
+
facade_host=getattr(args, "facade_host", None),
|
|
1060
|
+
facade_port=getattr(args, "facade_port", None),
|
|
1061
|
+
)
|
|
1034
1062
|
# If --force and the plist exists, unload the old one first so launchctl
|
|
1035
1063
|
# picks up the new ProgramArguments cleanly.
|
|
1036
1064
|
if plist_path.exists():
|
|
@@ -1049,6 +1077,8 @@ def _cmd_install(args, cfg: Config) -> int:
|
|
|
1049
1077
|
"label": label,
|
|
1050
1078
|
"plist": str(plist_path),
|
|
1051
1079
|
"extension_port": args.extension_port,
|
|
1080
|
+
"facade_host": getattr(args, "facade_host", None),
|
|
1081
|
+
"facade_port": getattr(args, "facade_port", None),
|
|
1052
1082
|
}, sort_keys=True))
|
|
1053
1083
|
return 0
|
|
1054
1084
|
|
|
@@ -29,6 +29,12 @@ _NAME_RE = re.compile(r"\A[A-Za-z0-9_-]{1,64}\Z")
|
|
|
29
29
|
# re-exports it for backwards compat.
|
|
30
30
|
DEFAULT_FACADE_PORT = 19990
|
|
31
31
|
|
|
32
|
+
# Default bind host for the Playwright-facing CDP facade. Stays loopback so the
|
|
33
|
+
# facade is NEVER reachable off-box by accident; override via `--facade-host` /
|
|
34
|
+
# `BD_FACADE_HOST` / `facade_host` in config.toml to bind a Tailscale/LAN
|
|
35
|
+
# interface (e.g. `0.0.0.0` or a specific tailnet IP) for remote CDP clients.
|
|
36
|
+
DEFAULT_FACADE_HOST = "127.0.0.1"
|
|
37
|
+
|
|
32
38
|
|
|
33
39
|
def check_name(name: str) -> str:
|
|
34
40
|
"""Path-traversal guard for filesystem-bound names (e.g. `--profile`)."""
|
|
@@ -116,6 +122,11 @@ class Config:
|
|
|
116
122
|
# - >0 -> explicit override port (CLI/env/toml).
|
|
117
123
|
# Use `resolved_facade_port()` to collapse this to "bind / don't bind".
|
|
118
124
|
facade_port: int | None = None
|
|
125
|
+
# Bind host for the Playwright facade. Defaults to loopback (never exposed by
|
|
126
|
+
# accident); set to a Tailscale/LAN IP or `0.0.0.0` to reach it off-box.
|
|
127
|
+
# Precedence mirrors facade_port: CLI `--facade-host` > `BD_FACADE_HOST` env
|
|
128
|
+
# > toml `facade_host` > DEFAULT_FACADE_HOST.
|
|
129
|
+
facade_host: str = DEFAULT_FACADE_HOST
|
|
119
130
|
backends: BackendsConfig = field(default_factory=BackendsConfig)
|
|
120
131
|
# Provenance for `env`: which alias key actually fired. Diagnostic-only.
|
|
121
132
|
cdp_ws_source: str | None = None # "BD_CDP_WS" | "BU_CDP_WS" | None
|
|
@@ -156,6 +167,7 @@ def load(
|
|
|
156
167
|
cli_config_path: str | None = None,
|
|
157
168
|
cli_extension_port: int | None = None,
|
|
158
169
|
cli_facade_port: int | None = None,
|
|
170
|
+
cli_facade_host: str | None = None,
|
|
159
171
|
env: dict[str, str] | None = None,
|
|
160
172
|
) -> Config:
|
|
161
173
|
"""Build a Config from CLI flags + env + optional toml file.
|
|
@@ -213,6 +225,9 @@ def load(
|
|
|
213
225
|
# Playwright facade port (phase A1). toml key `facade_port`.
|
|
214
226
|
if "facade_port" in toml and isinstance(toml["facade_port"], int):
|
|
215
227
|
cfg.facade_port = toml["facade_port"]
|
|
228
|
+
# Playwright facade bind host. toml key `facade_host`.
|
|
229
|
+
if "facade_host" in toml and isinstance(toml["facade_host"], str):
|
|
230
|
+
cfg.facade_host = toml["facade_host"]
|
|
216
231
|
|
|
217
232
|
# env level — BD_* wins over BU_*
|
|
218
233
|
if "BD_TIMEOUT" in e:
|
|
@@ -317,6 +332,10 @@ def load(
|
|
|
317
332
|
from .errors import UserError
|
|
318
333
|
raise UserError(
|
|
319
334
|
f"BD_FACADE_PORT must be an integer, got {e['BD_FACADE_PORT']!r}")
|
|
335
|
+
# Playwright facade bind host via env. Symmetric to BD_FACADE_PORT — set
|
|
336
|
+
# BD_FACADE_HOST=<tailnet-ip>/0.0.0.0 to reach the facade off-box.
|
|
337
|
+
if "BD_FACADE_HOST" in e:
|
|
338
|
+
cfg.facade_host = e["BD_FACADE_HOST"]
|
|
320
339
|
|
|
321
340
|
# CLI level — last word
|
|
322
341
|
if cli_backend is not None:
|
|
@@ -333,5 +352,8 @@ def load(
|
|
|
333
352
|
if cli_facade_port is not None:
|
|
334
353
|
# Phase A1: CLI `--facade-port` tops env / toml.
|
|
335
354
|
cfg.facade_port = cli_facade_port
|
|
355
|
+
if cli_facade_host is not None:
|
|
356
|
+
# CLI `--facade-host` tops env / toml.
|
|
357
|
+
cfg.facade_host = cli_facade_host
|
|
336
358
|
|
|
337
359
|
return cfg
|
|
@@ -158,13 +158,25 @@ class PlaywrightFacade:
|
|
|
158
158
|
`webSocketDebuggerUrl: ws://host/cdp`; `connect_over_cdp("ws://...")`
|
|
159
159
|
also accepts an `http://` URL it resolves via this route. We answer
|
|
160
160
|
`/json/version`, `/json`, and `/json/list` (the latter two are cheap
|
|
161
|
-
and some CDP clients probe them).
|
|
161
|
+
and some CDP clients probe them).
|
|
162
|
+
|
|
163
|
+
The advertised `webSocketDebuggerUrl` is derived from the incoming
|
|
164
|
+
request's `Host` header so a client that reached us over Tailscale/LAN
|
|
165
|
+
gets a ws URL that points back at the same authority it just used —
|
|
166
|
+
exactly what CloakBrowser does. We only fall back to the configured
|
|
167
|
+
`host:port` when no Host header is present (e.g. a hand-rolled probe)."""
|
|
162
168
|
path = (request.path or "/").split("?", 1)[0]
|
|
169
|
+
# Playwright's `connect_over_cdp("http://host:port")` probes
|
|
170
|
+
# `.../json/version/` WITH a trailing slash; normalize it away so the
|
|
171
|
+
# http bootstrap form works, not just the direct `ws://.../cdp` form.
|
|
172
|
+
if len(path) > 1:
|
|
173
|
+
path = path.rstrip("/")
|
|
163
174
|
session_id = self._session_for_request(request)
|
|
175
|
+
authority = self._authority_from_request(request)
|
|
164
176
|
if path == "/json/version":
|
|
165
|
-
return self._http_json(conn, self._version_payload(session_id))
|
|
177
|
+
return self._http_json(conn, self._version_payload(session_id, authority))
|
|
166
178
|
if path in ("/json", "/json/list"):
|
|
167
|
-
return self._http_json(conn, self._list_payload(session_id))
|
|
179
|
+
return self._http_json(conn, self._list_payload(session_id, authority))
|
|
168
180
|
# Anything else (e.g. the /cdp ws upgrade) falls through to the ws
|
|
169
181
|
# handler. Return None to allow the upgrade.
|
|
170
182
|
return None
|
|
@@ -180,22 +192,40 @@ class PlaywrightFacade:
|
|
|
180
192
|
qs = parse_qs(urlparse(request.path or "/").query)
|
|
181
193
|
return (qs.get("session") or [None])[0]
|
|
182
194
|
|
|
183
|
-
def
|
|
195
|
+
def _authority_from_request(self, request) -> str | None:
|
|
196
|
+
"""Return the `Host` header (``host[:port]``) the client used to reach
|
|
197
|
+
us, or None when absent. Drives the advertised ws so a remote client
|
|
198
|
+
that connected over Tailscale/LAN gets a ws URL that points back at the
|
|
199
|
+
same authority (not a hardcoded loopback it can't reach)."""
|
|
200
|
+
try:
|
|
201
|
+
host = request.headers.get("Host")
|
|
202
|
+
except (AttributeError, KeyError):
|
|
203
|
+
return None
|
|
204
|
+
host = (host or "").strip()
|
|
205
|
+
return host or None
|
|
206
|
+
|
|
207
|
+
def _ws_url(self, session_id: str | None = None,
|
|
208
|
+
authority: str | None = None) -> str:
|
|
209
|
+
# Prefer the incoming request's Host (already `host[:port]`); fall back
|
|
210
|
+
# to the configured bind host:port when no Host header was sent.
|
|
211
|
+
netloc = authority or f"{self._host}:{self._port}"
|
|
184
212
|
suffix = ""
|
|
185
213
|
if session_id:
|
|
186
214
|
from urllib.parse import quote
|
|
187
215
|
suffix = f"?session={quote(session_id, safe='')}"
|
|
188
|
-
return f"ws://{
|
|
216
|
+
return f"ws://{netloc}{FACADE_WS_PATH}{suffix}"
|
|
189
217
|
|
|
190
|
-
def _version_payload(self, session_id: str | None = None
|
|
218
|
+
def _version_payload(self, session_id: str | None = None,
|
|
219
|
+
authority: str | None = None) -> dict:
|
|
191
220
|
return {
|
|
192
221
|
"Browser": f"Browserwright/{__version__}",
|
|
193
222
|
"Protocol-Version": "1.3",
|
|
194
223
|
"User-Agent": f"Browserwright facade {__version__}",
|
|
195
|
-
"webSocketDebuggerUrl": self._ws_url(session_id),
|
|
224
|
+
"webSocketDebuggerUrl": self._ws_url(session_id, authority),
|
|
196
225
|
}
|
|
197
226
|
|
|
198
|
-
def _list_payload(self, session_id: str | None = None
|
|
227
|
+
def _list_payload(self, session_id: str | None = None,
|
|
228
|
+
authority: str | None = None) -> list:
|
|
199
229
|
# The browser-level endpoint is what Playwright wants; per-page targets
|
|
200
230
|
# are discovered via Target.* over the ws once connected. We advertise a
|
|
201
231
|
# single synthetic "browser" entry pointing at our ws.
|
|
@@ -203,7 +233,7 @@ class PlaywrightFacade:
|
|
|
203
233
|
"type": "browser",
|
|
204
234
|
"title": "Browserwright",
|
|
205
235
|
"url": "",
|
|
206
|
-
"webSocketDebuggerUrl": self._ws_url(session_id),
|
|
236
|
+
"webSocketDebuggerUrl": self._ws_url(session_id, authority),
|
|
207
237
|
}]
|
|
208
238
|
|
|
209
239
|
# ---- ws passthrough --------------------------------------------------
|
|
@@ -290,10 +290,15 @@ async def run_serve(cfg: Config) -> int:
|
|
|
290
290
|
return shared_context.holder.relay
|
|
291
291
|
|
|
292
292
|
facade = PlaywrightFacade(cfg=cfg, port=facade_port,
|
|
293
|
+
host=cfg.facade_host,
|
|
293
294
|
relay_getter=_shared_relay,
|
|
294
295
|
daemon=daemon)
|
|
295
296
|
bound = await facade.start()
|
|
296
|
-
|
|
297
|
+
# Advertise the bound host so `status`/skill discovery report a
|
|
298
|
+
# reachable ws (loopback by default; a tailnet/LAN IP when
|
|
299
|
+
# `--facade-host` opts in). Remote clients still get the real
|
|
300
|
+
# incoming Host echoed back via the facade's HTTP bootstrap.
|
|
301
|
+
facade_ws = f"ws://{cfg.facade_host}:{bound}/cdp"
|
|
297
302
|
# Advertise the bound ws so the skill layer can discover it (Phase C
|
|
298
303
|
# auto-enable). Best-effort: a write failure must not abort serving.
|
|
299
304
|
with contextlib.suppress(Exception):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: browserwright
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.4
|
|
4
4
|
Summary: Browserwright — let AI/code agents drive a real or isolated browser and author userscripts. Single package: the agent-facing REPL/site-skills/memory layer plus the bundled browser-resolving daemon (CDP proxy + extension/cloud backends).
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Requires-Dist: websockets==15.0.1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/server/executor_registry.py
RENAMED
|
File without changes
|
{browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/server/extension_upstream.py
RENAMED
|
File without changes
|
{browserwright-0.7.3 → browserwright-0.7.4}/src/browserwright/daemon/server/facade_extension.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|