This commit is contained in:
+35
-19
@@ -2,13 +2,9 @@
|
||||
"""
|
||||
Native Messaging Host for browser-cli.
|
||||
|
||||
Chrome launches this process when the extension calls connectNative().
|
||||
It relays messages between the Chrome extension (via stdin/stdout using the
|
||||
Native Messaging protocol) and the CLI (via a Unix domain socket).
|
||||
|
||||
Multi-browser support: the extension sends a "hello" message on startup
|
||||
with a profile alias. The host uses that alias to create a unique socket
|
||||
path and registers it in a shared registry file.
|
||||
Chrome launches this process when extension calls connectNative().
|
||||
It relays messages between extension (stdin/stdout Native Messaging protocol)
|
||||
and CLI (local IPC endpoint: Unix socket on Unix, named pipe on Windows).
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
@@ -18,15 +14,15 @@ import struct
|
||||
import sys
|
||||
import threading
|
||||
import uuid
|
||||
from multiprocessing.connection import Listener
|
||||
from pathlib import Path
|
||||
|
||||
SOCKET_DIR = Path("/tmp/.browser_cli")
|
||||
REGISTRY_PATH = SOCKET_DIR / "registry.json"
|
||||
DEFAULT_ALIAS = "default"
|
||||
from browser_cli.platform import DEFAULT_ALIAS, endpoint_for_alias, is_windows, registry_path, runtime_dir
|
||||
|
||||
SOCKET_PATH: str = "" # set after hello handshake
|
||||
PENDING: dict[str, queue.Queue] = {}
|
||||
PENDING_LOCK = threading.Lock()
|
||||
REGISTRY_PATH = registry_path()
|
||||
|
||||
# --- Native Messaging protocol (4-byte LE length prefix + UTF-8 JSON) ---
|
||||
|
||||
@@ -71,8 +67,7 @@ def _registry_remove(alias: str) -> None:
|
||||
|
||||
|
||||
def _socket_path_for(alias: str) -> str:
|
||||
safe = alias.replace(" ", "_").replace("/", "_")
|
||||
return str(SOCKET_DIR / f"{safe}.sock")
|
||||
return endpoint_for_alias(alias)
|
||||
|
||||
|
||||
def _resolve_profile_alias(first_msg: dict | None) -> str:
|
||||
@@ -113,6 +108,16 @@ def stdin_reader(alias: str):
|
||||
# --- Thread B: accept CLI socket connections ---
|
||||
|
||||
def socket_server(sock_path: str):
|
||||
if is_windows():
|
||||
while True:
|
||||
try:
|
||||
listener = Listener(sock_path, family="AF_PIPE")
|
||||
conn = listener.accept()
|
||||
except OSError:
|
||||
break
|
||||
threading.Thread(target=handle_cli_connection, args=(conn, listener), daemon=True).start()
|
||||
return
|
||||
|
||||
path = Path(sock_path)
|
||||
if path.exists():
|
||||
path.unlink()
|
||||
@@ -126,12 +131,12 @@ def socket_server(sock_path: str):
|
||||
conn, _ = sock.accept()
|
||||
except OSError:
|
||||
break
|
||||
threading.Thread(target=handle_cli_connection, args=(conn,), daemon=True).start()
|
||||
threading.Thread(target=handle_cli_connection, args=(conn, None), daemon=True).start()
|
||||
|
||||
|
||||
def handle_cli_connection(conn: socket.socket) -> None:
|
||||
def handle_cli_connection(conn, listener=None) -> None:
|
||||
try:
|
||||
data = _recv_all(conn)
|
||||
data = conn.recv_bytes() if is_windows() else _recv_all(conn)
|
||||
if not data:
|
||||
return
|
||||
cmd = json.loads(data)
|
||||
@@ -154,14 +159,24 @@ def handle_cli_connection(conn: socket.socket) -> None:
|
||||
with PENDING_LOCK:
|
||||
PENDING.pop(msg_id, None)
|
||||
|
||||
_send_all(conn, json.dumps(result).encode("utf-8"))
|
||||
response = json.dumps(result).encode("utf-8")
|
||||
if is_windows():
|
||||
conn.send_bytes(response)
|
||||
else:
|
||||
_send_all(conn, response)
|
||||
except Exception as exc:
|
||||
try:
|
||||
_send_all(conn, json.dumps({"success": False, "error": str(exc)}).encode("utf-8"))
|
||||
response = json.dumps({"success": False, "error": str(exc)}).encode("utf-8")
|
||||
if is_windows():
|
||||
conn.send_bytes(response)
|
||||
else:
|
||||
_send_all(conn, response)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
conn.close()
|
||||
if listener is not None:
|
||||
listener.close()
|
||||
|
||||
|
||||
# --- Socket helpers (length-prefixed framing) ---
|
||||
@@ -191,7 +206,8 @@ def _recv_exact(conn: socket.socket, n: int) -> bytes | None:
|
||||
|
||||
def _cleanup(alias: str):
|
||||
try:
|
||||
Path(_socket_path_for(alias)).unlink(missing_ok=True)
|
||||
if not is_windows():
|
||||
Path(_socket_path_for(alias)).unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
_registry_remove(alias)
|
||||
@@ -215,7 +231,7 @@ def main():
|
||||
PENDING[msg_id] = q
|
||||
write_native_message(sys.stdout.buffer, first_msg)
|
||||
|
||||
SOCKET_DIR.mkdir(mode=0o700, exist_ok=True)
|
||||
runtime_dir().mkdir(mode=0o700, exist_ok=True)
|
||||
sock_path = _socket_path_for(alias)
|
||||
_registry_add(alias, sock_path)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user