@qontinui/ui-bridge-native 0.6.9 → 0.6.11
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/control/action-executor.d.ts.map +1 -1
- package/dist/control/index.js +2 -1
- package/dist/control/index.js.map +1 -1
- package/dist/control/index.mjs +2 -1
- package/dist/control/index.mjs.map +1 -1
- package/dist/core/index.js +232 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +232 -2
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/registry.d.ts +46 -1
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/types.d.ts +114 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/vision-fields.d.ts +142 -0
- package/dist/core/vision-fields.d.ts.map +1 -0
- package/dist/debug/index.js.map +1 -1
- package/dist/debug/index.mjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +371 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +365 -22
- package/dist/index.mjs.map +1 -1
- package/dist/react/UIBridgeNativeProvider.d.ts.map +1 -1
- package/dist/react/index.js +323 -20
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +324 -21
- package/dist/react/index.mjs.map +1 -1
- package/dist/react/useUIElement.d.ts.map +1 -1
- package/dist/server/bind-retry.d.ts +64 -0
- package/dist/server/bind-retry.d.ts.map +1 -0
- package/dist/server/handlers.d.ts.map +1 -1
- package/dist/server/http-server.d.ts +9 -1
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +275 -13
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +274 -14
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/observability.d.ts +4 -3
- package/dist/server/observability.d.ts.map +1 -1
- package/dist/server/types.d.ts +10 -0
- package/dist/server/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createContext, useRef, useState, useCallback, useEffect, useMemo, use } from 'react';
|
|
2
|
-
import { Dimensions, StyleSheet, Platform, TouchableOpacity, Text, Modal, View, ScrollView } from 'react-native';
|
|
2
|
+
import { Dimensions, StyleSheet, Platform, PixelRatio, TouchableOpacity, Text, Modal, View, ScrollView } from 'react-native';
|
|
3
3
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
@@ -9,6 +9,179 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
9
9
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
10
10
|
});
|
|
11
11
|
|
|
12
|
+
// src/core/vision-fields.ts
|
|
13
|
+
var INTERACTIVE_HANDLER_NAMES = /* @__PURE__ */ new Set([
|
|
14
|
+
"onPress",
|
|
15
|
+
"onPressIn",
|
|
16
|
+
"onPressOut",
|
|
17
|
+
"onLongPress",
|
|
18
|
+
"onChangeText",
|
|
19
|
+
"onValueChange",
|
|
20
|
+
"onChange",
|
|
21
|
+
"onSubmitEditing",
|
|
22
|
+
"onClick",
|
|
23
|
+
"onTouchStart",
|
|
24
|
+
"onTouchEnd"
|
|
25
|
+
]);
|
|
26
|
+
var INTERACTIVE_TYPES = /* @__PURE__ */ new Set([
|
|
27
|
+
"button",
|
|
28
|
+
"input",
|
|
29
|
+
"switch",
|
|
30
|
+
"checkbox",
|
|
31
|
+
"radio",
|
|
32
|
+
"touchable",
|
|
33
|
+
"pressable",
|
|
34
|
+
"listItem"
|
|
35
|
+
]);
|
|
36
|
+
function roleForType(type) {
|
|
37
|
+
switch (type) {
|
|
38
|
+
case "button":
|
|
39
|
+
case "touchable":
|
|
40
|
+
case "pressable":
|
|
41
|
+
return "button";
|
|
42
|
+
case "input":
|
|
43
|
+
return "textbox";
|
|
44
|
+
case "text":
|
|
45
|
+
return "text";
|
|
46
|
+
case "switch":
|
|
47
|
+
return "switch";
|
|
48
|
+
case "checkbox":
|
|
49
|
+
return "checkbox";
|
|
50
|
+
case "radio":
|
|
51
|
+
return "radio";
|
|
52
|
+
case "image":
|
|
53
|
+
return "img";
|
|
54
|
+
case "list":
|
|
55
|
+
return "list";
|
|
56
|
+
case "listItem":
|
|
57
|
+
return "listitem";
|
|
58
|
+
case "modal":
|
|
59
|
+
return "dialog";
|
|
60
|
+
case "scroll":
|
|
61
|
+
case "view":
|
|
62
|
+
case "custom":
|
|
63
|
+
default:
|
|
64
|
+
return void 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function deriveInteractable(type, handlerNames) {
|
|
68
|
+
if (INTERACTIVE_TYPES.has(type)) return true;
|
|
69
|
+
if (handlerNames) {
|
|
70
|
+
for (const name of handlerNames) {
|
|
71
|
+
if (INTERACTIVE_HANDLER_NAMES.has(name)) return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
function deriveText(state, label) {
|
|
77
|
+
const candidates = [state.value, state.textContent, label];
|
|
78
|
+
for (const c of candidates) {
|
|
79
|
+
if (typeof c === "string") {
|
|
80
|
+
const trimmed = c.trim();
|
|
81
|
+
if (trimmed.length > 0) return trimmed;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return void 0;
|
|
85
|
+
}
|
|
86
|
+
var NAMED_COLORS = {
|
|
87
|
+
black: { r: 0, g: 0, b: 0 },
|
|
88
|
+
white: { r: 255, g: 255, b: 255 },
|
|
89
|
+
red: { r: 255, g: 0, b: 0 },
|
|
90
|
+
green: { r: 0, g: 128, b: 0 },
|
|
91
|
+
blue: { r: 0, g: 0, b: 255 },
|
|
92
|
+
gray: { r: 128, g: 128, b: 128 },
|
|
93
|
+
grey: { r: 128, g: 128, b: 128 },
|
|
94
|
+
silver: { r: 192, g: 192, b: 192 },
|
|
95
|
+
transparent: { r: 0, g: 0, b: 0 },
|
|
96
|
+
orange: { r: 255, g: 165, b: 0 },
|
|
97
|
+
yellow: { r: 255, g: 255, b: 0 },
|
|
98
|
+
purple: { r: 128, g: 0, b: 128 },
|
|
99
|
+
navy: { r: 0, g: 0, b: 128 }
|
|
100
|
+
};
|
|
101
|
+
var clamp255 = (n) => Math.max(0, Math.min(255, Math.round(n)));
|
|
102
|
+
function parseColorToRgb(input) {
|
|
103
|
+
if (typeof input !== "string") return void 0;
|
|
104
|
+
const raw = input.trim().toLowerCase();
|
|
105
|
+
if (raw.length === 0) return void 0;
|
|
106
|
+
if (Object.prototype.hasOwnProperty.call(NAMED_COLORS, raw)) {
|
|
107
|
+
return NAMED_COLORS[raw];
|
|
108
|
+
}
|
|
109
|
+
if (raw.startsWith("#")) {
|
|
110
|
+
const hex = raw.slice(1);
|
|
111
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
112
|
+
const r = parseInt(hex[0] + hex[0], 16);
|
|
113
|
+
const g = parseInt(hex[1] + hex[1], 16);
|
|
114
|
+
const b = parseInt(hex[2] + hex[2], 16);
|
|
115
|
+
if ([r, g, b].every((v) => Number.isFinite(v))) return { r, g, b };
|
|
116
|
+
return void 0;
|
|
117
|
+
}
|
|
118
|
+
if (hex.length === 6 || hex.length === 8) {
|
|
119
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
120
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
121
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
122
|
+
if ([r, g, b].every((v) => Number.isFinite(v))) return { r, g, b };
|
|
123
|
+
return void 0;
|
|
124
|
+
}
|
|
125
|
+
return void 0;
|
|
126
|
+
}
|
|
127
|
+
const rgbMatch = raw.match(/^rgba?\(([^)]+)\)$/);
|
|
128
|
+
if (rgbMatch) {
|
|
129
|
+
const parts = rgbMatch[1].split(",").map((p) => p.trim());
|
|
130
|
+
if (parts.length < 3) return void 0;
|
|
131
|
+
const channel = (p) => {
|
|
132
|
+
if (p.endsWith("%")) {
|
|
133
|
+
const pct = parseFloat(p.slice(0, -1));
|
|
134
|
+
if (!Number.isFinite(pct)) return void 0;
|
|
135
|
+
return clamp255(pct / 100 * 255);
|
|
136
|
+
}
|
|
137
|
+
const n = parseFloat(p);
|
|
138
|
+
if (!Number.isFinite(n)) return void 0;
|
|
139
|
+
return clamp255(n);
|
|
140
|
+
};
|
|
141
|
+
const r = channel(parts[0]);
|
|
142
|
+
const g = channel(parts[1]);
|
|
143
|
+
const b = channel(parts[2]);
|
|
144
|
+
if (r === void 0 || g === void 0 || b === void 0) return void 0;
|
|
145
|
+
return { r, g, b };
|
|
146
|
+
}
|
|
147
|
+
return void 0;
|
|
148
|
+
}
|
|
149
|
+
function parseFontSizePx(input) {
|
|
150
|
+
if (typeof input === "number") {
|
|
151
|
+
return Number.isFinite(input) && input > 0 ? input : void 0;
|
|
152
|
+
}
|
|
153
|
+
if (typeof input === "string") {
|
|
154
|
+
const m = input.trim().match(/^(-?\d*\.?\d+)/);
|
|
155
|
+
if (m) {
|
|
156
|
+
const n = parseFloat(m[1]);
|
|
157
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return void 0;
|
|
161
|
+
}
|
|
162
|
+
function projectVisionFields(args) {
|
|
163
|
+
const { type, label, handlerNames, state, flatStyle } = args;
|
|
164
|
+
const fields = {
|
|
165
|
+
interactable: deriveInteractable(type, handlerNames)
|
|
166
|
+
};
|
|
167
|
+
const text = deriveText(state, label);
|
|
168
|
+
if (text !== void 0) fields.text = text;
|
|
169
|
+
const role = roleForType(type);
|
|
170
|
+
if (role !== void 0) fields.role = role;
|
|
171
|
+
if (flatStyle) {
|
|
172
|
+
const fg = parseColorToRgb(flatStyle.color);
|
|
173
|
+
if (fg !== void 0) fields.fg_color = fg;
|
|
174
|
+
const bg = parseColorToRgb(flatStyle.backgroundColor);
|
|
175
|
+
if (bg !== void 0) fields.bg_color = bg;
|
|
176
|
+
const size = parseFontSizePx(flatStyle.fontSize);
|
|
177
|
+
if (size !== void 0) fields.font_size_px = size;
|
|
178
|
+
if (typeof flatStyle.fontFamily === "string" && flatStyle.fontFamily.trim().length > 0) {
|
|
179
|
+
fields.font_family = flatStyle.fontFamily;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return fields;
|
|
183
|
+
}
|
|
184
|
+
|
|
12
185
|
// src/core/registry.ts
|
|
13
186
|
function extractHandlerNames(props) {
|
|
14
187
|
if (!props) return [];
|
|
@@ -18,6 +191,25 @@ function matchesCurrentRoute(registrationRoute, currentRoute) {
|
|
|
18
191
|
if (registrationRoute == null || registrationRoute === "") return true;
|
|
19
192
|
return registrationRoute === currentRoute;
|
|
20
193
|
}
|
|
194
|
+
function projectBbox(state, visibility, pixelRatio = 1) {
|
|
195
|
+
if (visibility !== "visible") return void 0;
|
|
196
|
+
const layout = state.layout;
|
|
197
|
+
if (!layout) return void 0;
|
|
198
|
+
const ratio = pixelRatio > 0 && Number.isFinite(pixelRatio) ? pixelRatio : 1;
|
|
199
|
+
const toPx = (v) => Math.max(0, Math.round(v * ratio));
|
|
200
|
+
const hasPage = typeof layout.pageX === "number" && typeof layout.pageY === "number" && Number.isFinite(layout.pageX) && Number.isFinite(layout.pageY);
|
|
201
|
+
const originX = hasPage ? layout.pageX : layout.x;
|
|
202
|
+
const originY = hasPage ? layout.pageY : layout.y;
|
|
203
|
+
if (typeof originX !== "number" || typeof originY !== "number" || typeof layout.width !== "number" || typeof layout.height !== "number" || !Number.isFinite(originX) || !Number.isFinite(originY) || !Number.isFinite(layout.width) || !Number.isFinite(layout.height)) {
|
|
204
|
+
return void 0;
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
x: toPx(originX),
|
|
208
|
+
y: toPx(originY),
|
|
209
|
+
w: toPx(layout.width),
|
|
210
|
+
h: toPx(layout.height)
|
|
211
|
+
};
|
|
212
|
+
}
|
|
21
213
|
function inferActions(type) {
|
|
22
214
|
const baseActions = ["focus", "blur"];
|
|
23
215
|
switch (type) {
|
|
@@ -82,6 +274,15 @@ var NativeUIBridgeRegistry = class {
|
|
|
82
274
|
* already installs.
|
|
83
275
|
*/
|
|
84
276
|
this.routeProvider = null;
|
|
277
|
+
/**
|
|
278
|
+
* Device pixel ratio used to project `state.layout` (logical dp) into the
|
|
279
|
+
* physical-pixel `bbox` emitted on each snapshot element. Defaults to `1`
|
|
280
|
+
* (dp ≈ px) so callers/tests that never inject a ratio still get a usable
|
|
281
|
+
* box; the React provider wires the real `PixelRatio.get()` via
|
|
282
|
+
* {@link setPixelRatio}. Read from a non-React core module on purpose — see
|
|
283
|
+
* the `projectBbox` doc comment for why we don't import `react-native` here.
|
|
284
|
+
*/
|
|
285
|
+
this.pixelRatio = 1;
|
|
85
286
|
/**
|
|
86
287
|
* Sticky flag: flips `true` the first time any element is registered and
|
|
87
288
|
* stays `true` even after elements are unregistered. Lets agents distinguish
|
|
@@ -119,6 +320,21 @@ var NativeUIBridgeRegistry = class {
|
|
|
119
320
|
getRouteProvider() {
|
|
120
321
|
return this.routeProvider;
|
|
121
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* Set the device pixel ratio used to project per-element `bbox` geometry
|
|
325
|
+
* into physical pixels. The React provider passes `PixelRatio.get()` here
|
|
326
|
+
* once at startup. Ignores non-finite / non-positive values (keeps the
|
|
327
|
+
* previous ratio) so a bad call can't zero out every bbox.
|
|
328
|
+
*/
|
|
329
|
+
setPixelRatio(ratio) {
|
|
330
|
+
if (typeof ratio === "number" && Number.isFinite(ratio) && ratio > 0) {
|
|
331
|
+
this.pixelRatio = ratio;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/** Read-only accessor for the device pixel ratio used for bbox projection. */
|
|
335
|
+
getPixelRatio() {
|
|
336
|
+
return this.pixelRatio;
|
|
337
|
+
}
|
|
122
338
|
// ============================================================================
|
|
123
339
|
// Snapshot Enricher Slots
|
|
124
340
|
// ============================================================================
|
|
@@ -658,6 +874,18 @@ var NativeUIBridgeRegistry = class {
|
|
|
658
874
|
const handlers = extractHandlerNames(e.props);
|
|
659
875
|
const state = e.getState();
|
|
660
876
|
const visibility = !state.visible ? "hidden" : state.layout !== null ? "visible" : "likely-visible";
|
|
877
|
+
const bbox = projectBbox(state, visibility, this.pixelRatio);
|
|
878
|
+
const vision = projectVisionFields({
|
|
879
|
+
type: e.type,
|
|
880
|
+
label: e.label,
|
|
881
|
+
// Real interactivity signal: the names of props that are actually
|
|
882
|
+
// functions (onPress/onChangeText/...). NOT `e.actions`, which
|
|
883
|
+
// `inferActions` synthesizes press/click onto for every element —
|
|
884
|
+
// using it would mark plain text/view nodes interactable.
|
|
885
|
+
handlerNames: handlers,
|
|
886
|
+
state,
|
|
887
|
+
flatStyle: e.flatStyle
|
|
888
|
+
});
|
|
661
889
|
return {
|
|
662
890
|
id: e.id,
|
|
663
891
|
type: e.type,
|
|
@@ -668,7 +896,9 @@ var NativeUIBridgeRegistry = class {
|
|
|
668
896
|
customActions: e.customActions ? Object.keys(e.customActions) : void 0,
|
|
669
897
|
registeredHandlers: handlers.length > 0 ? handlers : void 0,
|
|
670
898
|
registrationRoute: e.registrationRoute,
|
|
671
|
-
visibility
|
|
899
|
+
visibility,
|
|
900
|
+
...bbox ? { bbox } : {},
|
|
901
|
+
...vision
|
|
672
902
|
};
|
|
673
903
|
}),
|
|
674
904
|
components: this.getAllComponents().map((c) => ({
|
|
@@ -985,9 +1215,10 @@ var DefaultNativeActionExecutor = class {
|
|
|
985
1215
|
requestId: request.requestId,
|
|
986
1216
|
durationMs: successTime - startTime
|
|
987
1217
|
});
|
|
1218
|
+
const fresh = this.registry.getElement(registered.id) ?? registered;
|
|
988
1219
|
return {
|
|
989
1220
|
success: true,
|
|
990
|
-
elementState:
|
|
1221
|
+
elementState: fresh.getState(),
|
|
991
1222
|
result,
|
|
992
1223
|
durationMs: successTime - startTime,
|
|
993
1224
|
timestamp: successTime,
|
|
@@ -1893,7 +2124,7 @@ function createDesignHandlers(registry, viewportProvider) {
|
|
|
1893
2124
|
var GRID_SIZE = 20;
|
|
1894
2125
|
var NAV_TYPES = /* @__PURE__ */ new Set(["button", "heading", "badge", "status-message"]);
|
|
1895
2126
|
var SKIP_TEXT_TYPES = /* @__PURE__ */ new Set(["button", "link", "tab", "menuitem"]);
|
|
1896
|
-
var
|
|
2127
|
+
var INTERACTIVE_TYPES2 = /* @__PURE__ */ new Set([
|
|
1897
2128
|
"button",
|
|
1898
2129
|
"input",
|
|
1899
2130
|
"switch",
|
|
@@ -2090,7 +2321,7 @@ function diagnosePageHealth(elements, viewport) {
|
|
|
2090
2321
|
let interactiveTotal = 0;
|
|
2091
2322
|
let interactiveDisabled = 0;
|
|
2092
2323
|
for (const el of elements) {
|
|
2093
|
-
if (!
|
|
2324
|
+
if (!INTERACTIVE_TYPES2.has(el.type)) continue;
|
|
2094
2325
|
interactiveTotal++;
|
|
2095
2326
|
if (el.state.enabled === false) interactiveDisabled++;
|
|
2096
2327
|
}
|
|
@@ -2158,6 +2389,13 @@ function matchElementsByText(registry, text, opts) {
|
|
|
2158
2389
|
scored.sort((a, b) => a.rank - b.rank);
|
|
2159
2390
|
return scored.map((s) => s.element);
|
|
2160
2391
|
}
|
|
2392
|
+
function resolveElementState(element) {
|
|
2393
|
+
const state = element.getState();
|
|
2394
|
+
if (element.type === "input" && state.value === void 0 && typeof element.props?.value === "string") {
|
|
2395
|
+
return { ...state, value: element.props.value };
|
|
2396
|
+
}
|
|
2397
|
+
return state;
|
|
2398
|
+
}
|
|
2161
2399
|
async function executeWorkflowStep(step, registry, executor) {
|
|
2162
2400
|
const startTime = Date.now();
|
|
2163
2401
|
try {
|
|
@@ -2366,6 +2604,16 @@ function createServerHandlers(registry, executor, config) {
|
|
|
2366
2604
|
const handlers = extractHandlerNames(e.props);
|
|
2367
2605
|
const state = e.getState();
|
|
2368
2606
|
const visibility = !state.visible ? "hidden" : state.layout !== null ? "visible" : "likely-visible";
|
|
2607
|
+
const bbox = projectBbox(state, visibility, registry.getPixelRatio());
|
|
2608
|
+
const vision = projectVisionFields({
|
|
2609
|
+
type: e.type,
|
|
2610
|
+
label: e.label,
|
|
2611
|
+
// Real handler names (onPress/...), NOT inferred actions which
|
|
2612
|
+
// synthesize press/click onto every element. See projectVisionFields.
|
|
2613
|
+
handlerNames: handlers,
|
|
2614
|
+
state,
|
|
2615
|
+
flatStyle: e.flatStyle
|
|
2616
|
+
});
|
|
2369
2617
|
return {
|
|
2370
2618
|
id: e.id,
|
|
2371
2619
|
type: e.type,
|
|
@@ -2376,7 +2624,9 @@ function createServerHandlers(registry, executor, config) {
|
|
|
2376
2624
|
customActions: e.customActions ? Object.keys(e.customActions) : void 0,
|
|
2377
2625
|
registeredHandlers: handlers.length > 0 ? handlers : void 0,
|
|
2378
2626
|
registrationRoute: e.registrationRoute,
|
|
2379
|
-
visibility
|
|
2627
|
+
visibility,
|
|
2628
|
+
...bbox ? { bbox } : {},
|
|
2629
|
+
...vision
|
|
2380
2630
|
};
|
|
2381
2631
|
});
|
|
2382
2632
|
return success2({ elements });
|
|
@@ -2394,7 +2644,7 @@ function createServerHandlers(registry, executor, config) {
|
|
|
2394
2644
|
type: element.type,
|
|
2395
2645
|
label: element.label,
|
|
2396
2646
|
identifier: element.getIdentifier(),
|
|
2397
|
-
state: element
|
|
2647
|
+
state: resolveElementState(element),
|
|
2398
2648
|
actions: element.actions,
|
|
2399
2649
|
customActions: element.customActions ? Object.keys(element.customActions) : void 0,
|
|
2400
2650
|
registeredHandlers: handlers.length > 0 ? handlers : void 0,
|
|
@@ -2408,18 +2658,19 @@ function createServerHandlers(registry, executor, config) {
|
|
|
2408
2658
|
if (!element) {
|
|
2409
2659
|
return error2(`Element not found: ${id}`, "ELEMENT_NOT_FOUND");
|
|
2410
2660
|
}
|
|
2411
|
-
return success2({ state: element
|
|
2661
|
+
return success2({ state: resolveElementState(element) });
|
|
2412
2662
|
},
|
|
2413
2663
|
executeAction: async (ctx) => {
|
|
2414
|
-
const
|
|
2415
|
-
const
|
|
2416
|
-
|
|
2664
|
+
const rawBody = ctx.body ?? {};
|
|
2665
|
+
const envelope = rawBody.request && typeof rawBody.request === "object" ? rawBody.request : rawBody;
|
|
2666
|
+
const id = ctx.params.id ?? rawBody.id ?? "";
|
|
2667
|
+
if (!envelope?.action) {
|
|
2417
2668
|
return error2("Action is required", "INVALID_REQUEST");
|
|
2418
2669
|
}
|
|
2419
2670
|
const response = await executor.executeAction(id, {
|
|
2420
|
-
action:
|
|
2421
|
-
params:
|
|
2422
|
-
waitOptions:
|
|
2671
|
+
action: envelope.action,
|
|
2672
|
+
params: envelope.params,
|
|
2673
|
+
waitOptions: envelope.waitOptions
|
|
2423
2674
|
});
|
|
2424
2675
|
if (!response.success) {
|
|
2425
2676
|
return error2(response.error || "Action failed", "ACTION_FAILED");
|
|
@@ -3058,7 +3309,7 @@ function createServerHandlers(registry, executor, config) {
|
|
|
3058
3309
|
};
|
|
3059
3310
|
if (config?.appInfo) {
|
|
3060
3311
|
response.uiBridge = {
|
|
3061
|
-
version: "0.
|
|
3312
|
+
version: "0.6.11",
|
|
3062
3313
|
...config.appInfo,
|
|
3063
3314
|
capabilities: ["elements", "components", "actions", "design"],
|
|
3064
3315
|
elementCount: stats.elements,
|
|
@@ -3650,6 +3901,14 @@ var NativeUIBridgeServer = class {
|
|
|
3650
3901
|
};
|
|
3651
3902
|
};
|
|
3652
3903
|
}
|
|
3904
|
+
/**
|
|
3905
|
+
* Whether the console/network capture should be installed. The dedicated
|
|
3906
|
+
* `observability` flag wins when set; otherwise falls back to `testHooks`
|
|
3907
|
+
* so consumers predating the flag keep their exact behavior.
|
|
3908
|
+
*/
|
|
3909
|
+
observabilityEnabled() {
|
|
3910
|
+
return this.config.observability ?? this.config.testHooks === true;
|
|
3911
|
+
}
|
|
3653
3912
|
/**
|
|
3654
3913
|
* Set the server adapter
|
|
3655
3914
|
*/
|
|
@@ -3863,7 +4122,7 @@ var NativeUIBridgeServer = class {
|
|
|
3863
4122
|
console.warn("[ui-bridge-native] See documentation for supported adapters.");
|
|
3864
4123
|
return;
|
|
3865
4124
|
}
|
|
3866
|
-
if (this.
|
|
4125
|
+
if (this.observabilityEnabled()) {
|
|
3867
4126
|
this.consoleErrorBuffer.install();
|
|
3868
4127
|
this.networkRequestBuffer.install();
|
|
3869
4128
|
}
|
|
@@ -5365,6 +5624,7 @@ function UIBridgeNativeProvider({
|
|
|
5365
5624
|
verbose: config.verbose,
|
|
5366
5625
|
onEvent
|
|
5367
5626
|
});
|
|
5627
|
+
registryRef.current.setPixelRatio(PixelRatio.get());
|
|
5368
5628
|
setGlobalRegistry(registryRef.current);
|
|
5369
5629
|
}
|
|
5370
5630
|
const registry = registryRef.current;
|
|
@@ -5395,6 +5655,10 @@ function UIBridgeNativeProvider({
|
|
|
5395
5655
|
cors: true,
|
|
5396
5656
|
appInfo: config.appInfo,
|
|
5397
5657
|
testHooks: features.testHooks === true,
|
|
5658
|
+
// Dedicated console/network-capture flag; the server defaults it to
|
|
5659
|
+
// the `testHooks` value when undefined, so passing it through verbatim
|
|
5660
|
+
// keeps existing consumers unchanged.
|
|
5661
|
+
observability: features.observability,
|
|
5398
5662
|
// Injected device-viewport getter for POST /control/page-health. The
|
|
5399
5663
|
// server doesn't import `react-native` itself — the require/import
|
|
5400
5664
|
// pattern there crashed the host RN app in 0.6.3/0.6.4 (Metro/Hermes
|
|
@@ -5462,6 +5726,7 @@ function UIBridgeNativeProvider({
|
|
|
5462
5726
|
}, [
|
|
5463
5727
|
features.server,
|
|
5464
5728
|
features.testHooks,
|
|
5729
|
+
features.observability,
|
|
5465
5730
|
config.serverPort,
|
|
5466
5731
|
config.appInfo,
|
|
5467
5732
|
registry,
|
|
@@ -5500,6 +5765,7 @@ function UIBridgeNativeProvider({
|
|
|
5500
5765
|
cors: true,
|
|
5501
5766
|
appInfo: config.appInfo,
|
|
5502
5767
|
testHooks: features.testHooks === true,
|
|
5768
|
+
observability: features.observability,
|
|
5503
5769
|
viewportProvider: () => {
|
|
5504
5770
|
const win = Dimensions.get("window");
|
|
5505
5771
|
return { width: win.width, height: win.height };
|
|
@@ -5703,12 +5969,41 @@ function scheduleDeadButtonWarning(bridge, id, type) {
|
|
|
5703
5969
|
}
|
|
5704
5970
|
function useUIElement(options) {
|
|
5705
5971
|
const bridge = useUIBridgeNativeOptional();
|
|
5706
|
-
const ref = useRef(null);
|
|
5707
5972
|
const [registered, setRegistered] = useState(false);
|
|
5708
5973
|
const [_layout, setLayout] = useState(null);
|
|
5709
5974
|
const propsRef = useRef({});
|
|
5710
5975
|
const registeredRef = useRef(false);
|
|
5711
5976
|
const registeredIdRef = useRef(null);
|
|
5977
|
+
const bridgeRef = useRef(bridge);
|
|
5978
|
+
bridgeRef.current = bridge;
|
|
5979
|
+
const nodeRef = useRef(null);
|
|
5980
|
+
const refInterceptorRef = useRef(null);
|
|
5981
|
+
if (refInterceptorRef.current === null) {
|
|
5982
|
+
refInterceptorRef.current = {
|
|
5983
|
+
get current() {
|
|
5984
|
+
return nodeRef.current;
|
|
5985
|
+
},
|
|
5986
|
+
set current(node) {
|
|
5987
|
+
const prev = nodeRef.current;
|
|
5988
|
+
nodeRef.current = node;
|
|
5989
|
+
const liveBridge = bridgeRef.current;
|
|
5990
|
+
const registeredId = registeredIdRef.current;
|
|
5991
|
+
if (!liveBridge || !registeredRef.current || registeredId === null) return;
|
|
5992
|
+
if (node == null && prev != null) {
|
|
5993
|
+
const state = liveBridge.registry.getElement(registeredId)?.getState();
|
|
5994
|
+
if (state && state.visible !== false) {
|
|
5995
|
+
liveBridge.registry.updateElementState(registeredId, { visible: false });
|
|
5996
|
+
}
|
|
5997
|
+
} else if (node != null && prev == null) {
|
|
5998
|
+
const state = liveBridge.registry.getElement(registeredId)?.getState();
|
|
5999
|
+
if (state && state.visible !== true) {
|
|
6000
|
+
liveBridge.registry.updateElementState(registeredId, { visible: true });
|
|
6001
|
+
}
|
|
6002
|
+
}
|
|
6003
|
+
}
|
|
6004
|
+
};
|
|
6005
|
+
}
|
|
6006
|
+
const ref = refInterceptorRef.current;
|
|
5712
6007
|
const {
|
|
5713
6008
|
id,
|
|
5714
6009
|
type = "custom",
|
|
@@ -5752,7 +6047,7 @@ function useUIElement(options) {
|
|
|
5752
6047
|
}
|
|
5753
6048
|
setRegistered(true);
|
|
5754
6049
|
scheduleDeadButtonWarning(bridge, id, type);
|
|
5755
|
-
}, [bridge, id, type, label, actions, customActions, treePath, style, stateStylesProp, valueProp]);
|
|
6050
|
+
}, [bridge, id, type, label, actions, customActions, treePath, style, stateStylesProp, valueProp, ref]);
|
|
5756
6051
|
const unregister = useCallback(() => {
|
|
5757
6052
|
if (!bridge || !registeredRef.current) return;
|
|
5758
6053
|
bridge.registry.unregisterElement(registeredIdRef.current ?? id);
|
|
@@ -5788,7 +6083,8 @@ function useUIElement(options) {
|
|
|
5788
6083
|
writeLayout({ x, y, width, height, pageX: x, pageY: y });
|
|
5789
6084
|
}
|
|
5790
6085
|
},
|
|
5791
|
-
|
|
6086
|
+
// `ref` is the once-per-hook interceptor object — identity-stable.
|
|
6087
|
+
[bridge, id, ref]
|
|
5792
6088
|
);
|
|
5793
6089
|
const registerRef = useRef(register);
|
|
5794
6090
|
const unregisterRef = useRef(unregister);
|
|
@@ -5809,7 +6105,14 @@ function useUIElement(options) {
|
|
|
5809
6105
|
useEffect(() => {
|
|
5810
6106
|
if (!bridge || !registered) return;
|
|
5811
6107
|
const node = ref.current;
|
|
5812
|
-
if (!node
|
|
6108
|
+
if (!node) {
|
|
6109
|
+
const seeded = bridge.registry.getElement(id)?.getState();
|
|
6110
|
+
if (seeded && seeded.visible !== false) {
|
|
6111
|
+
bridge.registry.updateElementState(id, { visible: false });
|
|
6112
|
+
}
|
|
6113
|
+
return;
|
|
6114
|
+
}
|
|
6115
|
+
if (typeof node.measureInWindow !== "function") return;
|
|
5813
6116
|
let cancelled = false;
|
|
5814
6117
|
let pendingTimer = null;
|
|
5815
6118
|
const backoffMs = [50, 200, 500, 1e3, 2500];
|
|
@@ -5848,7 +6151,7 @@ function useUIElement(options) {
|
|
|
5848
6151
|
cancelled = true;
|
|
5849
6152
|
if (pendingTimer != null) clearTimeout(pendingTimer);
|
|
5850
6153
|
};
|
|
5851
|
-
}, [bridge, registered, id]);
|
|
6154
|
+
}, [bridge, registered, id, ref]);
|
|
5852
6155
|
const registeredHandlerKeysRef = useRef([]);
|
|
5853
6156
|
useEffect(() => {
|
|
5854
6157
|
if (!bridge || !registered || !handlersProp) return;
|
|
@@ -6630,6 +6933,46 @@ function decodeTextPayload(payload) {
|
|
|
6630
6933
|
return new TextDecoder().decode(payload);
|
|
6631
6934
|
}
|
|
6632
6935
|
|
|
6936
|
+
// src/server/bind-retry.ts
|
|
6937
|
+
function isAddrInUseError(err) {
|
|
6938
|
+
const message = err instanceof Error ? err.message : typeof err === "string" ? err : String(err);
|
|
6939
|
+
const code = err?.code;
|
|
6940
|
+
return message.includes("EADDRINUSE") || message.toLowerCase().includes("address already in use") || code === "EADDRINUSE";
|
|
6941
|
+
}
|
|
6942
|
+
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
6943
|
+
async function bindWithRetry(attempt, options = {}) {
|
|
6944
|
+
const {
|
|
6945
|
+
minTotalMs = 3e4,
|
|
6946
|
+
initialDelayMs = 500,
|
|
6947
|
+
maxDelayMs = 5e3,
|
|
6948
|
+
backoffFactor = 2,
|
|
6949
|
+
logger = console,
|
|
6950
|
+
sleep: sleep2 = defaultSleep,
|
|
6951
|
+
now = Date.now
|
|
6952
|
+
} = options;
|
|
6953
|
+
const startedAt = now();
|
|
6954
|
+
let delay = initialDelayMs;
|
|
6955
|
+
let attemptIndex = 0;
|
|
6956
|
+
for (; ; ) {
|
|
6957
|
+
try {
|
|
6958
|
+
await attempt(attemptIndex);
|
|
6959
|
+
return;
|
|
6960
|
+
} catch (err) {
|
|
6961
|
+
const elapsed = now() - startedAt;
|
|
6962
|
+
const budgetExhausted = elapsed + delay >= minTotalMs;
|
|
6963
|
+
if (!isAddrInUseError(err) || budgetExhausted) {
|
|
6964
|
+
throw err;
|
|
6965
|
+
}
|
|
6966
|
+
logger.warn(
|
|
6967
|
+
`[ui-bridge-native] bind failed with EADDRINUSE (attempt ${attemptIndex + 1}); retrying in ${delay}ms (elapsed ${elapsed}ms / budget ${minTotalMs === Infinity ? "\u221E" : minTotalMs}ms)`
|
|
6968
|
+
);
|
|
6969
|
+
await sleep2(delay);
|
|
6970
|
+
delay = Math.min(delay * backoffFactor, maxDelayMs);
|
|
6971
|
+
attemptIndex += 1;
|
|
6972
|
+
}
|
|
6973
|
+
}
|
|
6974
|
+
}
|
|
6975
|
+
|
|
6633
6976
|
// src/server/ws-connection.ts
|
|
6634
6977
|
var WebSocketConnection = class {
|
|
6635
6978
|
constructor(id, socket, onMessage, onClose) {
|
|
@@ -7196,6 +7539,6 @@ var styles = StyleSheet.create({
|
|
|
7196
7539
|
}
|
|
7197
7540
|
});
|
|
7198
7541
|
|
|
7199
|
-
export { CloudRelayClient, DefaultNativeActionExecutor, DeviceAnnouncer, ModalDetector, NativeUIBridgeRegistry, NativeUIBridgeServer, State, ToastCapture, TransitionTo, UIBridgeInspector, UIBridgeNativeProvider, UI_BRIDGE_NATIVE_ROUTES, UndoTracker, WebSocketConnection, WebSocketEventBridge, buildTreePath, buildUpgradeResponse, captureNativeResponsiveSnapshot, captureNativeStateVariations, computeAcceptKey, createDesignHandlers, createNativeActionExecutor, createNativeElementIdentifier, createNativeServer, createServerHandlers, extractHandlerNames, findAllByPattern, findElementByIdentifier, getGlobalRegistry, getNativeElementDesignData, mapNativeStyleToExtended, matchesIdentifier, parseTreePath, resetGlobalRegistry, setGlobalRegistry, useToastRecorder, useUIBridge, useUIBridgeModal, useUIBridgeNative, useUIBridgeNativeOptional, useUIBridgeNativeRequired, useUIBridgeRequired, useUIBridgeToast, useUIComponent, useUIComponentAction, useUIElement, useUIElementWithProps };
|
|
7542
|
+
export { CloudRelayClient, DefaultNativeActionExecutor, DeviceAnnouncer, ModalDetector, NativeUIBridgeRegistry, NativeUIBridgeServer, State, ToastCapture, TransitionTo, UIBridgeInspector, UIBridgeNativeProvider, UI_BRIDGE_NATIVE_ROUTES, UndoTracker, WebSocketConnection, WebSocketEventBridge, bindWithRetry, buildTreePath, buildUpgradeResponse, captureNativeResponsiveSnapshot, captureNativeStateVariations, computeAcceptKey, createDesignHandlers, createNativeActionExecutor, createNativeElementIdentifier, createNativeServer, createServerHandlers, deriveInteractable, deriveText, extractHandlerNames, findAllByPattern, findElementByIdentifier, getGlobalRegistry, getNativeElementDesignData, isAddrInUseError, mapNativeStyleToExtended, matchesIdentifier, parseColorToRgb, parseFontSizePx, parseTreePath, projectVisionFields, resetGlobalRegistry, roleForType, setGlobalRegistry, useToastRecorder, useUIBridge, useUIBridgeModal, useUIBridgeNative, useUIBridgeNativeOptional, useUIBridgeNativeRequired, useUIBridgeRequired, useUIBridgeToast, useUIComponent, useUIComponentAction, useUIElement, useUIElementWithProps };
|
|
7200
7543
|
//# sourceMappingURL=index.mjs.map
|
|
7201
7544
|
//# sourceMappingURL=index.mjs.map
|