@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/react/index.mjs
CHANGED
|
@@ -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.
|
|
24949
|
+
root.version = "0.11.0";
|
|
24950
24950
|
root.specs = { getGlobalSpecStore };
|
|
24951
24951
|
root.registry = internals.registry;
|
|
24952
24952
|
root.browserCapture = internals.browserCapture;
|
|
@@ -32909,10 +32909,44 @@ function safeJsonStringify(value) {
|
|
|
32909
32909
|
});
|
|
32910
32910
|
}
|
|
32911
32911
|
var HEARTBEAT_INTERVAL_MS = 1e4;
|
|
32912
|
+
function resolveAuthToken(authHeader) {
|
|
32913
|
+
if (!authHeader) return null;
|
|
32914
|
+
try {
|
|
32915
|
+
const value = authHeader();
|
|
32916
|
+
if (typeof value !== "string") return null;
|
|
32917
|
+
const trimmed = value.trim();
|
|
32918
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
32919
|
+
} catch {
|
|
32920
|
+
return null;
|
|
32921
|
+
}
|
|
32922
|
+
}
|
|
32923
|
+
function transportHeaders(authHeader) {
|
|
32924
|
+
const headers = {
|
|
32925
|
+
"Content-Type": "application/json"
|
|
32926
|
+
};
|
|
32927
|
+
const token = resolveAuthToken(authHeader);
|
|
32928
|
+
if (token) {
|
|
32929
|
+
headers.Authorization = `Bearer ${token}`;
|
|
32930
|
+
}
|
|
32931
|
+
return headers;
|
|
32932
|
+
}
|
|
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;
|
|
32943
|
+
}
|
|
32912
32944
|
function useCommandRelay(options) {
|
|
32913
32945
|
const enabled = options?.enabled ?? true;
|
|
32914
32946
|
const basePath = options?.basePath ?? "/api/ui-bridge";
|
|
32915
32947
|
const heartbeatIntervalMs = options?.heartbeatInterval ?? HEARTBEAT_INTERVAL_MS;
|
|
32948
|
+
const authHeaderRef = useRef(void 0);
|
|
32949
|
+
authHeaderRef.current = options?.authHeader;
|
|
32916
32950
|
const uiBridge = useUIBridge();
|
|
32917
32951
|
const context = useUIBridgeOptional();
|
|
32918
32952
|
const bridge2 = useMemo(
|
|
@@ -32945,7 +32979,6 @@ function useCommandRelay(options) {
|
|
|
32945
32979
|
}),
|
|
32946
32980
|
[uiBridge, context]
|
|
32947
32981
|
);
|
|
32948
|
-
const eventSourceRef = useRef(null);
|
|
32949
32982
|
const reconnectTimeoutRef = useRef(null);
|
|
32950
32983
|
const processCommandRef = useRef(() => {
|
|
32951
32984
|
});
|
|
@@ -32968,7 +33001,7 @@ function useCommandRelay(options) {
|
|
|
32968
33001
|
try {
|
|
32969
33002
|
await fetch(`${basePath}/commands`, {
|
|
32970
33003
|
method: "POST",
|
|
32971
|
-
headers:
|
|
33004
|
+
headers: transportHeaders(authHeaderRef.current),
|
|
32972
33005
|
body: safeJsonStringify({
|
|
32973
33006
|
commandId,
|
|
32974
33007
|
success: ok,
|
|
@@ -33021,10 +33054,6 @@ function useCommandRelay(options) {
|
|
|
33021
33054
|
processCommandRef.current = processCommand;
|
|
33022
33055
|
useEffect(() => {
|
|
33023
33056
|
if (!enabled) {
|
|
33024
|
-
if (eventSourceRef.current) {
|
|
33025
|
-
eventSourceRef.current.close();
|
|
33026
|
-
eventSourceRef.current = null;
|
|
33027
|
-
}
|
|
33028
33057
|
if (reconnectTimeoutRef.current) {
|
|
33029
33058
|
clearTimeout(reconnectTimeoutRef.current);
|
|
33030
33059
|
reconnectTimeoutRef.current = null;
|
|
@@ -33032,35 +33061,73 @@ function useCommandRelay(options) {
|
|
|
33032
33061
|
return;
|
|
33033
33062
|
}
|
|
33034
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
|
+
};
|
|
33035
33079
|
const connect = () => {
|
|
33036
33080
|
if (!isMounted) return;
|
|
33037
|
-
|
|
33038
|
-
|
|
33039
|
-
|
|
33040
|
-
|
|
33041
|
-
|
|
33042
|
-
|
|
33043
|
-
|
|
33044
|
-
|
|
33045
|
-
|
|
33046
|
-
|
|
33047
|
-
|
|
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;
|
|
33048
33093
|
}
|
|
33049
|
-
|
|
33050
|
-
|
|
33051
|
-
|
|
33052
|
-
|
|
33053
|
-
|
|
33054
|
-
|
|
33055
|
-
|
|
33056
|
-
|
|
33057
|
-
|
|
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 {
|
|
33058
33120
|
}
|
|
33059
|
-
|
|
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
|
+
});
|
|
33060
33127
|
};
|
|
33061
33128
|
const handleVisibility = () => {
|
|
33062
33129
|
if (document.visibilityState === "visible") {
|
|
33063
|
-
if (!
|
|
33130
|
+
if (!abortController) {
|
|
33064
33131
|
if (reconnectTimeoutRef.current) {
|
|
33065
33132
|
clearTimeout(reconnectTimeoutRef.current);
|
|
33066
33133
|
reconnectTimeoutRef.current = null;
|
|
@@ -33073,10 +33140,7 @@ function useCommandRelay(options) {
|
|
|
33073
33140
|
document.addEventListener("visibilitychange", handleVisibility);
|
|
33074
33141
|
forceReconnectRef.current = () => {
|
|
33075
33142
|
if (!isMounted) return;
|
|
33076
|
-
|
|
33077
|
-
eventSourceRef.current.close();
|
|
33078
|
-
eventSourceRef.current = null;
|
|
33079
|
-
}
|
|
33143
|
+
closeStream();
|
|
33080
33144
|
if (reconnectTimeoutRef.current) {
|
|
33081
33145
|
clearTimeout(reconnectTimeoutRef.current);
|
|
33082
33146
|
reconnectTimeoutRef.current = null;
|
|
@@ -33087,10 +33151,7 @@ function useCommandRelay(options) {
|
|
|
33087
33151
|
isMounted = false;
|
|
33088
33152
|
forceReconnectRef.current = () => {
|
|
33089
33153
|
};
|
|
33090
|
-
|
|
33091
|
-
eventSourceRef.current.close();
|
|
33092
|
-
eventSourceRef.current = null;
|
|
33093
|
-
}
|
|
33154
|
+
closeStream();
|
|
33094
33155
|
if (reconnectTimeoutRef.current) {
|
|
33095
33156
|
clearTimeout(reconnectTimeoutRef.current);
|
|
33096
33157
|
reconnectTimeoutRef.current = null;
|
|
@@ -33123,7 +33184,7 @@ function useCommandRelay(options) {
|
|
|
33123
33184
|
if (heartbeatVersion !== void 0) body.version = heartbeatVersion;
|
|
33124
33185
|
const resp = await fetch(`${basePath}/heartbeat`, {
|
|
33125
33186
|
method: "POST",
|
|
33126
|
-
headers:
|
|
33187
|
+
headers: transportHeaders(authHeaderRef.current),
|
|
33127
33188
|
body: JSON.stringify(body)
|
|
33128
33189
|
});
|
|
33129
33190
|
if (resp.ok) {
|
|
@@ -33254,7 +33315,8 @@ function CommandRelayListener(props) {
|
|
|
33254
33315
|
appType: props.appType,
|
|
33255
33316
|
framework: props.framework,
|
|
33256
33317
|
capabilities: props.capabilities,
|
|
33257
|
-
version: props.version
|
|
33318
|
+
version: props.version,
|
|
33319
|
+
authHeader: props.authHeader
|
|
33258
33320
|
};
|
|
33259
33321
|
useCommandRelay(options);
|
|
33260
33322
|
return null;
|