@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.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.
|
|
25804
|
+
root.version = "0.11.0";
|
|
25805
25805
|
root.specs = { getGlobalSpecStore };
|
|
25806
25806
|
root.registry = internals.registry;
|
|
25807
25807
|
root.browserCapture = internals.browserCapture;
|
|
@@ -37521,10 +37521,44 @@ function safeJsonStringify(value) {
|
|
|
37521
37521
|
});
|
|
37522
37522
|
}
|
|
37523
37523
|
var HEARTBEAT_INTERVAL_MS = 1e4;
|
|
37524
|
+
function resolveAuthToken(authHeader) {
|
|
37525
|
+
if (!authHeader) return null;
|
|
37526
|
+
try {
|
|
37527
|
+
const value = authHeader();
|
|
37528
|
+
if (typeof value !== "string") return null;
|
|
37529
|
+
const trimmed = value.trim();
|
|
37530
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
37531
|
+
} catch {
|
|
37532
|
+
return null;
|
|
37533
|
+
}
|
|
37534
|
+
}
|
|
37535
|
+
function transportHeaders(authHeader) {
|
|
37536
|
+
const headers = {
|
|
37537
|
+
"Content-Type": "application/json"
|
|
37538
|
+
};
|
|
37539
|
+
const token = resolveAuthToken(authHeader);
|
|
37540
|
+
if (token) {
|
|
37541
|
+
headers.Authorization = `Bearer ${token}`;
|
|
37542
|
+
}
|
|
37543
|
+
return headers;
|
|
37544
|
+
}
|
|
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;
|
|
37555
|
+
}
|
|
37524
37556
|
function useCommandRelay(options) {
|
|
37525
37557
|
const enabled = options?.enabled ?? true;
|
|
37526
37558
|
const basePath = options?.basePath ?? "/api/ui-bridge";
|
|
37527
37559
|
const heartbeatIntervalMs = options?.heartbeatInterval ?? HEARTBEAT_INTERVAL_MS;
|
|
37560
|
+
const authHeaderRef = react.useRef(void 0);
|
|
37561
|
+
authHeaderRef.current = options?.authHeader;
|
|
37528
37562
|
const uiBridge = useUIBridge();
|
|
37529
37563
|
const context = useUIBridgeOptional();
|
|
37530
37564
|
const bridge2 = react.useMemo(
|
|
@@ -37557,7 +37591,6 @@ function useCommandRelay(options) {
|
|
|
37557
37591
|
}),
|
|
37558
37592
|
[uiBridge, context]
|
|
37559
37593
|
);
|
|
37560
|
-
const eventSourceRef = react.useRef(null);
|
|
37561
37594
|
const reconnectTimeoutRef = react.useRef(null);
|
|
37562
37595
|
const processCommandRef = react.useRef(() => {
|
|
37563
37596
|
});
|
|
@@ -37580,7 +37613,7 @@ function useCommandRelay(options) {
|
|
|
37580
37613
|
try {
|
|
37581
37614
|
await fetch(`${basePath}/commands`, {
|
|
37582
37615
|
method: "POST",
|
|
37583
|
-
headers:
|
|
37616
|
+
headers: transportHeaders(authHeaderRef.current),
|
|
37584
37617
|
body: safeJsonStringify({
|
|
37585
37618
|
commandId,
|
|
37586
37619
|
success: ok,
|
|
@@ -37633,10 +37666,6 @@ function useCommandRelay(options) {
|
|
|
37633
37666
|
processCommandRef.current = processCommand;
|
|
37634
37667
|
react.useEffect(() => {
|
|
37635
37668
|
if (!enabled) {
|
|
37636
|
-
if (eventSourceRef.current) {
|
|
37637
|
-
eventSourceRef.current.close();
|
|
37638
|
-
eventSourceRef.current = null;
|
|
37639
|
-
}
|
|
37640
37669
|
if (reconnectTimeoutRef.current) {
|
|
37641
37670
|
clearTimeout(reconnectTimeoutRef.current);
|
|
37642
37671
|
reconnectTimeoutRef.current = null;
|
|
@@ -37644,35 +37673,73 @@ function useCommandRelay(options) {
|
|
|
37644
37673
|
return;
|
|
37645
37674
|
}
|
|
37646
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
|
+
};
|
|
37647
37691
|
const connect = () => {
|
|
37648
37692
|
if (!isMounted) return;
|
|
37649
|
-
|
|
37650
|
-
|
|
37651
|
-
|
|
37652
|
-
|
|
37653
|
-
|
|
37654
|
-
|
|
37655
|
-
|
|
37656
|
-
|
|
37657
|
-
|
|
37658
|
-
|
|
37659
|
-
|
|
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;
|
|
37660
37705
|
}
|
|
37661
|
-
|
|
37662
|
-
|
|
37663
|
-
|
|
37664
|
-
|
|
37665
|
-
|
|
37666
|
-
|
|
37667
|
-
|
|
37668
|
-
|
|
37669
|
-
|
|
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 {
|
|
37670
37732
|
}
|
|
37671
|
-
|
|
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
|
+
});
|
|
37672
37739
|
};
|
|
37673
37740
|
const handleVisibility = () => {
|
|
37674
37741
|
if (document.visibilityState === "visible") {
|
|
37675
|
-
if (!
|
|
37742
|
+
if (!abortController) {
|
|
37676
37743
|
if (reconnectTimeoutRef.current) {
|
|
37677
37744
|
clearTimeout(reconnectTimeoutRef.current);
|
|
37678
37745
|
reconnectTimeoutRef.current = null;
|
|
@@ -37685,10 +37752,7 @@ function useCommandRelay(options) {
|
|
|
37685
37752
|
document.addEventListener("visibilitychange", handleVisibility);
|
|
37686
37753
|
forceReconnectRef.current = () => {
|
|
37687
37754
|
if (!isMounted) return;
|
|
37688
|
-
|
|
37689
|
-
eventSourceRef.current.close();
|
|
37690
|
-
eventSourceRef.current = null;
|
|
37691
|
-
}
|
|
37755
|
+
closeStream();
|
|
37692
37756
|
if (reconnectTimeoutRef.current) {
|
|
37693
37757
|
clearTimeout(reconnectTimeoutRef.current);
|
|
37694
37758
|
reconnectTimeoutRef.current = null;
|
|
@@ -37699,10 +37763,7 @@ function useCommandRelay(options) {
|
|
|
37699
37763
|
isMounted = false;
|
|
37700
37764
|
forceReconnectRef.current = () => {
|
|
37701
37765
|
};
|
|
37702
|
-
|
|
37703
|
-
eventSourceRef.current.close();
|
|
37704
|
-
eventSourceRef.current = null;
|
|
37705
|
-
}
|
|
37766
|
+
closeStream();
|
|
37706
37767
|
if (reconnectTimeoutRef.current) {
|
|
37707
37768
|
clearTimeout(reconnectTimeoutRef.current);
|
|
37708
37769
|
reconnectTimeoutRef.current = null;
|
|
@@ -37735,7 +37796,7 @@ function useCommandRelay(options) {
|
|
|
37735
37796
|
if (heartbeatVersion !== void 0) body.version = heartbeatVersion;
|
|
37736
37797
|
const resp = await fetch(`${basePath}/heartbeat`, {
|
|
37737
37798
|
method: "POST",
|
|
37738
|
-
headers:
|
|
37799
|
+
headers: transportHeaders(authHeaderRef.current),
|
|
37739
37800
|
body: JSON.stringify(body)
|
|
37740
37801
|
});
|
|
37741
37802
|
if (resp.ok) {
|
|
@@ -37866,7 +37927,8 @@ function CommandRelayListener(props) {
|
|
|
37866
37927
|
appType: props.appType,
|
|
37867
37928
|
framework: props.framework,
|
|
37868
37929
|
capabilities: props.capabilities,
|
|
37869
|
-
version: props.version
|
|
37930
|
+
version: props.version,
|
|
37931
|
+
authHeader: props.authHeader
|
|
37870
37932
|
};
|
|
37871
37933
|
useCommandRelay(options);
|
|
37872
37934
|
return null;
|