@qontinui/ui-bridge 0.9.1 → 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 +101 -39
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +101 -39
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +32 -0
- package/dist/react/index.d.ts +32 -0
- package/dist/react/index.js +101 -39
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +101 -39
- package/dist/react/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -25799,7 +25799,7 @@ function exposeProviderOnWindow(internals) {
|
|
|
25799
25799
|
if (typeof window === "undefined") return;
|
|
25800
25800
|
const w = window;
|
|
25801
25801
|
const root = w.__UI_BRIDGE__ ?? (w.__UI_BRIDGE__ = {});
|
|
25802
|
-
root.version = "0.
|
|
25802
|
+
root.version = "0.11.0";
|
|
25803
25803
|
root.specs = { getGlobalSpecStore };
|
|
25804
25804
|
root.registry = internals.registry;
|
|
25805
25805
|
root.browserCapture = internals.browserCapture;
|
|
@@ -37519,10 +37519,44 @@ function safeJsonStringify(value) {
|
|
|
37519
37519
|
});
|
|
37520
37520
|
}
|
|
37521
37521
|
var HEARTBEAT_INTERVAL_MS = 1e4;
|
|
37522
|
+
function resolveAuthToken(authHeader) {
|
|
37523
|
+
if (!authHeader) return null;
|
|
37524
|
+
try {
|
|
37525
|
+
const value = authHeader();
|
|
37526
|
+
if (typeof value !== "string") return null;
|
|
37527
|
+
const trimmed = value.trim();
|
|
37528
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
37529
|
+
} catch {
|
|
37530
|
+
return null;
|
|
37531
|
+
}
|
|
37532
|
+
}
|
|
37533
|
+
function transportHeaders(authHeader) {
|
|
37534
|
+
const headers = {
|
|
37535
|
+
"Content-Type": "application/json"
|
|
37536
|
+
};
|
|
37537
|
+
const token = resolveAuthToken(authHeader);
|
|
37538
|
+
if (token) {
|
|
37539
|
+
headers.Authorization = `Bearer ${token}`;
|
|
37540
|
+
}
|
|
37541
|
+
return headers;
|
|
37542
|
+
}
|
|
37543
|
+
function parseSSEDataBlock(block) {
|
|
37544
|
+
const dataLines = [];
|
|
37545
|
+
for (const line of block.split("\n")) {
|
|
37546
|
+
if (line.startsWith(":")) continue;
|
|
37547
|
+
if (!line.startsWith("data:")) continue;
|
|
37548
|
+
let value = line.slice(5);
|
|
37549
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
37550
|
+
dataLines.push(value);
|
|
37551
|
+
}
|
|
37552
|
+
return dataLines.length > 0 ? dataLines.join("\n") : null;
|
|
37553
|
+
}
|
|
37522
37554
|
function useCommandRelay(options) {
|
|
37523
37555
|
const enabled = options?.enabled ?? true;
|
|
37524
37556
|
const basePath = options?.basePath ?? "/api/ui-bridge";
|
|
37525
37557
|
const heartbeatIntervalMs = options?.heartbeatInterval ?? HEARTBEAT_INTERVAL_MS;
|
|
37558
|
+
const authHeaderRef = useRef(void 0);
|
|
37559
|
+
authHeaderRef.current = options?.authHeader;
|
|
37526
37560
|
const uiBridge = useUIBridge();
|
|
37527
37561
|
const context = useUIBridgeOptional();
|
|
37528
37562
|
const bridge2 = useMemo(
|
|
@@ -37555,7 +37589,6 @@ function useCommandRelay(options) {
|
|
|
37555
37589
|
}),
|
|
37556
37590
|
[uiBridge, context]
|
|
37557
37591
|
);
|
|
37558
|
-
const eventSourceRef = useRef(null);
|
|
37559
37592
|
const reconnectTimeoutRef = useRef(null);
|
|
37560
37593
|
const processCommandRef = useRef(() => {
|
|
37561
37594
|
});
|
|
@@ -37578,7 +37611,7 @@ function useCommandRelay(options) {
|
|
|
37578
37611
|
try {
|
|
37579
37612
|
await fetch(`${basePath}/commands`, {
|
|
37580
37613
|
method: "POST",
|
|
37581
|
-
headers:
|
|
37614
|
+
headers: transportHeaders(authHeaderRef.current),
|
|
37582
37615
|
body: safeJsonStringify({
|
|
37583
37616
|
commandId,
|
|
37584
37617
|
success: ok,
|
|
@@ -37631,10 +37664,6 @@ function useCommandRelay(options) {
|
|
|
37631
37664
|
processCommandRef.current = processCommand;
|
|
37632
37665
|
useEffect(() => {
|
|
37633
37666
|
if (!enabled) {
|
|
37634
|
-
if (eventSourceRef.current) {
|
|
37635
|
-
eventSourceRef.current.close();
|
|
37636
|
-
eventSourceRef.current = null;
|
|
37637
|
-
}
|
|
37638
37667
|
if (reconnectTimeoutRef.current) {
|
|
37639
37668
|
clearTimeout(reconnectTimeoutRef.current);
|
|
37640
37669
|
reconnectTimeoutRef.current = null;
|
|
@@ -37642,35 +37671,73 @@ function useCommandRelay(options) {
|
|
|
37642
37671
|
return;
|
|
37643
37672
|
}
|
|
37644
37673
|
let isMounted = true;
|
|
37674
|
+
let abortController = null;
|
|
37675
|
+
const closeStream = () => {
|
|
37676
|
+
if (abortController) {
|
|
37677
|
+
abortController.abort();
|
|
37678
|
+
abortController = null;
|
|
37679
|
+
}
|
|
37680
|
+
};
|
|
37681
|
+
const scheduleReconnect = () => {
|
|
37682
|
+
if (!isMounted) return;
|
|
37683
|
+
if (reconnectTimeoutRef.current) return;
|
|
37684
|
+
reconnectTimeoutRef.current = setTimeout(() => {
|
|
37685
|
+
reconnectTimeoutRef.current = null;
|
|
37686
|
+
if (isMounted) connect();
|
|
37687
|
+
}, SSE_RECONNECT_DELAY_MS);
|
|
37688
|
+
};
|
|
37645
37689
|
const connect = () => {
|
|
37646
37690
|
if (!isMounted) return;
|
|
37647
|
-
|
|
37648
|
-
|
|
37649
|
-
|
|
37650
|
-
|
|
37651
|
-
|
|
37652
|
-
|
|
37653
|
-
|
|
37654
|
-
|
|
37655
|
-
|
|
37656
|
-
|
|
37657
|
-
|
|
37691
|
+
closeStream();
|
|
37692
|
+
const controller = new AbortController();
|
|
37693
|
+
abortController = controller;
|
|
37694
|
+
const url = `${basePath}/commands/stream${tabId ? `?tabId=${encodeURIComponent(tabId)}` : ""}`;
|
|
37695
|
+
const headers = { Accept: "text/event-stream" };
|
|
37696
|
+
const token = resolveAuthToken(authHeaderRef.current);
|
|
37697
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
37698
|
+
fetch(url, { method: "GET", headers, signal: controller.signal, cache: "no-store" }).then(async (resp) => {
|
|
37699
|
+
if (!resp.ok || !resp.body) {
|
|
37700
|
+
if (abortController === controller) abortController = null;
|
|
37701
|
+
scheduleReconnect();
|
|
37702
|
+
return;
|
|
37658
37703
|
}
|
|
37659
|
-
|
|
37660
|
-
|
|
37661
|
-
|
|
37662
|
-
|
|
37663
|
-
|
|
37664
|
-
|
|
37665
|
-
|
|
37666
|
-
|
|
37667
|
-
|
|
37704
|
+
const reader = resp.body.getReader();
|
|
37705
|
+
const decoder = new TextDecoder();
|
|
37706
|
+
let buffer = "";
|
|
37707
|
+
try {
|
|
37708
|
+
for (; ; ) {
|
|
37709
|
+
const { value, done } = await reader.read();
|
|
37710
|
+
if (done) break;
|
|
37711
|
+
buffer += decoder.decode(value, { stream: true });
|
|
37712
|
+
let sep;
|
|
37713
|
+
while ((sep = buffer.indexOf("\n\n")) !== -1) {
|
|
37714
|
+
const block = buffer.slice(0, sep);
|
|
37715
|
+
buffer = buffer.slice(sep + 2);
|
|
37716
|
+
const data = parseSSEDataBlock(block);
|
|
37717
|
+
if (data == null) continue;
|
|
37718
|
+
try {
|
|
37719
|
+
const parsed = JSON.parse(data);
|
|
37720
|
+
if (parsed.type === "connected") continue;
|
|
37721
|
+
if (parsed.commandId && parsed.action) {
|
|
37722
|
+
processCommandRef.current(parsed);
|
|
37723
|
+
}
|
|
37724
|
+
} catch (e) {
|
|
37725
|
+
console.error("[UIBridge] Failed to parse SSE message:", e);
|
|
37726
|
+
}
|
|
37727
|
+
}
|
|
37728
|
+
}
|
|
37729
|
+
} catch {
|
|
37668
37730
|
}
|
|
37669
|
-
|
|
37731
|
+
if (abortController === controller) abortController = null;
|
|
37732
|
+
if (isMounted && !controller.signal.aborted) scheduleReconnect();
|
|
37733
|
+
}).catch(() => {
|
|
37734
|
+
if (abortController === controller) abortController = null;
|
|
37735
|
+
if (isMounted && !controller.signal.aborted) scheduleReconnect();
|
|
37736
|
+
});
|
|
37670
37737
|
};
|
|
37671
37738
|
const handleVisibility = () => {
|
|
37672
37739
|
if (document.visibilityState === "visible") {
|
|
37673
|
-
if (!
|
|
37740
|
+
if (!abortController) {
|
|
37674
37741
|
if (reconnectTimeoutRef.current) {
|
|
37675
37742
|
clearTimeout(reconnectTimeoutRef.current);
|
|
37676
37743
|
reconnectTimeoutRef.current = null;
|
|
@@ -37683,10 +37750,7 @@ function useCommandRelay(options) {
|
|
|
37683
37750
|
document.addEventListener("visibilitychange", handleVisibility);
|
|
37684
37751
|
forceReconnectRef.current = () => {
|
|
37685
37752
|
if (!isMounted) return;
|
|
37686
|
-
|
|
37687
|
-
eventSourceRef.current.close();
|
|
37688
|
-
eventSourceRef.current = null;
|
|
37689
|
-
}
|
|
37753
|
+
closeStream();
|
|
37690
37754
|
if (reconnectTimeoutRef.current) {
|
|
37691
37755
|
clearTimeout(reconnectTimeoutRef.current);
|
|
37692
37756
|
reconnectTimeoutRef.current = null;
|
|
@@ -37697,10 +37761,7 @@ function useCommandRelay(options) {
|
|
|
37697
37761
|
isMounted = false;
|
|
37698
37762
|
forceReconnectRef.current = () => {
|
|
37699
37763
|
};
|
|
37700
|
-
|
|
37701
|
-
eventSourceRef.current.close();
|
|
37702
|
-
eventSourceRef.current = null;
|
|
37703
|
-
}
|
|
37764
|
+
closeStream();
|
|
37704
37765
|
if (reconnectTimeoutRef.current) {
|
|
37705
37766
|
clearTimeout(reconnectTimeoutRef.current);
|
|
37706
37767
|
reconnectTimeoutRef.current = null;
|
|
@@ -37733,7 +37794,7 @@ function useCommandRelay(options) {
|
|
|
37733
37794
|
if (heartbeatVersion !== void 0) body.version = heartbeatVersion;
|
|
37734
37795
|
const resp = await fetch(`${basePath}/heartbeat`, {
|
|
37735
37796
|
method: "POST",
|
|
37736
|
-
headers:
|
|
37797
|
+
headers: transportHeaders(authHeaderRef.current),
|
|
37737
37798
|
body: JSON.stringify(body)
|
|
37738
37799
|
});
|
|
37739
37800
|
if (resp.ok) {
|
|
@@ -37864,7 +37925,8 @@ function CommandRelayListener(props) {
|
|
|
37864
37925
|
appType: props.appType,
|
|
37865
37926
|
framework: props.framework,
|
|
37866
37927
|
capabilities: props.capabilities,
|
|
37867
|
-
version: props.version
|
|
37928
|
+
version: props.version,
|
|
37929
|
+
authHeader: props.authHeader
|
|
37868
37930
|
};
|
|
37869
37931
|
useCommandRelay(options);
|
|
37870
37932
|
return null;
|