add moveing of tabs and groups, multi browser support, auto complite into terminal, extract html and adding testing
This commit is contained in:
+71
-15
@@ -5,6 +5,10 @@ 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.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
@@ -16,7 +20,10 @@ import threading
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
SOCKET_PATH = "/tmp/browser-cli.sock"
|
||||
REGISTRY_PATH = Path("/tmp/browser-cli-registry.json")
|
||||
DEFAULT_ALIAS = "default"
|
||||
|
||||
SOCKET_PATH: str = "" # set after hello handshake
|
||||
PENDING: dict[str, queue.Queue] = {}
|
||||
PENDING_LOCK = threading.Lock()
|
||||
|
||||
@@ -40,16 +47,48 @@ def write_native_message(stream, msg: dict) -> None:
|
||||
stream.flush()
|
||||
|
||||
|
||||
# --- Registry helpers ---
|
||||
|
||||
def _registry_add(alias: str, sock_path: str) -> None:
|
||||
try:
|
||||
reg = json.loads(REGISTRY_PATH.read_text()) if REGISTRY_PATH.exists() else {}
|
||||
reg[alias] = sock_path
|
||||
REGISTRY_PATH.write_text(json.dumps(reg))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _registry_remove(alias: str) -> None:
|
||||
try:
|
||||
if not REGISTRY_PATH.exists():
|
||||
return
|
||||
reg = json.loads(REGISTRY_PATH.read_text())
|
||||
reg.pop(alias, None)
|
||||
REGISTRY_PATH.write_text(json.dumps(reg))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _socket_path_for(alias: str) -> str:
|
||||
safe = alias.replace(" ", "_").replace("/", "_")
|
||||
return f"/tmp/browser-cli-{safe}.sock"
|
||||
|
||||
|
||||
# --- Thread A: read messages from extension (stdin) ---
|
||||
|
||||
def stdin_reader():
|
||||
def stdin_reader(alias: str):
|
||||
stdin = sys.stdin.buffer
|
||||
while True:
|
||||
msg = read_native_message(stdin)
|
||||
if msg is None:
|
||||
# Extension disconnected — clean up socket and exit
|
||||
_cleanup()
|
||||
# Extension disconnected — clean up and exit
|
||||
_cleanup(alias)
|
||||
os._exit(0)
|
||||
|
||||
# Profile alias handshake
|
||||
if msg.get("type") == "hello":
|
||||
continue # already handled during startup
|
||||
|
||||
msg_id = msg.get("id")
|
||||
if msg_id:
|
||||
with PENDING_LOCK:
|
||||
@@ -60,13 +99,13 @@ def stdin_reader():
|
||||
|
||||
# --- Thread B: accept CLI socket connections ---
|
||||
|
||||
def socket_server():
|
||||
path = Path(SOCKET_PATH)
|
||||
def socket_server(sock_path: str):
|
||||
path = Path(sock_path)
|
||||
if path.exists():
|
||||
path.unlink()
|
||||
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.bind(SOCKET_PATH)
|
||||
sock.bind(sock_path)
|
||||
sock.listen(16)
|
||||
|
||||
while True:
|
||||
@@ -92,10 +131,8 @@ def handle_cli_connection(conn: socket.socket) -> None:
|
||||
with PENDING_LOCK:
|
||||
PENDING[msg_id] = response_queue
|
||||
|
||||
# Forward command to extension via stdout
|
||||
write_native_message(sys.stdout.buffer, cmd)
|
||||
|
||||
# Wait for extension's response (30 s timeout)
|
||||
try:
|
||||
result = response_queue.get(timeout=30)
|
||||
except queue.Empty:
|
||||
@@ -139,20 +176,39 @@ def _recv_exact(conn: socket.socket, n: int) -> bytes | None:
|
||||
return buf
|
||||
|
||||
|
||||
def _cleanup():
|
||||
def _cleanup(alias: str):
|
||||
try:
|
||||
Path(SOCKET_PATH).unlink(missing_ok=True)
|
||||
Path(_socket_path_for(alias)).unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
_registry_remove(alias)
|
||||
|
||||
|
||||
def main():
|
||||
# Start socket server thread
|
||||
t = threading.Thread(target=socket_server, daemon=True)
|
||||
stdin = sys.stdin.buffer
|
||||
|
||||
# Wait for the hello handshake to learn the profile alias
|
||||
first_msg = read_native_message(stdin)
|
||||
if first_msg and first_msg.get("type") == "hello":
|
||||
alias = first_msg.get("alias") or DEFAULT_ALIAS
|
||||
else:
|
||||
# No hello — fall back to default, re-queue message if it was a command
|
||||
alias = DEFAULT_ALIAS
|
||||
if first_msg:
|
||||
msg_id = first_msg.get("id")
|
||||
if msg_id:
|
||||
q: queue.Queue = queue.Queue()
|
||||
with PENDING_LOCK:
|
||||
PENDING[msg_id] = q
|
||||
write_native_message(sys.stdout.buffer, first_msg)
|
||||
|
||||
sock_path = _socket_path_for(alias)
|
||||
_registry_add(alias, sock_path)
|
||||
|
||||
t = threading.Thread(target=socket_server, args=(sock_path,), daemon=True)
|
||||
t.start()
|
||||
|
||||
# Read extension messages on main thread (blocks until extension disconnects)
|
||||
stdin_reader()
|
||||
stdin_reader(alias)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user