@qontinui/ui-bridge 0.10.0 → 0.11.0

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
@@ -25801,7 +25801,7 @@ function exposeProviderOnWindow(internals) {
25801
25801
  if (typeof window === "undefined") return;
25802
25802
  const w = window;
25803
25803
  const root = w.__UI_BRIDGE__ ?? (w.__UI_BRIDGE__ = {});
25804
- root.version = "0.10.0";
25804
+ root.version = "0.11.0";
25805
25805
  root.specs = { getGlobalSpecStore };
25806
25806
  root.registry = internals.registry;
25807
25807
  root.browserCapture = internals.browserCapture;
@@ -37542,11 +37542,16 @@ function transportHeaders(authHeader) {
37542
37542
  }
37543
37543
  return headers;
37544
37544
  }
37545
- function appendAuthQuery(url, authHeader) {
37546
- const token = resolveAuthToken(authHeader);
37547
- if (!token) return url;
37548
- const sep = url.includes("?") ? "&" : "?";
37549
- return `${url}${sep}_auth=${encodeURIComponent(token)}`;
37545
+ function parseSSEDataBlock(block) {
37546
+ const dataLines = [];
37547
+ for (const line of block.split("\n")) {
37548
+ if (line.startsWith(":")) continue;
37549
+ if (!line.startsWith("data:")) continue;
37550
+ let value = line.slice(5);
37551
+ if (value.startsWith(" ")) value = value.slice(1);
37552
+ dataLines.push(value);
37553
+ }
37554
+ return dataLines.length > 0 ? dataLines.join("\n") : null;
37550
37555
  }
37551
37556
  function useCommandRelay(options) {
37552
37557
  const enabled = options?.enabled ?? true;
@@ -37586,7 +37591,6 @@ function useCommandRelay(options) {
37586
37591
  }),
37587
37592
  [uiBridge, context]
37588
37593
  );
37589
- const eventSourceRef = react.useRef(null);
37590
37594
  const reconnectTimeoutRef = react.useRef(null);
37591
37595
  const processCommandRef = react.useRef(() => {
37592
37596
  });
