"""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. """ from __future__ import annotations import re from browser_cli.constants import DEFAULT_REMOTE_PORT from browser_cli.errors import BrowserNotConnected 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}"