@qontinui/ui-bridge-native 0.2.0 → 0.2.1
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/index.d.mts +4 -4
- package/dist/control/index.d.ts +4 -4
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +39 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +39 -1
- package/dist/core/index.mjs.map +1 -1
- package/dist/debug/index.js +2 -2
- package/dist/debug/index.js.map +1 -1
- package/dist/debug/index.mjs +2 -2
- package/dist/debug/index.mjs.map +1 -1
- package/dist/design/index.d.mts +1 -1
- package/dist/design/index.d.ts +1 -1
- package/dist/{http-server-DCdR8WlL.d.mts → http-server-D2U1k48d.d.mts} +89 -8
- package/dist/{http-server-Dj3grQW4.d.ts → http-server-ck8clUsZ.d.ts} +89 -8
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +364 -39
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +364 -39
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +5 -5
- package/dist/react/index.d.ts +5 -5
- package/dist/react/index.js +346 -39
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +346 -39
- package/dist/react/index.mjs.map +1 -1
- package/dist/{registry-Cahd8Gn2.d.mts → registry-BxXaxmsT.d.ts} +24 -1
- package/dist/{registry-z-3WJsxp.d.ts → registry-KdUW_koV.d.mts} +24 -1
- package/dist/server/index.d.mts +5 -5
- package/dist/server/index.d.ts +5 -5
- package/dist/server/index.js +272 -16
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +272 -16
- package/dist/server/index.mjs.map +1 -1
- package/dist/{types-Oz13YLZq.d.mts → types-BwdPNpII.d.mts} +52 -4
- package/dist/{types-Oz13YLZq.d.ts → types-BwdPNpII.d.ts} +52 -4
- package/dist/{types-ajakyp-l.d.ts → types-DCEv5qb6.d.ts} +1 -1
- package/dist/{types-DAfCeWXc.d.mts → types-DjB0zJJH.d.mts} +1 -1
- package/dist/{useUIBridgeToast-GmT1TzUe.d.mts → useUIBridgeToast-BPIEbsEc.d.mts} +11 -5
- package/dist/{useUIBridgeToast-D32KXyL1.d.ts → useUIBridgeToast-DD13oF3k.d.ts} +11 -5
- package/package.json +2 -2
package/dist/server/index.mjs
CHANGED
|
@@ -133,6 +133,25 @@ var UI_BRIDGE_NATIVE_ROUTES = {
|
|
|
133
133
|
path: "/ui-bridge/design/style-guide",
|
|
134
134
|
description: "Clear loaded style guide"
|
|
135
135
|
},
|
|
136
|
+
// AI helpers
|
|
137
|
+
AI_FILL_FORM: {
|
|
138
|
+
method: "POST",
|
|
139
|
+
path: "/ui-bridge/ai/fill-form",
|
|
140
|
+
description: "Fill multiple input elements in a single call"
|
|
141
|
+
},
|
|
142
|
+
// Test hooks — drive ModalDetector state from outside the React tree
|
|
143
|
+
// (gated by `features.testHooks`). Mirrors `pushModal` / `dismissModal`
|
|
144
|
+
// calls that components normally make in-process.
|
|
145
|
+
PUSH_MODAL: {
|
|
146
|
+
method: "POST",
|
|
147
|
+
path: "/ui-bridge/control/modal/push",
|
|
148
|
+
description: "Push a modal onto the modal stack (testHooks)"
|
|
149
|
+
},
|
|
150
|
+
DISMISS_MODAL: {
|
|
151
|
+
method: "POST",
|
|
152
|
+
path: "/ui-bridge/control/modal/dismiss/:id",
|
|
153
|
+
description: "Dismiss a modal by id (testHooks)"
|
|
154
|
+
},
|
|
136
155
|
// Health
|
|
137
156
|
HEALTH: {
|
|
138
157
|
method: "GET",
|
|
@@ -935,6 +954,20 @@ function createServerHandlers(registry, executor, config) {
|
|
|
935
954
|
return success2(response);
|
|
936
955
|
},
|
|
937
956
|
// Discovery
|
|
957
|
+
//
|
|
958
|
+
// Native parity with the runner's `POST /ui-bridge/ai/find`. The runner
|
|
959
|
+
// matches a free-text `query` against label > text > value > aria-label >
|
|
960
|
+
// placeholder > name. We replicate that here against the native registry's
|
|
961
|
+
// analogues:
|
|
962
|
+
// 1. `label` (the registered display label)
|
|
963
|
+
// 2. identifier fields: `testId`, `accessibilityLabel`, `treePath`, `uiId`
|
|
964
|
+
// 3. registered handler names (e.g. `onPress`, `onChangeText`)
|
|
965
|
+
//
|
|
966
|
+
// Match is case-insensitive substring. Empty/missing query falls back to
|
|
967
|
+
// the structured filters (types/visibleOnly/etc.) so callers that don't
|
|
968
|
+
// pass `query` still get the legacy behaviour. Once we've narrowed by
|
|
969
|
+
// query the response is shaped through the executor's NativeFindResponse
|
|
970
|
+
// envelope so the wire format stays stable.
|
|
938
971
|
find: async (ctx) => {
|
|
939
972
|
const body = ctx.body;
|
|
940
973
|
const response = await executor.find({
|
|
@@ -942,9 +975,49 @@ function createServerHandlers(registry, executor, config) {
|
|
|
942
975
|
testIdPattern: body?.testIdPattern,
|
|
943
976
|
accessibilityLabelPattern: body?.accessibilityLabelPattern,
|
|
944
977
|
visibleOnly: body?.visibleOnly,
|
|
945
|
-
limit
|
|
978
|
+
// When `query` is supplied we apply our own limit *after* ranking so
|
|
979
|
+
// the highest-priority matches survive the cap; defer slicing to us
|
|
980
|
+
// by widening here.
|
|
981
|
+
limit: body?.query && body.query.length > 0 ? void 0 : body?.limit
|
|
982
|
+
});
|
|
983
|
+
const rawQuery = typeof body?.query === "string" ? body.query.trim() : "";
|
|
984
|
+
if (rawQuery.length === 0) {
|
|
985
|
+
return success2(response);
|
|
986
|
+
}
|
|
987
|
+
const needle = rawQuery.toLowerCase();
|
|
988
|
+
const containsNeedle = (value) => typeof value === "string" && value.toLowerCase().includes(needle);
|
|
989
|
+
const scored = [];
|
|
990
|
+
for (const el of response.elements) {
|
|
991
|
+
let priorityRank = Number.POSITIVE_INFINITY;
|
|
992
|
+
if (containsNeedle(el.label)) {
|
|
993
|
+
priorityRank = Math.min(priorityRank, 0);
|
|
994
|
+
}
|
|
995
|
+
const ident = el.identifier;
|
|
996
|
+
if (ident) {
|
|
997
|
+
if (containsNeedle(ident.testId)) priorityRank = Math.min(priorityRank, 1);
|
|
998
|
+
if (containsNeedle(ident.accessibilityLabel)) priorityRank = Math.min(priorityRank, 1);
|
|
999
|
+
if (containsNeedle(ident.treePath)) priorityRank = Math.min(priorityRank, 1);
|
|
1000
|
+
if (containsNeedle(ident.uiId)) priorityRank = Math.min(priorityRank, 1);
|
|
1001
|
+
}
|
|
1002
|
+
if (priorityRank === Number.POSITIVE_INFINITY) {
|
|
1003
|
+
const registered = registry.getElement(el.id);
|
|
1004
|
+
const handlers = extractHandlerNames(registered?.props);
|
|
1005
|
+
if (handlers.some((h) => h.toLowerCase().includes(needle))) {
|
|
1006
|
+
priorityRank = 2;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
if (priorityRank !== Number.POSITIVE_INFINITY) {
|
|
1010
|
+
scored.push({ priorityRank, element: el });
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
scored.sort((a, b) => a.priorityRank - b.priorityRank);
|
|
1014
|
+
const limit = body?.limit && body.limit > 0 ? body.limit : void 0;
|
|
1015
|
+
const filtered = limit ? scored.slice(0, limit) : scored;
|
|
1016
|
+
return success2({
|
|
1017
|
+
...response,
|
|
1018
|
+
elements: filtered.map((s) => s.element),
|
|
1019
|
+
total: filtered.length
|
|
946
1020
|
});
|
|
947
|
-
return success2(response);
|
|
948
1021
|
},
|
|
949
1022
|
getSnapshot: async (ctx) => {
|
|
950
1023
|
const visibleOnly = ctx.query?.visibleOnly === "true" || ctx.body?.visibleOnly === true;
|
|
@@ -1045,15 +1118,162 @@ function createServerHandlers(registry, executor, config) {
|
|
|
1045
1118
|
getMethods: async () => {
|
|
1046
1119
|
return error2("Methods introspection not configured", "NOT_SUPPORTED");
|
|
1047
1120
|
},
|
|
1121
|
+
// AI helpers
|
|
1122
|
+
//
|
|
1123
|
+
// Mobile-side parity with the runner's `POST /ui-bridge/ai/fill-form`.
|
|
1124
|
+
// Each entry in `fields` is dispatched through the existing executor as a
|
|
1125
|
+
// `type` action with `clear: true` so the input value is replaced
|
|
1126
|
+
// atomically (the closest native equivalent to the web bridge's
|
|
1127
|
+
// "setValue" semantics — there is no `setValue` action on native).
|
|
1128
|
+
//
|
|
1129
|
+
// Failures do not short-circuit: every field is attempted, and the
|
|
1130
|
+
// response carries per-field success/error plus aggregate counts.
|
|
1131
|
+
fillForm: async (ctx) => {
|
|
1132
|
+
const body = ctx.body;
|
|
1133
|
+
const rawFields = body?.fields;
|
|
1134
|
+
if (!Array.isArray(rawFields)) {
|
|
1135
|
+
return error2('Request body must include "fields" array', "INVALID_REQUEST");
|
|
1136
|
+
}
|
|
1137
|
+
const results = [];
|
|
1138
|
+
for (let i = 0; i < rawFields.length; i++) {
|
|
1139
|
+
const field = rawFields[i];
|
|
1140
|
+
if (!field || typeof field !== "object") {
|
|
1141
|
+
results.push({
|
|
1142
|
+
elementId: "",
|
|
1143
|
+
success: false,
|
|
1144
|
+
error: `fields[${i}] must be an object with "elementId" and "value"`
|
|
1145
|
+
});
|
|
1146
|
+
continue;
|
|
1147
|
+
}
|
|
1148
|
+
const { elementId, value } = field;
|
|
1149
|
+
if (typeof elementId !== "string" || elementId.length === 0) {
|
|
1150
|
+
results.push({
|
|
1151
|
+
elementId: typeof elementId === "string" ? elementId : "",
|
|
1152
|
+
success: false,
|
|
1153
|
+
error: `fields[${i}] is missing a non-empty "elementId"`
|
|
1154
|
+
});
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1157
|
+
if (typeof value !== "string") {
|
|
1158
|
+
results.push({
|
|
1159
|
+
elementId,
|
|
1160
|
+
success: false,
|
|
1161
|
+
error: `fields[${i}] is missing a string "value"`
|
|
1162
|
+
});
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
try {
|
|
1166
|
+
const response = await executor.executeAction(elementId, {
|
|
1167
|
+
action: "type",
|
|
1168
|
+
params: { text: value, clear: true }
|
|
1169
|
+
});
|
|
1170
|
+
if (response.success) {
|
|
1171
|
+
results.push({ elementId, success: true });
|
|
1172
|
+
} else {
|
|
1173
|
+
results.push({
|
|
1174
|
+
elementId,
|
|
1175
|
+
success: false,
|
|
1176
|
+
error: response.error ?? "Action failed"
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
} catch (err) {
|
|
1180
|
+
results.push({
|
|
1181
|
+
elementId,
|
|
1182
|
+
success: false,
|
|
1183
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
const succeededCount = results.filter((r) => r.success).length;
|
|
1188
|
+
const failedCount = results.length - succeededCount;
|
|
1189
|
+
return success2({ results, succeededCount, failedCount });
|
|
1190
|
+
},
|
|
1191
|
+
// Test hooks — modal stack manipulation
|
|
1192
|
+
//
|
|
1193
|
+
// These two endpoints exist so external automation runners can drive
|
|
1194
|
+
// ModalDetector state via HTTP, mirroring the in-tree `pushModal` /
|
|
1195
|
+
// `dismissModal` calls that components normally make. They're gated by
|
|
1196
|
+
// `features.testHooks` at the route layer (see http-server.ts); the
|
|
1197
|
+
// handlers themselves still defend against a missing modalDetector
|
|
1198
|
+
// because the bare server path used by CloudRelayClient may run before
|
|
1199
|
+
// enrichers are wired.
|
|
1200
|
+
pushModal: async (ctx) => {
|
|
1201
|
+
const detector = registry.getEnrichers().modalDetector;
|
|
1202
|
+
if (!detector) {
|
|
1203
|
+
return error2(
|
|
1204
|
+
"modal detector not registered",
|
|
1205
|
+
"ENRICHER_UNAVAILABLE"
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
const body = ctx.body;
|
|
1209
|
+
const id = body?.id;
|
|
1210
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
1211
|
+
return error2(
|
|
1212
|
+
'pushModal requires a non-empty "id"',
|
|
1213
|
+
"INVALID_REQUEST"
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
const allowedTypes = ["modal", "sheet", "drawer", "popover", "alertdialog", "dialog"];
|
|
1217
|
+
const requestedType = body?.type;
|
|
1218
|
+
if (requestedType !== void 0 && (typeof requestedType !== "string" || !allowedTypes.includes(requestedType))) {
|
|
1219
|
+
return error2(
|
|
1220
|
+
`pushModal "type" must be one of: ${allowedTypes.join(", ")}`,
|
|
1221
|
+
"INVALID_REQUEST"
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
detector.pushModal({
|
|
1225
|
+
id,
|
|
1226
|
+
title: body?.title,
|
|
1227
|
+
// requestedType is narrowed by the allowedTypes guard above; the cast
|
|
1228
|
+
// converts the validated string to the NativeModalInfo['type'] union.
|
|
1229
|
+
type: requestedType,
|
|
1230
|
+
blocking: body?.blocking,
|
|
1231
|
+
dismissible: body?.dismissible
|
|
1232
|
+
});
|
|
1233
|
+
const stored = detector.getActive().find((m) => m.id === id);
|
|
1234
|
+
return success2({
|
|
1235
|
+
pushed: true,
|
|
1236
|
+
id,
|
|
1237
|
+
title: stored?.title,
|
|
1238
|
+
type: stored?.type,
|
|
1239
|
+
blocking: stored?.blocking,
|
|
1240
|
+
dismissible: stored?.dismissible,
|
|
1241
|
+
pushedAt: stored?.detectedAt ?? Date.now()
|
|
1242
|
+
});
|
|
1243
|
+
},
|
|
1244
|
+
dismissModal: async (ctx) => {
|
|
1245
|
+
const detector = registry.getEnrichers().modalDetector;
|
|
1246
|
+
if (!detector) {
|
|
1247
|
+
return error2(
|
|
1248
|
+
"modal detector not registered",
|
|
1249
|
+
"ENRICHER_UNAVAILABLE"
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
const { id } = ctx.params;
|
|
1253
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
1254
|
+
return error2(
|
|
1255
|
+
"dismissModal requires a modal id in the path",
|
|
1256
|
+
"INVALID_REQUEST"
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
const wasPresent = detector.getActive().some((m) => m.id === id);
|
|
1260
|
+
if (!wasPresent) {
|
|
1261
|
+
return error2(`modal not found: ${id}`, "MODAL_NOT_FOUND");
|
|
1262
|
+
}
|
|
1263
|
+
detector.dismissModal(id);
|
|
1264
|
+
return success2({ dismissed: id });
|
|
1265
|
+
},
|
|
1048
1266
|
// Design Review
|
|
1049
1267
|
...designHandlers,
|
|
1050
1268
|
// Health
|
|
1051
1269
|
health: async () => {
|
|
1052
1270
|
const stats = registry.getStats();
|
|
1271
|
+
const registration = registry.getRegistrationCoverage();
|
|
1053
1272
|
const response = {
|
|
1054
1273
|
status: "healthy",
|
|
1055
1274
|
timestamp: Date.now(),
|
|
1056
|
-
...stats
|
|
1275
|
+
...stats,
|
|
1276
|
+
registration
|
|
1057
1277
|
};
|
|
1058
1278
|
if (config?.appInfo) {
|
|
1059
1279
|
response.uiBridge = {
|
|
@@ -1154,7 +1374,24 @@ var WS_ROUTES = [
|
|
|
1154
1374
|
{ pattern: "design/evaluate/baseline", handler: "saveBaseline", httpMethods: ["POST"] },
|
|
1155
1375
|
{ pattern: "design/evaluate/diff", handler: "diffBaseline", httpMethods: ["POST"] },
|
|
1156
1376
|
// Meta / Introspection
|
|
1157
|
-
{ pattern: "meta/methods", handler: "getMethods", httpMethods: ["GET"] }
|
|
1377
|
+
{ pattern: "meta/methods", handler: "getMethods", httpMethods: ["GET"] },
|
|
1378
|
+
// AI helpers — mobile parity with the runner's `/ui-bridge/ai/*` family.
|
|
1379
|
+
{ pattern: "ai/fill-form", handler: "fillForm", httpMethods: ["POST"] },
|
|
1380
|
+
// Test hooks — gated behind `features.testHooks`. Let external runners
|
|
1381
|
+
// drive the ModalDetector over HTTP so tests can flip `snapshot.modalStack`
|
|
1382
|
+
// without mounting React components.
|
|
1383
|
+
{
|
|
1384
|
+
pattern: "control/modal/push",
|
|
1385
|
+
handler: "pushModal",
|
|
1386
|
+
httpMethods: ["POST"],
|
|
1387
|
+
requiresTestHooks: true
|
|
1388
|
+
},
|
|
1389
|
+
{
|
|
1390
|
+
pattern: "control/modal/dismiss/:id",
|
|
1391
|
+
handler: "dismissModal",
|
|
1392
|
+
httpMethods: ["POST"],
|
|
1393
|
+
requiresTestHooks: true
|
|
1394
|
+
}
|
|
1158
1395
|
];
|
|
1159
1396
|
var HANDLER_DESCRIPTIONS = {
|
|
1160
1397
|
health: "Liveness probe; returns app metadata + element count",
|
|
@@ -1187,7 +1424,10 @@ var HANDLER_DESCRIPTIONS = {
|
|
|
1187
1424
|
getQualityContexts: "List available quality evaluation contexts",
|
|
1188
1425
|
saveBaseline: "Save a quality baseline snapshot",
|
|
1189
1426
|
diffBaseline: "Diff current UI against a saved baseline",
|
|
1190
|
-
getMethods: "List every handler name (legacy introspection)"
|
|
1427
|
+
getMethods: "List every handler name (legacy introspection)",
|
|
1428
|
+
fillForm: "Fill multiple inputs in one call: body { fields: [{elementId, value}] } \u2014 each field dispatches `type` + `clear:true`",
|
|
1429
|
+
pushModal: "TEST HOOK \u2014 push a modal onto the snapshot.modalStack via the registry ModalDetector",
|
|
1430
|
+
dismissModal: "TEST HOOK \u2014 dismiss a modal by id from the snapshot.modalStack via the registry ModalDetector"
|
|
1191
1431
|
};
|
|
1192
1432
|
var ACTION_REFERENCE = [
|
|
1193
1433
|
{ name: "press", aliases: ["click"], params: "PressActionParams (optional position)" },
|
|
@@ -1204,10 +1444,11 @@ var ACTION_REFERENCE = [
|
|
|
1204
1444
|
{ name: "swipe", params: "SwipeActionParams { direction, distance }" },
|
|
1205
1445
|
{ name: "toggle", params: "none \u2014 flips boolean controls" }
|
|
1206
1446
|
];
|
|
1207
|
-
function buildRoutesPayload() {
|
|
1447
|
+
function buildRoutesPayload(testHooks) {
|
|
1208
1448
|
const entries = [];
|
|
1209
1449
|
for (const r of WS_ROUTES) {
|
|
1210
1450
|
if (!r.httpMethods) continue;
|
|
1451
|
+
if (r.requiresTestHooks && !testHooks) continue;
|
|
1211
1452
|
for (const m of r.httpMethods) {
|
|
1212
1453
|
entries.push({ method: m, path: `/ui-bridge/${r.pattern}` });
|
|
1213
1454
|
}
|
|
@@ -1217,11 +1458,11 @@ function buildRoutesPayload() {
|
|
|
1217
1458
|
entries.sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method));
|
|
1218
1459
|
return { routes: entries, count: entries.length };
|
|
1219
1460
|
}
|
|
1220
|
-
function buildHelpPayload() {
|
|
1461
|
+
function buildHelpPayload(testHooks) {
|
|
1221
1462
|
return {
|
|
1222
1463
|
version: 1,
|
|
1223
1464
|
description: "UI Bridge Native HTTP surface. Prefix every path with /ui-bridge/.",
|
|
1224
|
-
routes: WS_ROUTES.map((r) => ({
|
|
1465
|
+
routes: WS_ROUTES.filter((r) => !r.requiresTestHooks || testHooks).map((r) => ({
|
|
1225
1466
|
pattern: r.pattern,
|
|
1226
1467
|
httpMethods: r.httpMethods ?? [],
|
|
1227
1468
|
wsOnly: !r.httpMethods,
|
|
@@ -1257,7 +1498,8 @@ var NativeUIBridgeServer = class {
|
|
|
1257
1498
|
appInfo: this.config.appInfo
|
|
1258
1499
|
});
|
|
1259
1500
|
this.handlers.getMethods = async () => {
|
|
1260
|
-
const
|
|
1501
|
+
const testHooks = this.config.testHooks === true;
|
|
1502
|
+
const methods = WS_ROUTES.filter((r) => !r.requiresTestHooks || testHooks).map((r) => ({
|
|
1261
1503
|
method: r.pattern,
|
|
1262
1504
|
httpMethods: r.httpMethods ? [...r.httpMethods] : void 0
|
|
1263
1505
|
}));
|
|
@@ -1300,7 +1542,11 @@ var NativeUIBridgeServer = class {
|
|
|
1300
1542
|
timestamp: Date.now()
|
|
1301
1543
|
};
|
|
1302
1544
|
} catch (e) {
|
|
1303
|
-
return {
|
|
1545
|
+
return {
|
|
1546
|
+
success: false,
|
|
1547
|
+
error: `Navigation failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
1548
|
+
timestamp: Date.now()
|
|
1549
|
+
};
|
|
1304
1550
|
}
|
|
1305
1551
|
};
|
|
1306
1552
|
this.handlers.pageReplace = async (ctx) => {
|
|
@@ -1326,7 +1572,7 @@ var NativeUIBridgeServer = class {
|
|
|
1326
1572
|
} catch (e) {
|
|
1327
1573
|
return {
|
|
1328
1574
|
success: false,
|
|
1329
|
-
error: `Replace navigation failed: ${e.message}`,
|
|
1575
|
+
error: `Replace navigation failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
1330
1576
|
timestamp: Date.now()
|
|
1331
1577
|
};
|
|
1332
1578
|
}
|
|
@@ -1345,7 +1591,7 @@ var NativeUIBridgeServer = class {
|
|
|
1345
1591
|
} catch (e) {
|
|
1346
1592
|
return {
|
|
1347
1593
|
success: false,
|
|
1348
|
-
error: `Back navigation failed: ${e.message}`,
|
|
1594
|
+
error: `Back navigation failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
1349
1595
|
timestamp: Date.now()
|
|
1350
1596
|
};
|
|
1351
1597
|
}
|
|
@@ -1366,7 +1612,11 @@ var NativeUIBridgeServer = class {
|
|
|
1366
1612
|
timestamp: Date.now()
|
|
1367
1613
|
};
|
|
1368
1614
|
} catch (e) {
|
|
1369
|
-
return {
|
|
1615
|
+
return {
|
|
1616
|
+
success: false,
|
|
1617
|
+
error: `Refresh failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
1618
|
+
timestamp: Date.now()
|
|
1619
|
+
};
|
|
1370
1620
|
}
|
|
1371
1621
|
};
|
|
1372
1622
|
}
|
|
@@ -1424,7 +1674,7 @@ var NativeUIBridgeServer = class {
|
|
|
1424
1674
|
} catch (e) {
|
|
1425
1675
|
return {
|
|
1426
1676
|
success: false,
|
|
1427
|
-
error: `Screenshot capture failed: ${e.message}`,
|
|
1677
|
+
error: `Screenshot capture failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
1428
1678
|
timestamp: Date.now()
|
|
1429
1679
|
};
|
|
1430
1680
|
}
|
|
@@ -1979,9 +2229,11 @@ var NativeUIBridgeServer = class {
|
|
|
1979
2229
|
* "control/component/my-comp/action/submit" → handlers.executeComponentAction
|
|
1980
2230
|
*/
|
|
1981
2231
|
async routeMethodToHandler(method, params) {
|
|
2232
|
+
const testHooks = this.config.testHooks === true;
|
|
1982
2233
|
for (const route of WS_ROUTES) {
|
|
1983
2234
|
const pathParams = parsePath(route.pattern, method);
|
|
1984
2235
|
if (!pathParams) continue;
|
|
2236
|
+
if (route.requiresTestHooks && !testHooks) continue;
|
|
1985
2237
|
const ctx = {
|
|
1986
2238
|
params: pathParams,
|
|
1987
2239
|
query: params.query && typeof params.query === "object" ? params.query : {},
|
|
@@ -2059,17 +2311,18 @@ var NativeUIBridgeServer = class {
|
|
|
2059
2311
|
};
|
|
2060
2312
|
}
|
|
2061
2313
|
const stripped = path.slice(PREFIX.length);
|
|
2314
|
+
const testHooks = this.config.testHooks === true;
|
|
2062
2315
|
if (method === "GET" && stripped === "_help") {
|
|
2063
2316
|
return {
|
|
2064
2317
|
success: true,
|
|
2065
|
-
data: buildHelpPayload(),
|
|
2318
|
+
data: buildHelpPayload(testHooks),
|
|
2066
2319
|
timestamp: Date.now()
|
|
2067
2320
|
};
|
|
2068
2321
|
}
|
|
2069
2322
|
if (method === "GET" && stripped === "_routes") {
|
|
2070
2323
|
return {
|
|
2071
2324
|
success: true,
|
|
2072
|
-
data: buildRoutesPayload(),
|
|
2325
|
+
data: buildRoutesPayload(testHooks),
|
|
2073
2326
|
timestamp: Date.now()
|
|
2074
2327
|
};
|
|
2075
2328
|
}
|
|
@@ -2080,6 +2333,9 @@ var NativeUIBridgeServer = class {
|
|
|
2080
2333
|
if (!route.httpMethods) {
|
|
2081
2334
|
continue;
|
|
2082
2335
|
}
|
|
2336
|
+
if (route.requiresTestHooks && !testHooks) {
|
|
2337
|
+
continue;
|
|
2338
|
+
}
|
|
2083
2339
|
if (!route.httpMethods.includes(method)) {
|
|
2084
2340
|
patternMatchedButWrongVerb = true;
|
|
2085
2341
|
continue;
|