@@ -37662,10 +37666,6 @@ function useCommandRelay(options) {
37662
37666
  processCommandRef.current = processCommand;
37663
37667
  react.useEffect(() => {
37664
37668
  if (!enabled) {
37665
- if (eventSourceRef.current) {
37666
- eventSourceRef.current.close();
37667
- eventSourceRef.current = null;
37668
- }
37669
37669
  if (reconnectTimeoutRef.current) {
37670
37670
  clearTimeout(reconnectTimeoutRef.current);
37671
37671
  reconnectTimeoutRef.current = null;
@@ -37673,34 +37673,73 @@ function useCommandRelay(options) {
37673
37673
  return;
37674
37674
  }
37675
37675
  let isMounted = true;
37676
+ let abortController = null;
37677
+ const closeStream = () => {
37678
+ if (abortController) {
37679
+ abortController.abort();
37680
+ abortController = null;
37681
+ }
37682
+ };
37683
+ const scheduleReconnect = () => {
37684
+ if (!isMounted) return;
37685
+ if (reconnectTimeoutRef.current) return;
37686
+ reconnectTimeoutRef.current = setTimeout(() => {
37687
+ reconnectTimeoutRef.current = null;
37688
+ if (isMounted) connect();
37689
+ }, SSE_RECONNECT_DELAY_MS);
37690
+ };
37676
37691
  const connect = () => {
37677
37692
  if (!isMounted) return;
37678
- const baseUrl = `${basePath}/commands/stream${tabId ? `?tabId=${encodeURIComponent(tabId)}` : ""}`;
37679
- const es = new EventSource(appendAuthQuery(baseUrl, authHeaderRef.current));
37680
- eventSourceRef.current = es;
37681
- es.onmessage = (event) => {
37682
- try {
37683
- const data = JSON.parse(event.data);
37684
- if (data.type === "connected") return;
37685
- if (data.commandId && data.action) processCommandRef.current(data);
37686
- } catch (e) {
37687
- console.error("[UIBridge] Failed to parse SSE message:", e);
37693
+ closeStream();
37694
+ const controller = new AbortController();
37695
+ abortController = controller;
37696
+ const url = `${basePath}/commands/stream${tabId ? `?tabId=${encodeURIComponent(tabId)}` : ""}`;
37697
+ const headers = { Accept: "text/event-stream" };
37698
+ const token = resolveAuthToken(authHeaderRef.current);
37699
+ if (token) headers.Authorization = `Bearer ${token}`;
37700
+ fetch(url, { method: "GET", headers, signal: controller.signal, cache: "no-store" }).then(async (resp) => {
37701
+ if (!resp.ok || !resp.body) {
37702
+ if (abortController === controller) abortController = null;
37703
+ scheduleReconnect();
37704
+ return;
37688
37705
  }
37689
- };
37690
- es.onerror = () => {
37691
- es.close();
37692
- if (eventSourceRef.current === es) eventSourceRef.current = null;
37693
- if (isMounted) {
37694
- reconnectTimeoutRef.current = setTimeout(() => {
37695
- reconnectTimeoutRef.current = null;
37696
- if (isMounted) connect();
37697
- }, SSE_RECONNECT_DELAY_MS);
37706
+ const reader = resp.body.getReader();
37707
+ const decoder = new TextDecoder();
37708
+ let buffer = "";
37709
+ try {
37710
+ for (; ; ) {
37711
+ const { value, done } = await reader.read();
37712
+ if (done) break;
37713
+ buffer += decoder.decode(value, { stream: true });
37714
+ let sep;
37715
+ while ((sep = buffer.indexOf("\n\n")) !== -1) {
37716
+ const block = buffer.slice(0, sep);
37717
+ buffer = buffer.slice(sep + 2);
37718
+ const data = parseSSEDataBlock(block);
37719
+ if (data == null) continue;
37720
+ try {
37721
+ const parsed = JSON.parse(data);
37722
+ if (parsed.type === "connected") continue;
37723
+ if (parsed.commandId && parsed.action) {
37724
+ processCommandRef.current(parsed);
37725
+ }
37726
+ } catch (e) {
37727
+ console.error("[UIBridge] Failed to parse SSE message:", e);
37728
+ }
37729
+ }
37730
+ }
37731
+ } catch {
37698
37732
  }
37699
- };
37733
+ if (abortController === controller) abortController = null;
37734
+ if (isMounted && !controller.signal.aborted) scheduleReconnect();
37735
+ }).catch(() => {
37736
+ if (abortController === controller) abortController = null;
37737
+ if (isMounted && !controller.signal.aborted) scheduleReconnect();
37738
+ });
37700
37739
  };
37701
37740
  const handleVisibility = () => {
37702
37741
  if (document.visibilityState === "visible") {
37703
- if (!eventSourceRef.current) {
37742
+ if (!abortController) {
37704
37743
  if (reconnectTimeoutRef.current) {
37705
37744
  clearTimeout(reconnectTimeoutRef.current);
37706
37745
  reconnectTimeoutRef.current = null;
@@ -37713,10 +37752,7 @@ function useCommandRelay(options) {
37713
37752
  document.addEventListener("visibilitychange", handleVisibility);
37714
37753
  forceReconnectRef.current = () => {
37715
37754
  if (!isMounted) return;
37716
- if (eventSourceRef.current) {
37717
- eventSourceRef.current.close();
37718
- eventSourceRef.current = null;
37719
- }
37755
+ closeStream();
37720
37756
  if (reconnectTimeoutRef.current) {
37721
37757
  clearTimeout(reconnectTimeoutRef.current);
37722
37758
  reconnectTimeoutRef.current = null;
@@ -37727,10 +37763,7 @@ function useCommandRelay(options) {
37727
37763
  isMounted = false;
37728
37764
  forceReconnectRef.current = () => {
37729
37765
  };
37730
- if (eventSourceRef.current) {
37731
- eventSourceRef.current.close();
37732
- eventSourceRef.current = null;
37733
- }
37766
+ closeStream();
37734
37767
  if (reconnectTimeoutRef.current) {
37735
37768
  clearTimeout(reconnectTimeoutRef.current);
37736
37769
  reconnectTimeoutRef.current = null;