@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.
@@ -24946,7 +24946,7 @@ function exposeProviderOnWindow(internals) {
24946
24946
  if (typeof window === "undefined") return;
24947
24947
  const w = window;
24948
24948
  const root = w.__UI_BRIDGE__ ?? (w.__UI_BRIDGE__ = {});
24949
- root.version = "0.10.0";
24949
+ root.version = "0.11.0";
24950
24950
  root.specs = { getGlobalSpecStore };
24951
24951
  root.registry = internals.registry;
24952
24952
  root.browserCapture = internals.browserCapture;
@@ -32930,11 +32930,16 @@ function transportHeaders(authHeader) {
32930
32930
  }
32931
32931
  return headers;
32932
32932
  }
32933
- function appendAuthQuery(url, authHeader) {
32934
- const token = resolveAuthToken(authHeader);
32935
- if (!token) return url;
32936
- const sep = url.includes("?") ? "&" : "?";
32937
- return `${url}${sep}_auth=${encodeURIComponent(token)}`;
32933
+ function parseSSEDataBlock(block) {
32934
+ const dataLines = [];
32935
+ for (const line of block.split("\n")) {
32936
+ if (line.startsWith(":")) continue;
32937
+ if (!line.startsWith("data:")) continue;
32938
+ let value = line.slice(5);
32939
+ if (value.startsWith(" ")) value = value.slice(1);
32940
+ dataLines.push(value);
32941
+ }
32942
+ return dataLines.length > 0 ? dataLines.join("\n") : null;
32938
32943
  }
32939
32944
  function useCommandRelay(options) {
32940
32945
  const enabled = options?.enabled ?? true;
@@ -32974,7 +32979,6 @@ function useCommandRelay(options) {
32974
32979
  }),
32975
32980
  [uiBridge, context]
32976
32981
  );
32977
- const eventSourceRef = useRef(null);
32978
32982
  const reconnectTimeoutRef = useRef(null);
32979
32983
  const processCommandRef = useRef(() => {
32980
32984
  });
