@btraut/browser-bridge 0.13.0 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -28,6 +28,7 @@ var import_commander = require("commander");
28
28
 
29
29
  // packages/shared/src/core-readiness.ts
30
30
  var import_promises = require("node:timers/promises");
31
+ var import_node_net = require("node:net");
31
32
 
32
33
  // packages/shared/src/logging.ts
33
34
  var import_node_fs2 = require("node:fs");
@@ -604,6 +605,27 @@ var DEFAULT_HEALTH_RETRY_MS = 250;
604
605
  var DEFAULT_HEALTH_ATTEMPTS = 20;
605
606
  var DEFAULT_HEALTH_TIMEOUT_MS = 2e3;
606
607
  var DEFAULT_HEALTH_BUDGET_MS = 15e3;
608
+ var DEFAULT_PORT_REACHABILITY_TIMEOUT_MS = 300;
609
+ var isPortReachableDefault = async (runtime) => {
610
+ return await new Promise((resolve4) => {
611
+ const socket = new import_node_net.Socket();
612
+ let settled = false;
613
+ const finish = (reachable) => {
614
+ if (settled) {
615
+ return;
616
+ }
617
+ settled = true;
618
+ socket.removeAllListeners();
619
+ socket.destroy();
620
+ resolve4(reachable);
621
+ };
622
+ socket.setTimeout(DEFAULT_PORT_REACHABILITY_TIMEOUT_MS);
623
+ socket.once("connect", () => finish(true));
624
+ socket.once("timeout", () => finish(false));
625
+ socket.once("error", () => finish(false));
626
+ socket.connect(runtime.port, runtime.host);
627
+ });
628
+ };
607
629
  var resolveTimeoutMs = (timeoutMs) => {
608
630
  const candidate = timeoutMs ?? (process.env.BROWSER_BRIDGE_CORE_TIMEOUT_MS ? Number.parseInt(process.env.BROWSER_BRIDGE_CORE_TIMEOUT_MS, 10) : process.env.BROWSER_VISION_CORE_TIMEOUT_MS ? Number.parseInt(process.env.BROWSER_VISION_CORE_TIMEOUT_MS, 10) : void 0);
609
631
  if (candidate === void 0 || candidate === null) {
@@ -669,57 +691,71 @@ var createCoreReadinessController = (options = {}) => {
669
691
  baseUrl = `http://${runtime.host}:${runtime.port}`;
670
692
  };
671
693
  const checkHealth = async () => {
672
- try {
673
- const controller = new AbortController();
674
- const timeout = setTimeout(() => controller.abort(), healthTimeoutMs);
694
+ for (const method of ["POST", "GET"]) {
675
695
  try {
676
- let response;
696
+ const controller = new AbortController();
697
+ const timeout = setTimeout(() => controller.abort(), healthTimeoutMs);
677
698
  try {
678
- response = await fetchImpl(`${baseUrl}/health`, {
679
- method: "POST",
680
- signal: controller.signal
681
- });
682
- } catch (error) {
683
- if (controller.signal.aborted || error instanceof Error && error.name === "AbortError") {
684
- logger.warn(`${logPrefix}.health.timeout`, {
699
+ let response;
700
+ try {
701
+ response = await fetchImpl(`${baseUrl}/health`, {
702
+ method,
703
+ signal: controller.signal
704
+ });
705
+ } catch (error) {
706
+ if (controller.signal.aborted || error instanceof Error && error.name === "AbortError") {
707
+ logger.warn(`${logPrefix}.health.timeout`, {
708
+ base_url: baseUrl,
709
+ method,
710
+ timeout_ms: healthTimeoutMs
711
+ });
712
+ continue;
713
+ }
714
+ logger.warn(`${logPrefix}.health.fetch_failed`, {
685
715
  base_url: baseUrl,
686
- timeout_ms: healthTimeoutMs
716
+ method,
717
+ error
687
718
  });
688
- return false;
719
+ throw error;
720
+ }
721
+ if (!response.ok) {
722
+ logger.warn(`${logPrefix}.health.non_ok`, {
723
+ base_url: baseUrl,
724
+ method,
725
+ status: response.status
726
+ });
727
+ continue;
728
+ }
729
+ const data = await response.json().catch(() => null);
730
+ const ok = Boolean(data?.ok);
731
+ if (ok) {
732
+ if (method === "GET") {
733
+ logger.info(`${logPrefix}.health.compat_probe`, {
734
+ base_url: baseUrl,
735
+ method
736
+ });
737
+ }
738
+ return true;
689
739
  }
690
- logger.warn(`${logPrefix}.health.fetch_failed`, {
691
- base_url: baseUrl,
692
- error
693
- });
694
- throw error;
695
- }
696
- if (!response.ok) {
697
- logger.warn(`${logPrefix}.health.non_ok`, {
698
- base_url: baseUrl,
699
- status: response.status
700
- });
701
- return false;
702
- }
703
- const data = await response.json().catch(() => null);
704
- const ok = Boolean(data?.ok);
705
- if (!ok) {
706
740
  logger.warn(`${logPrefix}.health.not_ready`, {
707
- base_url: baseUrl
741
+ base_url: baseUrl,
742
+ method
708
743
  });
744
+ } finally {
745
+ clearTimeout(timeout);
709
746
  }
710
- return ok;
711
- } finally {
712
- clearTimeout(timeout);
747
+ } catch (error) {
748
+ logger.warn(`${logPrefix}.health.error`, {
749
+ base_url: baseUrl,
750
+ method,
751
+ error
752
+ });
713
753
  }
714
- } catch (error) {
715
- logger.warn(`${logPrefix}.health.error`, {
716
- base_url: baseUrl,
717
- error
718
- });
719
- return false;
720
754
  }
755
+ return false;
721
756
  };
722
757
  const ensureCoreRunning = async () => {
758
+ const portReachabilityCheck = options.portReachabilityCheck ?? isPortReachableDefault;
723
759
  refreshRuntime();
724
760
  if (await checkHealth()) {
725
761
  logger.debug(`${logPrefix}.ensure_ready.already_running`, {
@@ -760,6 +796,21 @@ var createCoreReadinessController = (options = {}) => {
760
796
  health_budget_ms: healthBudgetMs,
761
797
  health_timeout_ms: healthTimeoutMs
762
798
  });
799
+ let portOccupied = false;
800
+ try {
801
+ portOccupied = await portReachabilityCheck(runtime);
802
+ } catch (error) {
803
+ logger.warn(`${logPrefix}.ensure_ready.port_probe_failed`, {
804
+ host: runtime.host,
805
+ port: runtime.port,
806
+ error
807
+ });
808
+ }
809
+ if (portOccupied) {
810
+ throw new Error(
811
+ `Core daemon failed to start on ${runtime.host}:${runtime.port}. A process is already listening on this port but did not pass Browser Bridge health checks. Retry with --no-daemon to reuse it, or enable isolated mode (BROWSER_BRIDGE_ISOLATED_MODE=1) for per-worktree ports.`
812
+ );
813
+ }
763
814
  throw new Error(
764
815
  `Core daemon failed to start on ${runtime.host}:${runtime.port}.`
765
816
  );