feat: harden remote serve and reuse connections
Testing / remote-protocol-compat (0.9.5) (push) Successful in 56s
Testing / remote-protocol-compat (0.9.3) (push) Successful in 59s
Testing / test (push) Successful in 1m1s
Build & Publish Package / publish (push) Successful in 33s
Package Extension / package-extension (push) Successful in 36s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 56s
Testing / remote-protocol-compat (0.9.3) (push) Successful in 59s
Testing / test (push) Successful in 1m1s
Build & Publish Package / publish (push) Successful in 33s
Package Extension / package-extension (push) Successful in 36s
- Gate TCP serve commands with safe-by-default policies, per-key allow tokens, per-key rate limiting, and audit labels. - Reuse authenticated encrypted remote sessions and parallelize/caches multi-browser fanout to reduce repeated handshake roundtrips. - Increase paged native-host batch size with extension-side byte budgeting to speed large tab listings safely. - Point install output at public Chrome Web Store / Firefox AMO listings by default, with --dev preserving unpacked workflows. - Share search-engine metadata between CLI and SDK and bump the package/extension version to 0.16.0. - Cover the new security, pooling, paging, install, and fanout behavior with expanded Python and extension tests.
This commit is contained in:
@@ -114,7 +114,7 @@ class NavigationNS(Namespace):
|
||||
) -> None:
|
||||
"""Open a search query in the given engine (e.g. 'google', 'youtube', 'ddg')."""
|
||||
from urllib.parse import quote_plus
|
||||
from browser_cli.commands.search import ENGINES
|
||||
from browser_cli.search.engines import ENGINES
|
||||
template = ENGINES.get(engine)
|
||||
if template is None:
|
||||
raise ValueError(f"Unknown search engine '{engine}'. Available: {', '.join(ENGINES)}")
|
||||
|
||||
+22
-10
@@ -7,12 +7,14 @@ helpers; single-browser mode falls straight through to ``_cmd``.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import importlib
|
||||
import sys
|
||||
from collections.abc import Callable, Iterable
|
||||
from typing import TYPE_CHECKING, Protocol, cast
|
||||
|
||||
from browser_cli.client import BrowserTarget
|
||||
from browser_cli.client.core import _run_concurrent
|
||||
from browser_cli.errors import BrowserNotConnected
|
||||
from browser_cli.models import BrowserCounts, Tab
|
||||
|
||||
@@ -81,18 +83,28 @@ class RoutingMixin:
|
||||
return targets
|
||||
|
||||
def _collect_multi_browser(self, command: str, args: dict | None = None):
|
||||
results = []
|
||||
targets = self._multi_browser_targets()
|
||||
for target in targets:
|
||||
try:
|
||||
if target.remote:
|
||||
data = _browser_cli_package().send_command(
|
||||
command, args, profile=target.profile, remote=target.remote, key=self._client._key
|
||||
)
|
||||
else:
|
||||
data = _browser_cli_package().send_command(command, args, profile=target.profile)
|
||||
except (BrowserNotConnected, RuntimeError):
|
||||
|
||||
def _send(target: BrowserTarget):
|
||||
package = _browser_cli_package()
|
||||
if target.remote:
|
||||
return package.send_command(
|
||||
command, args, profile=target.profile, remote=target.remote, key=self._client._key
|
||||
)
|
||||
return package.send_command(command, args, profile=target.profile)
|
||||
|
||||
# Run per-target roundtrips concurrently — each is a blocking, network-bound
|
||||
# send_command, so offloading to threads gives real overlap while still
|
||||
# invoking the (test-patchable) sync entry point.
|
||||
raw = _run_concurrent([
|
||||
(lambda t=t: asyncio.to_thread(_send, t)) for t in targets
|
||||
])
|
||||
results = []
|
||||
for target, data in zip(targets, raw):
|
||||
if isinstance(data, (BrowserNotConnected, RuntimeError)):
|
||||
continue
|
||||
if isinstance(data, BaseException):
|
||||
raise data
|
||||
results.append((target, data))
|
||||
if results:
|
||||
return results
|
||||
|
||||
Reference in New Issue
Block a user