@@ -33050,10 +33054,6 @@ function useCommandRelay(options) {
33050
33054
  processCommandRef.current = processCommand;
33051
33055
  useEffect(() => {
33052
33056
  if (!enabled) {
33053
- if (eventSourceRef.current) {
33054
- eventSourceRef.current.close();
33055
- eventSourceRef.current = null;
33056
- }
33057
33057
  if (reconnectTimeoutRef.current) {
33058
33058
  clearTimeout(reconnectTimeoutRef.current);
33059
33059
  reconnectTimeoutRef.current = null;
@@ -33061,34 +33061,73 @@ function useCommandRelay(options) {
33061
33061
  return;
33062
33062
  }
33063
33063
  let isMounted = true;
33064
+ let abortController = null;
33065
+ const closeStream = () => {
33066
+ if (abortController) {
33067
+ abortController.abort();
33068
+ abortController = null;
33069
+ }
33070
+ };
33071
+ const scheduleReconnect = () => {
33072
+ if (!isMounted) return;
33073
+ if (reconnectTimeoutRef.current) return;
33074
+ reconnectTimeoutRef.current = setTimeout(() => {
33075
+ reconnectTimeoutRef.current = null;
33076
+ if (isMounted) connect();
33077
+ }, SSE_RECONNECT_DELAY_MS);
33078
+ };
33064
33079
  const connect = () => {
33065
33080
  if (!isMounted) return;
33066
- const baseUrl = `${basePath}/commands/stream${tabId ? `?tabId=${encodeURIComponent(tabId)}` : ""}`;
33067
- const es = new EventSource(appendAuthQuery(baseUrl, authHeaderRef.current));
33068
- eventSourceRef.current = es;
33069
- es.onmessage = (event) => {
33070
- try {
33071
- const data = JSON.parse(event.data);
33072
- if (data.type === "connected") return;
33073
- if (data.commandId && data.action) processCommandRef.current(data);
33074
- } catch (e) {
33075
- console.error("[UIBridge] Failed to parse SSE message:", e);
33081
+ closeStream();
33082
+ const controller = new AbortController();
33083
+ abortController = controller;
33084
+ const url = `${basePath}/commands/stream${tabId ? `?tabId=${encodeURIComponent(tabId)}` : ""}`;
33085
+ const headers = { Accept: "text/event-stream" };
33086
+ const token = resolveAuthToken(authHeaderRef.current);
33087
+ if (token) headers.Authorization = `Bearer ${token}`;
33088
+ fetch(url, { method: "GET", headers, signal: controller.signal, cache: "no-store" }).then(async (resp) => {
33089
+ if (!resp.ok || !resp.body) {
33090
+ if (abortController === controller) abortController = null;
33091
+ scheduleReconnect();
33092
+ return;
33076
33093
  }
33077
- };
33078
- es.onerror = () => {
33079
- es.close();
33080
- if (eventSourceRef.current === es) eventSourceRef.current = null;
33081
- if (isMounted) {
33082
- reconnectTimeoutRef.current = setTimeout(() => {
33083
- reconnectTimeoutRef.current = null;
33084
- if (isMounted) connect();
33085
- }, SSE_RECONNECT_DELAY_MS);
33094
+ const reader = resp.body.getReader();
33095
+ const decoder = new TextDecoder();
33096
+ let buffer = "";
33097
+ try {
33098
+ for (; ; ) {
33099
+ const { value, done } = await reader.read();
33100
+ if (done) break;
33101
+ buffer += decoder.decode(value, { stream: true });
33102
+ let sep;
33103
+ while ((sep = buffer.indexOf("\n\n")) !== -1) {
33104
+ const block = buffer.slice(0, sep);
33105
+ buffer = buffer.slice(sep + 2);
33106
+ const data = parseSSEDataBlock(block);
33107
+ if (data == null) continue;
33108
+ try {
33109
+ const parsed = JSON.parse(data);
33110
+ if (parsed.type === "connected") continue;
33111
+ if (parsed.commandId && parsed.action) {
33112
+ processCommandRef.current(parsed);
33113
+ }
33114
+ } catch (e) {
33115
+ console.error("[UIBridge] Failed to parse SSE message:", e);
33116
+ }
33117
+ }
33118
+ }
33119
+ } catch {
33086
33120
  }
33087
- };
33121
+ if (abortController === controller) abortController = null;
33122
+ if (isMounted && !controller.signal.aborted) scheduleReconnect();
33123
+ }).catch(() => {
33124
+ if (abortController === controller) abortController = null;
33125
+ if (isMounted && !controller.signal.aborted) scheduleReconnect();
33126
+ });
33088
33127
  };
33089
33128
  const handleVisibility = () => {
33090
33129
  if (document.visibilityState === "visible") {
33091
- if (!eventSourceRef.current) {
33130
+ if (!abortController) {
33092
33131
  if (reconnectTimeoutRef.current) {
33093
33132
  clearTimeout(reconnectTimeoutRef.current);
33094
33133
  reconnectTimeoutRef.current = null;
@@ -33101,10 +33140,7 @@ function useCommandRelay(options) {
33101
33140
  document.addEventListener("visibilitychange", handleVisibility);
33102
33141
  forceReconnectRef.current = () => {
33103
33142
  if (!isMounted) return;
33104
- if (eventSourceRef.current) {
33105
- eventSourceRef.current.close();
33106
- eventSourceRef.current = null;
33107
- }
33143
+ closeStream();
33108
33144
  if (reconnectTimeoutRef.current) {
33109
33145
  clearTimeout(reconnectTimeoutRef.current);
33110
33146
  reconnectTimeoutRef.current = null;
@@ -33115,10 +33151,7 @@ function useCommandRelay(options) {
33115
33151
  isMounted = false;
33116
33152
  forceReconnectRef.current = () => {
33117
33153
  };
33118
- if (eventSourceRef.current) {
33119
- eventSourceRef.current.close();
33120
- eventSourceRef.current = null;
33121
- }
33154
+ closeStream();
33122
33155
  if (reconnectTimeoutRef.current) {
33123
33156
  clearTimeout(reconnectTimeoutRef.current);
33124
33157
  reconnectTimeoutRef.current = null;