Files
browser-cli/browser_cli/endpoints.py
T
daniel156161 fd5447cbb9
Testing / remote-protocol-compat (0.9.3) (push) Successful in 42s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 44s
Package Extension / package-extension (push) Successful in 43s
Build & Publish Package / publish (push) Successful in 43s
Testing / test (push) Successful in 45s
refactor(api): namespaced SDK + dedicated transport layer
Restructure the Python API and internals around composable namespaces and
a standalone transport/endpoint layer. Bump to 0.12.0.

Python API:
- Replace flat methods (b.tabs_list(), b.group_list()) with namespaces:
  b.nav, b.tabs, b.groups, b.windows, b.dom, b.extract, b.page, b.storage,
  b.cookies, b.session, b.perf, b.extension.
- Shrink browser_cli/__init__.py to a thin composition root; move all
  behaviour into browser_cli/sdk/ (one module per namespace + factories,
  base, routing).

Internals:
- Add browser_cli/transport.py and remote_transport.py to isolate IPC from
  command logic; client.py now delegates instead of owning transport.
- Add browser_cli/endpoints.py for endpoint resolution and
  browser_cli/errors.py for shared error types.
- Extract markdown rendering into browser_cli/markdown.py (out of extract).
- Add USER_AGENT to version_manager.

Tooling & tests:
- Add justfile with common dev tasks.
- Update CLI commands and demo to the namespaced API.
- Rework tests for the new layout; add test_transport.py and
  test_refactor_boundaries.py to lock in module boundaries.

BREAKING CHANGE: flat API methods are removed in favour of namespaces
(e.g. b.tabs_list() -> b.tabs.list(), b.group_list() -> b.groups.list()).
2026-06-11 13:58:41 +02:00

58 lines
2.1 KiB
Python

"""Remote endpoint string handling — parsing, normalization, display.
Pure helpers (no sockets, no I/O) for turning user-facing ``host[:port]``
strings into the canonical forms the rest of the client uses, and back into the
short forms shown to humans. Re-exported from :mod:`browser_cli.client` for
backward compatibility.
"""
from __future__ import annotations
import re
from browser_cli.errors import BrowserNotConnected
_DEFAULT_REMOTE_PORT = 443
def _looks_like_domain(host: str) -> bool:
"""True if host looks like a domain name rather than an IP address or localhost."""
if host in {"localhost", "127.0.0.1", "::1"}:
return False
if re.match(r'^\d{1,3}(\.\d{1,3}){3}$', host):
return False
return '.' in host and any(c.isalpha() for c in host)
def _normalize_endpoint(endpoint: str) -> str:
"""Strip :443 from domain-like endpoints so they are stored without the default port."""
if not endpoint:
return endpoint
host, sep, port = endpoint.rpartition(":")
if sep and port == "443" and _looks_like_domain(host):
return host
return endpoint
def _resolve_connect_endpoint(endpoint: str) -> str:
"""Return host:port for TCP connection; domain without port defaults to :443."""
_, sep, _ = endpoint.rpartition(":")
if not sep:
if _looks_like_domain(endpoint):
return f"{endpoint}:{_DEFAULT_REMOTE_PORT}"
raise BrowserNotConnected(
f"Invalid remote endpoint '{endpoint}': expected host:port"
)
return endpoint
def display_browser_name(profile_name: str, sock_path: str) -> str:
from pathlib import Path
if profile_name != "default":
return profile_name
return Path(sock_path).stem or profile_name
def _remote_display_name(endpoint: str, profile_name: str, display_name: str) -> str:
host, sep, port = endpoint.rpartition(":")
if sep and (port == "8765" or (port == "443" and _looks_like_domain(host))):
display_endpoint = host
else:
display_endpoint = endpoint # normalized domain (no port) or non-default port
return f"{display_endpoint}:{display_name or profile_name}"