@qontinui/ui-bridge-native 0.1.6 → 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/{action-executor.d.ts → index.d.mts} +14 -6
- package/dist/control/index.d.ts +95 -5
- package/dist/control/index.js +74 -11
- package/dist/control/index.js.map +1 -1
- package/dist/control/index.mjs +74 -11
- package/dist/control/index.mjs.map +1 -1
- package/dist/core/index.d.mts +46 -0
- package/dist/core/index.d.ts +43 -6
- package/dist/core/index.js +108 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +108 -2
- package/dist/core/index.mjs.map +1 -1
- package/dist/debug/{inspector.d.ts → index.d.mts} +6 -3
- package/dist/debug/index.d.ts +44 -4
- 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/{design-inspector-native.d.ts → index.d.mts} +11 -7
- package/dist/design/index.d.ts +60 -5
- package/dist/{design/design-types.d.ts → design-types-BHr6Q1FX.d.mts} +11 -10
- package/dist/design-types-BHr6Q1FX.d.ts +205 -0
- package/dist/http-server-D2U1k48d.d.mts +703 -0
- package/dist/http-server-ck8clUsZ.d.ts +703 -0
- package/dist/index.d.mts +89 -0
- package/dist/index.d.ts +86 -28
- package/dist/index.js +754 -50
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +749 -51
- package/dist/index.mjs.map +1 -1
- package/dist/react/{useAutoRegister.d.ts → index.d.mts} +15 -7
- package/dist/react/index.d.ts +69 -8
- package/dist/react/index.js +733 -50
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +731 -51
- package/dist/react/index.mjs.map +1 -1
- package/dist/{core/registry.d.ts → registry-BxXaxmsT.d.ts} +51 -10
- package/dist/registry-KdUW_koV.d.mts +253 -0
- package/dist/server/index.d.mts +119 -0
- package/dist/server/index.d.ts +117 -13
- 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/{core/types.d.ts → types-BwdPNpII.d.mts} +172 -30
- package/dist/types-BwdPNpII.d.ts +525 -0
- package/dist/{control/types.d.ts → types-DCEv5qb6.d.ts} +36 -15
- package/dist/types-DjB0zJJH.d.mts +197 -0
- package/dist/useUIBridgeToast-BPIEbsEc.d.mts +726 -0
- package/dist/useUIBridgeToast-DD13oF3k.d.ts +726 -0
- package/package.json +2 -2
- package/dist/control/action-executor.d.ts.map +0 -1
- package/dist/control/index.d.ts.map +0 -1
- package/dist/control/types.d.ts.map +0 -1
- package/dist/core/element-identifier.d.ts +0 -41
- package/dist/core/element-identifier.d.ts.map +0 -1
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/registry.d.ts.map +0 -1
- package/dist/core/types.d.ts.map +0 -1
- package/dist/debug/index.d.ts.map +0 -1
- package/dist/debug/inspector.d.ts.map +0 -1
- package/dist/design/design-inspector-native.d.ts.map +0 -1
- package/dist/design/design-types.d.ts.map +0 -1
- package/dist/design/index.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/react/UIBridgeNativeProvider.d.ts +0 -147
- package/dist/react/UIBridgeNativeProvider.d.ts.map +0 -1
- package/dist/react/index.d.ts.map +0 -1
- package/dist/react/useAutoRegister.d.ts.map +0 -1
- package/dist/react/useUIBridge.d.ts +0 -103
- package/dist/react/useUIBridge.d.ts.map +0 -1
- package/dist/react/useUIComponent.d.ts +0 -112
- package/dist/react/useUIComponent.d.ts.map +0 -1
- package/dist/react/useUIElement.d.ts +0 -162
- package/dist/react/useUIElement.d.ts.map +0 -1
- package/dist/server/design-handlers.d.ts +0 -47
- package/dist/server/design-handlers.d.ts.map +0 -1
- package/dist/server/handlers.d.ts +0 -20
- package/dist/server/handlers.d.ts.map +0 -1
- package/dist/server/http-server.d.ts +0 -247
- package/dist/server/http-server.d.ts.map +0 -1
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/types.d.ts +0 -200
- package/dist/server/types.d.ts.map +0 -1
- package/dist/server/ws-connection.d.ts +0 -48
- package/dist/server/ws-connection.d.ts.map +0 -1
- package/dist/server/ws-event-bridge.d.ts +0 -113
- package/dist/server/ws-event-bridge.d.ts.map +0 -1
- package/dist/server/ws-protocol.d.ts +0 -48
- package/dist/server/ws-protocol.d.ts.map +0 -1
- package/dist/server/ws-types.d.ts +0 -65
- package/dist/server/ws-types.d.ts.map +0 -1
- package/dist/transport/CloudRelayClient.d.ts +0 -51
- package/dist/transport/CloudRelayClient.d.ts.map +0 -1
- package/dist/transport/DeviceAnnouncer.d.ts +0 -75
- package/dist/transport/DeviceAnnouncer.d.ts.map +0 -1
- package/dist/transport/index.d.ts +0 -3
- package/dist/transport/index.d.ts.map +0 -1
package/dist/react/index.mjs
CHANGED
|
@@ -53,9 +53,51 @@ var NativeUIBridgeRegistry = class {
|
|
|
53
53
|
this.components = /* @__PURE__ */ new Map();
|
|
54
54
|
this.workflows = /* @__PURE__ */ new Map();
|
|
55
55
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
56
|
+
this.enrichers = {};
|
|
57
|
+
this.snapshotExtras = /* @__PURE__ */ new Map();
|
|
58
|
+
/**
|
|
59
|
+
* Sticky flag: flips `true` the first time any element is registered and
|
|
60
|
+
* stays `true` even after elements are unregistered. Lets agents distinguish
|
|
61
|
+
* "this route never wired any elements" from "this route registered then
|
|
62
|
+
* unmounted".
|
|
63
|
+
*/
|
|
64
|
+
this.everHadRegistrations = false;
|
|
56
65
|
this.config = config;
|
|
57
66
|
}
|
|
58
67
|
// ============================================================================
|
|
68
|
+
// Snapshot Enricher Slots
|
|
69
|
+
// ============================================================================
|
|
70
|
+
/**
|
|
71
|
+
* Register/replace canonical enrichers (modal/toast/undo). HMR-safe — calling
|
|
72
|
+
* with a partial set merges into existing slots instead of clobbering them.
|
|
73
|
+
*/
|
|
74
|
+
setEnrichers(e) {
|
|
75
|
+
this.enrichers = { ...this.enrichers, ...e };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Read-only accessor for the canonical enrichers slot. Returned object is
|
|
79
|
+
* the live reference, so callers can detect "no detector wired" via
|
|
80
|
+
* `getEnrichers().modalDetector === undefined`. Used by the test-hook HTTP
|
|
81
|
+
* handlers (`control/modal/push`, `control/modal/dismiss/:id`) which need
|
|
82
|
+
* to drive the ModalDetector directly without re-instantiating it.
|
|
83
|
+
*/
|
|
84
|
+
getEnrichers() {
|
|
85
|
+
return this.enrichers;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Register a custom snapshot enricher. The returned object will be
|
|
89
|
+
* `Object.assign`ed onto the snapshot, so use unique top-level keys to avoid
|
|
90
|
+
* clobbering canonical fields. Returns a disposer.
|
|
91
|
+
*/
|
|
92
|
+
registerSnapshotEnricher(name, fn) {
|
|
93
|
+
this.snapshotExtras.set(name, fn);
|
|
94
|
+
return () => this.unregisterSnapshotEnricher(name);
|
|
95
|
+
}
|
|
96
|
+
/** Remove a custom snapshot enricher by name */
|
|
97
|
+
unregisterSnapshotEnricher(name) {
|
|
98
|
+
this.snapshotExtras.delete(name);
|
|
99
|
+
}
|
|
100
|
+
// ============================================================================
|
|
59
101
|
// Element Management
|
|
60
102
|
// ============================================================================
|
|
61
103
|
/**
|
|
@@ -121,6 +163,7 @@ var NativeUIBridgeRegistry = class {
|
|
|
121
163
|
stateStyles
|
|
122
164
|
};
|
|
123
165
|
this.elements.set(id, registered);
|
|
166
|
+
this.everHadRegistrations = true;
|
|
124
167
|
this.emit("element:registered", { id, type, label });
|
|
125
168
|
if (this.config.verbose) {
|
|
126
169
|
console.log(`[ui-bridge-native] Registered element: ${id} (${type})`);
|
|
@@ -418,7 +461,7 @@ var NativeUIBridgeRegistry = class {
|
|
|
418
461
|
const currentRoute = routeInfo.currentRoute;
|
|
419
462
|
elements = elements.filter((e) => e.registrationRoute === currentRoute);
|
|
420
463
|
}
|
|
421
|
-
|
|
464
|
+
const snapshot = {
|
|
422
465
|
timestamp: Date.now(),
|
|
423
466
|
elements: elements.map((e) => {
|
|
424
467
|
const handlers = extractHandlerNames(e.props);
|
|
@@ -448,8 +491,52 @@ var NativeUIBridgeRegistry = class {
|
|
|
448
491
|
stepCount: w.steps.length
|
|
449
492
|
})),
|
|
450
493
|
currentRoute: routeInfo?.currentRoute ?? null,
|
|
451
|
-
segments: routeInfo?.segments
|
|
494
|
+
segments: routeInfo?.segments,
|
|
495
|
+
registration: this.getRegistrationCoverage()
|
|
452
496
|
};
|
|
497
|
+
if (this.enrichers.modalDetector) {
|
|
498
|
+
try {
|
|
499
|
+
snapshot.modalStack = this.enrichers.modalDetector.getSnapshotModalContext();
|
|
500
|
+
} catch (error3) {
|
|
501
|
+
if (this.config.verbose) {
|
|
502
|
+
console.warn(`[ui-bridge-native] modalDetector enricher threw:`, error3);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (this.enrichers.toastCapture) {
|
|
507
|
+
try {
|
|
508
|
+
snapshot.toasts = this.enrichers.toastCapture.getSnapshotToastContext();
|
|
509
|
+
} catch (error3) {
|
|
510
|
+
if (this.config.verbose) {
|
|
511
|
+
console.warn(`[ui-bridge-native] toastCapture enricher threw:`, error3);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (this.enrichers.undoTracker) {
|
|
516
|
+
try {
|
|
517
|
+
snapshot.undoRedo = this.enrichers.undoTracker.getSnapshotUndoContext();
|
|
518
|
+
} catch (error3) {
|
|
519
|
+
if (this.config.verbose) {
|
|
520
|
+
console.warn(`[ui-bridge-native] undoTracker enricher threw:`, error3);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
if (this.snapshotExtras.size > 0) {
|
|
525
|
+
const ctx = { elements, currentRoute: routeInfo?.currentRoute ?? null };
|
|
526
|
+
for (const [name, fn] of this.snapshotExtras) {
|
|
527
|
+
try {
|
|
528
|
+
const extra = fn(ctx);
|
|
529
|
+
if (extra && typeof extra === "object") {
|
|
530
|
+
Object.assign(snapshot, extra);
|
|
531
|
+
}
|
|
532
|
+
} catch (error3) {
|
|
533
|
+
if (this.config.verbose) {
|
|
534
|
+
console.warn(`[ui-bridge-native] snapshot enricher "${name}" threw:`, error3);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return snapshot;
|
|
453
540
|
}
|
|
454
541
|
/**
|
|
455
542
|
* Get registry statistics
|
|
@@ -461,6 +548,25 @@ var NativeUIBridgeRegistry = class {
|
|
|
461
548
|
workflows: this.workflows.size
|
|
462
549
|
};
|
|
463
550
|
}
|
|
551
|
+
/**
|
|
552
|
+
* Compute registration coverage metadata for the current registry state.
|
|
553
|
+
*
|
|
554
|
+
* Groups currently-registered elements by `registrationRoute`, bucketing
|
|
555
|
+
* elements without a route under `'?'`. `everHadRegistrations` is sticky —
|
|
556
|
+
* once true, it stays true for the lifetime of the registry instance.
|
|
557
|
+
*/
|
|
558
|
+
getRegistrationCoverage() {
|
|
559
|
+
const byRoute = {};
|
|
560
|
+
for (const element of this.elements.values()) {
|
|
561
|
+
const key = element.registrationRoute == null || element.registrationRoute === "" ? "?" : element.registrationRoute;
|
|
562
|
+
byRoute[key] = (byRoute[key] ?? 0) + 1;
|
|
563
|
+
}
|
|
564
|
+
return {
|
|
565
|
+
totalRegistered: this.elements.size,
|
|
566
|
+
everHadRegistrations: this.everHadRegistrations,
|
|
567
|
+
byRoute
|
|
568
|
+
};
|
|
569
|
+
}
|
|
464
570
|
/**
|
|
465
571
|
* Clear all registrations
|
|
466
572
|
*/
|
|
@@ -543,6 +649,23 @@ function sleep(ms) {
|
|
|
543
649
|
var DefaultNativeActionExecutor = class {
|
|
544
650
|
constructor(registry) {
|
|
545
651
|
this.registry = registry;
|
|
652
|
+
this.actionListeners = /* @__PURE__ */ new Set();
|
|
653
|
+
}
|
|
654
|
+
onActionExecuted(listener) {
|
|
655
|
+
this.actionListeners.add(listener);
|
|
656
|
+
return () => {
|
|
657
|
+
this.actionListeners.delete(listener);
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
/** Notify listeners out-of-band; isolate each invocation so one bad listener can't poison the rest. */
|
|
661
|
+
emitActionEvent(event) {
|
|
662
|
+
for (const listener of this.actionListeners) {
|
|
663
|
+
try {
|
|
664
|
+
listener(event);
|
|
665
|
+
} catch (error3) {
|
|
666
|
+
console.warn(`[ui-bridge-native] Action listener error:`, error3);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
546
669
|
}
|
|
547
670
|
/**
|
|
548
671
|
* Execute an action on an element
|
|
@@ -556,11 +679,23 @@ var DefaultNativeActionExecutor = class {
|
|
|
556
679
|
registered = findElementByIdentifier(elementId) ?? void 0;
|
|
557
680
|
}
|
|
558
681
|
if (!registered) {
|
|
682
|
+
const failTime = Date.now();
|
|
683
|
+
const errorMsg = `Element not found: ${elementId}`;
|
|
684
|
+
this.emitActionEvent({
|
|
685
|
+
elementId,
|
|
686
|
+
action: request.action,
|
|
687
|
+
params: request.params,
|
|
688
|
+
success: false,
|
|
689
|
+
error: errorMsg,
|
|
690
|
+
timestamp: failTime,
|
|
691
|
+
requestId: request.requestId,
|
|
692
|
+
durationMs: failTime - startTime
|
|
693
|
+
});
|
|
559
694
|
return {
|
|
560
695
|
success: false,
|
|
561
|
-
error:
|
|
562
|
-
durationMs:
|
|
563
|
-
timestamp:
|
|
696
|
+
error: errorMsg,
|
|
697
|
+
durationMs: failTime - startTime,
|
|
698
|
+
timestamp: failTime,
|
|
564
699
|
requestId: request.requestId
|
|
565
700
|
};
|
|
566
701
|
}
|
|
@@ -568,33 +703,67 @@ var DefaultNativeActionExecutor = class {
|
|
|
568
703
|
const waitResult = await this.waitForElementInternal(registered.id, request.waitOptions);
|
|
569
704
|
waitDurationMs = waitResult.waitedMs;
|
|
570
705
|
if (!waitResult.met) {
|
|
706
|
+
const failTime = Date.now();
|
|
707
|
+
const errorMsg = waitResult.error || "Wait condition not met";
|
|
708
|
+
this.emitActionEvent({
|
|
709
|
+
elementId,
|
|
710
|
+
action: request.action,
|
|
711
|
+
params: request.params,
|
|
712
|
+
success: false,
|
|
713
|
+
error: errorMsg,
|
|
714
|
+
timestamp: failTime,
|
|
715
|
+
requestId: request.requestId,
|
|
716
|
+
durationMs: failTime - startTime
|
|
717
|
+
});
|
|
571
718
|
return {
|
|
572
719
|
success: false,
|
|
573
|
-
error:
|
|
574
|
-
durationMs:
|
|
575
|
-
timestamp:
|
|
720
|
+
error: errorMsg,
|
|
721
|
+
durationMs: failTime - startTime,
|
|
722
|
+
timestamp: failTime,
|
|
576
723
|
requestId: request.requestId,
|
|
577
724
|
waitDurationMs
|
|
578
725
|
};
|
|
579
726
|
}
|
|
580
727
|
}
|
|
581
728
|
const result = await this.performAction(registered, request.action, request.params);
|
|
729
|
+
const successTime = Date.now();
|
|
730
|
+
this.emitActionEvent({
|
|
731
|
+
elementId,
|
|
732
|
+
action: request.action,
|
|
733
|
+
params: request.params,
|
|
734
|
+
success: true,
|
|
735
|
+
timestamp: successTime,
|
|
736
|
+
requestId: request.requestId,
|
|
737
|
+
durationMs: successTime - startTime
|
|
738
|
+
});
|
|
582
739
|
return {
|
|
583
740
|
success: true,
|
|
584
741
|
elementState: registered.getState(),
|
|
585
742
|
result,
|
|
586
|
-
durationMs:
|
|
587
|
-
timestamp:
|
|
743
|
+
durationMs: successTime - startTime,
|
|
744
|
+
timestamp: successTime,
|
|
588
745
|
requestId: request.requestId,
|
|
589
746
|
waitDurationMs
|
|
590
747
|
};
|
|
591
748
|
} catch (error3) {
|
|
749
|
+
const failTime = Date.now();
|
|
750
|
+
const errorMsg = error3 instanceof Error ? error3.message : String(error3);
|
|
751
|
+
this.emitActionEvent({
|
|
752
|
+
elementId,
|
|
753
|
+
action: request.action,
|
|
754
|
+
params: request.params,
|
|
755
|
+
success: false,
|
|
756
|
+
error: errorMsg,
|
|
757
|
+
timestamp: failTime,
|
|
758
|
+
requestId: request.requestId,
|
|
759
|
+
durationMs: failTime - startTime
|
|
760
|
+
});
|
|
592
761
|
return {
|
|
593
762
|
success: false,
|
|
594
|
-
error:
|
|
763
|
+
error: errorMsg,
|
|
595
764
|
stack: error3 instanceof Error ? error3.stack : void 0,
|
|
596
|
-
durationMs:
|
|
597
|
-
timestamp:
|
|
765
|
+
durationMs: failTime - startTime,
|
|
766
|
+
timestamp: failTime,
|
|
598
767
|
requestId: request.requestId,
|
|
599
768
|
waitDurationMs
|
|
600
769
|
};
|
|
@@ -1753,6 +1922,20 @@ function createServerHandlers(registry, executor, config) {
|
|
|
1753
1922
|
return success2(response);
|
|
1754
1923
|
},
|
|
1755
1924
|
// Discovery
|
|
1925
|
+
//
|
|
1926
|
+
// Native parity with the runner's `POST /ui-bridge/ai/find`. The runner
|
|
1927
|
+
// matches a free-text `query` against label > text > value > aria-label >
|
|
1928
|
+
// placeholder > name. We replicate that here against the native registry's
|
|
1929
|
+
// analogues:
|
|
1930
|
+
// 1. `label` (the registered display label)
|
|
1931
|
+
// 2. identifier fields: `testId`, `accessibilityLabel`, `treePath`, `uiId`
|
|
1932
|
+
// 3. registered handler names (e.g. `onPress`, `onChangeText`)
|
|
1933
|
+
//
|
|
1934
|
+
// Match is case-insensitive substring. Empty/missing query falls back to
|
|
1935
|
+
// the structured filters (types/visibleOnly/etc.) so callers that don't
|
|
1936
|
+
// pass `query` still get the legacy behaviour. Once we've narrowed by
|
|
1937
|
+
// query the response is shaped through the executor's NativeFindResponse
|
|
1938
|
+
// envelope so the wire format stays stable.
|
|
1756
1939
|
find: async (ctx) => {
|
|
1757
1940
|
const body = ctx.body;
|
|
1758
1941
|
const response = await executor.find({
|
|
@@ -1760,9 +1943,49 @@ function createServerHandlers(registry, executor, config) {
|
|
|
1760
1943
|
testIdPattern: body?.testIdPattern,
|
|
1761
1944
|
accessibilityLabelPattern: body?.accessibilityLabelPattern,
|
|
1762
1945
|
visibleOnly: body?.visibleOnly,
|
|
1763
|
-
limit
|
|
1946
|
+
// When `query` is supplied we apply our own limit *after* ranking so
|
|
1947
|
+
// the highest-priority matches survive the cap; defer slicing to us
|
|
1948
|
+
// by widening here.
|
|
1949
|
+
limit: body?.query && body.query.length > 0 ? void 0 : body?.limit
|
|
1950
|
+
});
|
|
1951
|
+
const rawQuery = typeof body?.query === "string" ? body.query.trim() : "";
|
|
1952
|
+
if (rawQuery.length === 0) {
|
|
1953
|
+
return success2(response);
|
|
1954
|
+
}
|
|
1955
|
+
const needle = rawQuery.toLowerCase();
|
|
1956
|
+
const containsNeedle = (value) => typeof value === "string" && value.toLowerCase().includes(needle);
|
|
1957
|
+
const scored = [];
|
|
1958
|
+
for (const el of response.elements) {
|
|
1959
|
+
let priorityRank = Number.POSITIVE_INFINITY;
|
|
1960
|
+
if (containsNeedle(el.label)) {
|
|
1961
|
+
priorityRank = Math.min(priorityRank, 0);
|
|
1962
|
+
}
|
|
1963
|
+
const ident = el.identifier;
|
|
1964
|
+
if (ident) {
|
|
1965
|
+
if (containsNeedle(ident.testId)) priorityRank = Math.min(priorityRank, 1);
|
|
1966
|
+
if (containsNeedle(ident.accessibilityLabel)) priorityRank = Math.min(priorityRank, 1);
|
|
1967
|
+
if (containsNeedle(ident.treePath)) priorityRank = Math.min(priorityRank, 1);
|
|
1968
|
+
if (containsNeedle(ident.uiId)) priorityRank = Math.min(priorityRank, 1);
|
|
1969
|
+
}
|
|
1970
|
+
if (priorityRank === Number.POSITIVE_INFINITY) {
|
|
1971
|
+
const registered = registry.getElement(el.id);
|
|
1972
|
+
const handlers = extractHandlerNames(registered?.props);
|
|
1973
|
+
if (handlers.some((h) => h.toLowerCase().includes(needle))) {
|
|
1974
|
+
priorityRank = 2;
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
if (priorityRank !== Number.POSITIVE_INFINITY) {
|
|
1978
|
+
scored.push({ priorityRank, element: el });
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
scored.sort((a, b) => a.priorityRank - b.priorityRank);
|
|
1982
|
+
const limit = body?.limit && body.limit > 0 ? body.limit : void 0;
|
|
1983
|
+
const filtered = limit ? scored.slice(0, limit) : scored;
|
|
1984
|
+
return success2({
|
|
1985
|
+
...response,
|
|
1986
|
+
elements: filtered.map((s) => s.element),
|
|
1987
|
+
total: filtered.length
|
|
1764
1988
|
});
|
|
1765
|
-
return success2(response);
|
|
1766
1989
|
},
|
|
1767
1990
|
getSnapshot: async (ctx) => {
|
|
1768
1991
|
const visibleOnly = ctx.query?.visibleOnly === "true" || ctx.body?.visibleOnly === true;
|
|
@@ -1863,15 +2086,162 @@ function createServerHandlers(registry, executor, config) {
|
|
|
1863
2086
|
getMethods: async () => {
|
|
1864
2087
|
return error2("Methods introspection not configured", "NOT_SUPPORTED");
|
|
1865
2088
|
},
|
|
2089
|
+
// AI helpers
|
|
2090
|
+
//
|
|
2091
|
+
// Mobile-side parity with the runner's `POST /ui-bridge/ai/fill-form`.
|
|
2092
|
+
// Each entry in `fields` is dispatched through the existing executor as a
|
|
2093
|
+
// `type` action with `clear: true` so the input value is replaced
|
|
2094
|
+
// atomically (the closest native equivalent to the web bridge's
|
|
2095
|
+
// "setValue" semantics — there is no `setValue` action on native).
|
|
2096
|
+
//
|
|
2097
|
+
// Failures do not short-circuit: every field is attempted, and the
|
|
2098
|
+
// response carries per-field success/error plus aggregate counts.
|
|
2099
|
+
fillForm: async (ctx) => {
|
|
2100
|
+
const body = ctx.body;
|
|
2101
|
+
const rawFields = body?.fields;
|
|
2102
|
+
if (!Array.isArray(rawFields)) {
|
|
2103
|
+
return error2('Request body must include "fields" array', "INVALID_REQUEST");
|
|
2104
|
+
}
|
|
2105
|
+
const results = [];
|
|
2106
|
+
for (let i = 0; i < rawFields.length; i++) {
|
|
2107
|
+
const field = rawFields[i];
|
|
2108
|
+
if (!field || typeof field !== "object") {
|
|
2109
|
+
results.push({
|
|
2110
|
+
elementId: "",
|
|
2111
|
+
success: false,
|
|
2112
|
+
error: `fields[${i}] must be an object with "elementId" and "value"`
|
|
2113
|
+
});
|
|
2114
|
+
continue;
|
|
2115
|
+
}
|
|
2116
|
+
const { elementId, value } = field;
|
|
2117
|
+
if (typeof elementId !== "string" || elementId.length === 0) {
|
|
2118
|
+
results.push({
|
|
2119
|
+
elementId: typeof elementId === "string" ? elementId : "",
|
|
2120
|
+
success: false,
|
|
2121
|
+
error: `fields[${i}] is missing a non-empty "elementId"`
|
|
2122
|
+
});
|
|
2123
|
+
continue;
|
|
2124
|
+
}
|
|
2125
|
+
if (typeof value !== "string") {
|
|
2126
|
+
results.push({
|
|
2127
|
+
elementId,
|
|
2128
|
+
success: false,
|
|
2129
|
+
error: `fields[${i}] is missing a string "value"`
|
|
2130
|
+
});
|
|
2131
|
+
continue;
|
|
2132
|
+
}
|
|
2133
|
+
try {
|
|
2134
|
+
const response = await executor.executeAction(elementId, {
|
|
2135
|
+
action: "type",
|
|
2136
|
+
params: { text: value, clear: true }
|
|
2137
|
+
});
|
|
2138
|
+
if (response.success) {
|
|
2139
|
+
results.push({ elementId, success: true });
|
|
2140
|
+
} else {
|
|
2141
|
+
results.push({
|
|
2142
|
+
elementId,
|
|
2143
|
+
success: false,
|
|
2144
|
+
error: response.error ?? "Action failed"
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
} catch (err) {
|
|
2148
|
+
results.push({
|
|
2149
|
+
elementId,
|
|
2150
|
+
success: false,
|
|
2151
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2152
|
+
});
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
const succeededCount = results.filter((r) => r.success).length;
|
|
2156
|
+
const failedCount = results.length - succeededCount;
|
|
2157
|
+
return success2({ results, succeededCount, failedCount });
|
|
2158
|
+
},
|
|
2159
|
+
// Test hooks — modal stack manipulation
|
|
2160
|
+
//
|
|
2161
|
+
// These two endpoints exist so external automation runners can drive
|
|
2162
|
+
// ModalDetector state via HTTP, mirroring the in-tree `pushModal` /
|
|
2163
|
+
// `dismissModal` calls that components normally make. They're gated by
|
|
2164
|
+
// `features.testHooks` at the route layer (see http-server.ts); the
|
|
2165
|
+
// handlers themselves still defend against a missing modalDetector
|
|
2166
|
+
// because the bare server path used by CloudRelayClient may run before
|
|
2167
|
+
// enrichers are wired.
|
|
2168
|
+
pushModal: async (ctx) => {
|
|
2169
|
+
const detector = registry.getEnrichers().modalDetector;
|
|
2170
|
+
if (!detector) {
|
|
2171
|
+
return error2(
|
|
2172
|
+
"modal detector not registered",
|
|
2173
|
+
"ENRICHER_UNAVAILABLE"
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
const body = ctx.body;
|
|
2177
|
+
const id = body?.id;
|
|
2178
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
2179
|
+
return error2(
|
|
2180
|
+
'pushModal requires a non-empty "id"',
|
|
2181
|
+
"INVALID_REQUEST"
|
|
2182
|
+
);
|
|
2183
|
+
}
|
|
2184
|
+
const allowedTypes = ["modal", "sheet", "drawer", "popover", "alertdialog", "dialog"];
|
|
2185
|
+
const requestedType = body?.type;
|
|
2186
|
+
if (requestedType !== void 0 && (typeof requestedType !== "string" || !allowedTypes.includes(requestedType))) {
|
|
2187
|
+
return error2(
|
|
2188
|
+
`pushModal "type" must be one of: ${allowedTypes.join(", ")}`,
|
|
2189
|
+
"INVALID_REQUEST"
|
|
2190
|
+
);
|
|
2191
|
+
}
|
|
2192
|
+
detector.pushModal({
|
|
2193
|
+
id,
|
|
2194
|
+
title: body?.title,
|
|
2195
|
+
// requestedType is narrowed by the allowedTypes guard above; the cast
|
|
2196
|
+
// converts the validated string to the NativeModalInfo['type'] union.
|
|
2197
|
+
type: requestedType,
|
|
2198
|
+
blocking: body?.blocking,
|
|
2199
|
+
dismissible: body?.dismissible
|
|
2200
|
+
});
|
|
2201
|
+
const stored = detector.getActive().find((m) => m.id === id);
|
|
2202
|
+
return success2({
|
|
2203
|
+
pushed: true,
|
|
2204
|
+
id,
|
|
2205
|
+
title: stored?.title,
|
|
2206
|
+
type: stored?.type,
|
|
2207
|
+
blocking: stored?.blocking,
|
|
2208
|
+
dismissible: stored?.dismissible,
|
|
2209
|
+
pushedAt: stored?.detectedAt ?? Date.now()
|
|
2210
|
+
});
|
|
2211
|
+
},
|
|
2212
|
+
dismissModal: async (ctx) => {
|
|
2213
|
+
const detector = registry.getEnrichers().modalDetector;
|
|
2214
|
+
if (!detector) {
|
|
2215
|
+
return error2(
|
|
2216
|
+
"modal detector not registered",
|
|
2217
|
+
"ENRICHER_UNAVAILABLE"
|
|
2218
|
+
);
|
|
2219
|
+
}
|
|
2220
|
+
const { id } = ctx.params;
|
|
2221
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
2222
|
+
return error2(
|
|
2223
|
+
"dismissModal requires a modal id in the path",
|
|
2224
|
+
"INVALID_REQUEST"
|
|
2225
|
+
);
|
|
2226
|
+
}
|
|
2227
|
+
const wasPresent = detector.getActive().some((m) => m.id === id);
|
|
2228
|
+
if (!wasPresent) {
|
|
2229
|
+
return error2(`modal not found: ${id}`, "MODAL_NOT_FOUND");
|
|
2230
|
+
}
|
|
2231
|
+
detector.dismissModal(id);
|
|
2232
|
+
return success2({ dismissed: id });
|
|
2233
|
+
},
|
|
1866
2234
|
// Design Review
|
|
1867
2235
|
...designHandlers,
|
|
1868
2236
|
// Health
|
|
1869
2237
|
health: async () => {
|
|
1870
2238
|
const stats = registry.getStats();
|
|
2239
|
+
const registration = registry.getRegistrationCoverage();
|
|
1871
2240
|
const response = {
|
|
1872
2241
|
status: "healthy",
|
|
1873
2242
|
timestamp: Date.now(),
|
|
1874
|
-
...stats
|
|
2243
|
+
...stats,
|
|
2244
|
+
registration
|
|
1875
2245
|
};
|
|
1876
2246
|
if (config?.appInfo) {
|
|
1877
2247
|
response.uiBridge = {
|
|
@@ -1972,7 +2342,24 @@ var WS_ROUTES = [
|
|
|
1972
2342
|
{ pattern: "design/evaluate/baseline", handler: "saveBaseline", httpMethods: ["POST"] },
|
|
1973
2343
|
{ pattern: "design/evaluate/diff", handler: "diffBaseline", httpMethods: ["POST"] },
|
|
1974
2344
|
// Meta / Introspection
|
|
1975
|
-
{ pattern: "meta/methods", handler: "getMethods", httpMethods: ["GET"] }
|
|
2345
|
+
{ pattern: "meta/methods", handler: "getMethods", httpMethods: ["GET"] },
|
|
2346
|
+
// AI helpers — mobile parity with the runner's `/ui-bridge/ai/*` family.
|
|
2347
|
+
{ pattern: "ai/fill-form", handler: "fillForm", httpMethods: ["POST"] },
|
|
2348
|
+
// Test hooks — gated behind `features.testHooks`. Let external runners
|
|
2349
|
+
// drive the ModalDetector over HTTP so tests can flip `snapshot.modalStack`
|
|
2350
|
+
// without mounting React components.
|
|
2351
|
+
{
|
|
2352
|
+
pattern: "control/modal/push",
|
|
2353
|
+
handler: "pushModal",
|
|
2354
|
+
httpMethods: ["POST"],
|
|
2355
|
+
requiresTestHooks: true
|
|
2356
|
+
},
|
|
2357
|
+
{
|
|
2358
|
+
pattern: "control/modal/dismiss/:id",
|
|
2359
|
+
handler: "dismissModal",
|
|
2360
|
+
httpMethods: ["POST"],
|
|
2361
|
+
requiresTestHooks: true
|
|
2362
|
+
}
|
|
1976
2363
|
];
|
|
1977
2364
|
var HANDLER_DESCRIPTIONS = {
|
|
1978
2365
|
health: "Liveness probe; returns app metadata + element count",
|
|
@@ -2005,7 +2392,10 @@ var HANDLER_DESCRIPTIONS = {
|
|
|
2005
2392
|
getQualityContexts: "List available quality evaluation contexts",
|
|
2006
2393
|
saveBaseline: "Save a quality baseline snapshot",
|
|
2007
2394
|
diffBaseline: "Diff current UI against a saved baseline",
|
|
2008
|
-
getMethods: "List every handler name (legacy introspection)"
|
|
2395
|
+
getMethods: "List every handler name (legacy introspection)",
|
|
2396
|
+
fillForm: "Fill multiple inputs in one call: body { fields: [{elementId, value}] } \u2014 each field dispatches `type` + `clear:true`",
|
|
2397
|
+
pushModal: "TEST HOOK \u2014 push a modal onto the snapshot.modalStack via the registry ModalDetector",
|
|
2398
|
+
dismissModal: "TEST HOOK \u2014 dismiss a modal by id from the snapshot.modalStack via the registry ModalDetector"
|
|
2009
2399
|
};
|
|
2010
2400
|
var ACTION_REFERENCE = [
|
|
2011
2401
|
{ name: "press", aliases: ["click"], params: "PressActionParams (optional position)" },
|
|
@@ -2022,10 +2412,11 @@ var ACTION_REFERENCE = [
|
|
|
2022
2412
|
{ name: "swipe", params: "SwipeActionParams { direction, distance }" },
|
|
2023
2413
|
{ name: "toggle", params: "none \u2014 flips boolean controls" }
|
|
2024
2414
|
];
|
|
2025
|
-
function buildRoutesPayload() {
|
|
2415
|
+
function buildRoutesPayload(testHooks) {
|
|
2026
2416
|
const entries = [];
|
|
2027
2417
|
for (const r of WS_ROUTES) {
|
|
2028
2418
|
if (!r.httpMethods) continue;
|
|
2419
|
+
if (r.requiresTestHooks && !testHooks) continue;
|
|
2029
2420
|
for (const m of r.httpMethods) {
|
|
2030
2421
|
entries.push({ method: m, path: `/ui-bridge/${r.pattern}` });
|
|
2031
2422
|
}
|
|
@@ -2035,11 +2426,11 @@ function buildRoutesPayload() {
|
|
|
2035
2426
|
entries.sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method));
|
|
2036
2427
|
return { routes: entries, count: entries.length };
|
|
2037
2428
|
}
|
|
2038
|
-
function buildHelpPayload() {
|
|
2429
|
+
function buildHelpPayload(testHooks) {
|
|
2039
2430
|
return {
|
|
2040
2431
|
version: 1,
|
|
2041
2432
|
description: "UI Bridge Native HTTP surface. Prefix every path with /ui-bridge/.",
|
|
2042
|
-
routes: WS_ROUTES.map((r) => ({
|
|
2433
|
+
routes: WS_ROUTES.filter((r) => !r.requiresTestHooks || testHooks).map((r) => ({
|
|
2043
2434
|
pattern: r.pattern,
|
|
2044
2435
|
httpMethods: r.httpMethods ?? [],
|
|
2045
2436
|
wsOnly: !r.httpMethods,
|
|
@@ -2075,7 +2466,8 @@ var NativeUIBridgeServer = class {
|
|
|
2075
2466
|
appInfo: this.config.appInfo
|
|
2076
2467
|
});
|
|
2077
2468
|
this.handlers.getMethods = async () => {
|
|
2078
|
-
const
|
|
2469
|
+
const testHooks = this.config.testHooks === true;
|
|
2470
|
+
const methods = WS_ROUTES.filter((r) => !r.requiresTestHooks || testHooks).map((r) => ({
|
|
2079
2471
|
method: r.pattern,
|
|
2080
2472
|
httpMethods: r.httpMethods ? [...r.httpMethods] : void 0
|
|
2081
2473
|
}));
|
|
@@ -2118,7 +2510,11 @@ var NativeUIBridgeServer = class {
|
|
|
2118
2510
|
timestamp: Date.now()
|
|
2119
2511
|
};
|
|
2120
2512
|
} catch (e) {
|
|
2121
|
-
return {
|
|
2513
|
+
return {
|
|
2514
|
+
success: false,
|
|
2515
|
+
error: `Navigation failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
2516
|
+
timestamp: Date.now()
|
|
2517
|
+
};
|
|
2122
2518
|
}
|
|
2123
2519
|
};
|
|
2124
2520
|
this.handlers.pageReplace = async (ctx) => {
|
|
@@ -2144,7 +2540,7 @@ var NativeUIBridgeServer = class {
|
|
|
2144
2540
|
} catch (e) {
|
|
2145
2541
|
return {
|
|
2146
2542
|
success: false,
|
|
2147
|
-
error: `Replace navigation failed: ${e.message}`,
|
|
2543
|
+
error: `Replace navigation failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
2148
2544
|
timestamp: Date.now()
|
|
2149
2545
|
};
|
|
2150
2546
|
}
|
|
@@ -2163,7 +2559,7 @@ var NativeUIBridgeServer = class {
|
|
|
2163
2559
|
} catch (e) {
|
|
2164
2560
|
return {
|
|
2165
2561
|
success: false,
|
|
2166
|
-
error: `Back navigation failed: ${e.message}`,
|
|
2562
|
+
error: `Back navigation failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
2167
2563
|
timestamp: Date.now()
|
|
2168
2564
|
};
|
|
2169
2565
|
}
|
|
@@ -2184,7 +2580,11 @@ var NativeUIBridgeServer = class {
|
|
|
2184
2580
|
timestamp: Date.now()
|
|
2185
2581
|
};
|
|
2186
2582
|
} catch (e) {
|
|
2187
|
-
return {
|
|
2583
|
+
return {
|
|
2584
|
+
success: false,
|
|
2585
|
+
error: `Refresh failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
2586
|
+
timestamp: Date.now()
|
|
2587
|
+
};
|
|
2188
2588
|
}
|
|
2189
2589
|
};
|
|
2190
2590
|
}
|
|
@@ -2242,7 +2642,7 @@ var NativeUIBridgeServer = class {
|
|
|
2242
2642
|
} catch (e) {
|
|
2243
2643
|
return {
|
|
2244
2644
|
success: false,
|
|
2245
|
-
error: `Screenshot capture failed: ${e.message}`,
|
|
2645
|
+
error: `Screenshot capture failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
2246
2646
|
timestamp: Date.now()
|
|
2247
2647
|
};
|
|
2248
2648
|
}
|
|
@@ -2797,9 +3197,11 @@ var NativeUIBridgeServer = class {
|
|
|
2797
3197
|
* "control/component/my-comp/action/submit" → handlers.executeComponentAction
|
|
2798
3198
|
*/
|
|
2799
3199
|
async routeMethodToHandler(method, params) {
|
|
3200
|
+
const testHooks = this.config.testHooks === true;
|
|
2800
3201
|
for (const route of WS_ROUTES) {
|
|
2801
3202
|
const pathParams = parsePath(route.pattern, method);
|
|
2802
3203
|
if (!pathParams) continue;
|
|
3204
|
+
if (route.requiresTestHooks && !testHooks) continue;
|
|
2803
3205
|
const ctx = {
|
|
2804
3206
|
params: pathParams,
|
|
2805
3207
|
query: params.query && typeof params.query === "object" ? params.query : {},
|
|
@@ -2877,17 +3279,18 @@ var NativeUIBridgeServer = class {
|
|
|
2877
3279
|
};
|
|
2878
3280
|
}
|
|
2879
3281
|
const stripped = path.slice(PREFIX.length);
|
|
3282
|
+
const testHooks = this.config.testHooks === true;
|
|
2880
3283
|
if (method === "GET" && stripped === "_help") {
|
|
2881
3284
|
return {
|
|
2882
3285
|
success: true,
|
|
2883
|
-
data: buildHelpPayload(),
|
|
3286
|
+
data: buildHelpPayload(testHooks),
|
|
2884
3287
|
timestamp: Date.now()
|
|
2885
3288
|
};
|
|
2886
3289
|
}
|
|
2887
3290
|
if (method === "GET" && stripped === "_routes") {
|
|
2888
3291
|
return {
|
|
2889
3292
|
success: true,
|
|
2890
|
-
data: buildRoutesPayload(),
|
|
3293
|
+
data: buildRoutesPayload(testHooks),
|
|
2891
3294
|
timestamp: Date.now()
|
|
2892
3295
|
};
|
|
2893
3296
|
}
|
|
@@ -2898,6 +3301,9 @@ var NativeUIBridgeServer = class {
|
|
|
2898
3301
|
if (!route.httpMethods) {
|
|
2899
3302
|
continue;
|
|
2900
3303
|
}
|
|
3304
|
+
if (route.requiresTestHooks && !testHooks) {
|
|
3305
|
+
continue;
|
|
3306
|
+
}
|
|
2901
3307
|
if (!route.httpMethods.includes(method)) {
|
|
2902
3308
|
patternMatchedButWrongVerb = true;
|
|
2903
3309
|
continue;
|
|
@@ -3191,6 +3597,27 @@ var WebSocketEventBridge = class {
|
|
|
3191
3597
|
}, this.heartbeatIntervalMs);
|
|
3192
3598
|
}
|
|
3193
3599
|
};
|
|
3600
|
+
|
|
3601
|
+
// src/transport/logger.ts
|
|
3602
|
+
function createTransportLogger() {
|
|
3603
|
+
return {
|
|
3604
|
+
silent: false,
|
|
3605
|
+
log(message, ...args) {
|
|
3606
|
+
if (!this.silent) {
|
|
3607
|
+
console.log(message, ...args);
|
|
3608
|
+
}
|
|
3609
|
+
},
|
|
3610
|
+
warn(message, ...args) {
|
|
3611
|
+
console.warn(message, ...args);
|
|
3612
|
+
},
|
|
3613
|
+
error(message, ...args) {
|
|
3614
|
+
console.error(message, ...args);
|
|
3615
|
+
}
|
|
3616
|
+
};
|
|
3617
|
+
}
|
|
3618
|
+
var transportLogger = createTransportLogger();
|
|
3619
|
+
|
|
3620
|
+
// src/transport/DeviceAnnouncer.ts
|
|
3194
3621
|
var DEFAULT_PORT = 8087;
|
|
3195
3622
|
var RECONNECT_INITIAL_DELAY_MS = 2e3;
|
|
3196
3623
|
var RECONNECT_MAX_DELAY_MS = 6e4;
|
|
@@ -3225,7 +3652,7 @@ var DeviceAnnouncer = class {
|
|
|
3225
3652
|
*/
|
|
3226
3653
|
async startMdnsAdvertise(ZeroconfCtor) {
|
|
3227
3654
|
if (!ZeroconfCtor) {
|
|
3228
|
-
|
|
3655
|
+
transportLogger.log(
|
|
3229
3656
|
"[DeviceAnnouncer] mDNS advertisement skipped (no Zeroconf constructor provided). Pass one to enable mDNS."
|
|
3230
3657
|
);
|
|
3231
3658
|
return;
|
|
@@ -3241,9 +3668,9 @@ var DeviceAnnouncer = class {
|
|
|
3241
3668
|
pairing_token: this.pairingToken
|
|
3242
3669
|
});
|
|
3243
3670
|
this.state = { ...this.state, mdnsActive: true };
|
|
3244
|
-
|
|
3671
|
+
transportLogger.log(`[DeviceAnnouncer] mDNS: advertising "${serviceName}" on port ${port}`);
|
|
3245
3672
|
} catch (err) {
|
|
3246
|
-
|
|
3673
|
+
transportLogger.warn("[DeviceAnnouncer] mDNS start failed:", err);
|
|
3247
3674
|
}
|
|
3248
3675
|
}
|
|
3249
3676
|
// ── Cloud relay ───────────────────────────────────────────────────────────
|
|
@@ -3259,7 +3686,7 @@ var DeviceAnnouncer = class {
|
|
|
3259
3686
|
}
|
|
3260
3687
|
if (this.stopped) return;
|
|
3261
3688
|
const url = `${this.config.cloudRelayUrl}?token=${encodeURIComponent(this.config.cloudToken)}`;
|
|
3262
|
-
|
|
3689
|
+
transportLogger.log("[DeviceAnnouncer] Cloud relay: connecting to", url);
|
|
3263
3690
|
const ws = new WebSocket(url);
|
|
3264
3691
|
this.cloudWs = ws;
|
|
3265
3692
|
ws.onopen = () => {
|
|
@@ -3269,7 +3696,7 @@ var DeviceAnnouncer = class {
|
|
|
3269
3696
|
}
|
|
3270
3697
|
this.reconnectDelay = RECONNECT_INITIAL_DELAY_MS;
|
|
3271
3698
|
this.state = { ...this.state, cloudConnected: true };
|
|
3272
|
-
|
|
3699
|
+
transportLogger.log("[DeviceAnnouncer] Cloud relay: connected");
|
|
3273
3700
|
ws.send(
|
|
3274
3701
|
JSON.stringify({
|
|
3275
3702
|
type: "device_register",
|
|
@@ -3284,7 +3711,7 @@ var DeviceAnnouncer = class {
|
|
|
3284
3711
|
ws.onmessage = (event) => {
|
|
3285
3712
|
try {
|
|
3286
3713
|
const msg = JSON.parse(event.data);
|
|
3287
|
-
|
|
3714
|
+
transportLogger.log("[DeviceAnnouncer] Cloud relay message:", msg.type);
|
|
3288
3715
|
} catch {
|
|
3289
3716
|
}
|
|
3290
3717
|
};
|
|
@@ -3295,7 +3722,7 @@ var DeviceAnnouncer = class {
|
|
|
3295
3722
|
}
|
|
3296
3723
|
};
|
|
3297
3724
|
ws.onerror = (err) => {
|
|
3298
|
-
|
|
3725
|
+
transportLogger.warn("[DeviceAnnouncer] Cloud relay error:", err);
|
|
3299
3726
|
};
|
|
3300
3727
|
}
|
|
3301
3728
|
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
@@ -3330,7 +3757,7 @@ var DeviceAnnouncer = class {
|
|
|
3330
3757
|
scheduleReconnect() {
|
|
3331
3758
|
if (this.stopped) return;
|
|
3332
3759
|
const delay = this.reconnectDelay;
|
|
3333
|
-
|
|
3760
|
+
transportLogger.log(`[DeviceAnnouncer] Cloud relay: reconnecting in ${delay}ms`);
|
|
3334
3761
|
this.reconnectTimer = setTimeout(() => {
|
|
3335
3762
|
this.reconnectTimer = null;
|
|
3336
3763
|
if (!this.stopped) {
|
|
@@ -3392,7 +3819,7 @@ var CloudRelayClient = class {
|
|
|
3392
3819
|
connect() {
|
|
3393
3820
|
if (this.stopped) return;
|
|
3394
3821
|
const url = `${this.config.relayUrl}?token=${encodeURIComponent(this.config.authToken)}`;
|
|
3395
|
-
|
|
3822
|
+
transportLogger.log("[CloudRelayClient] Connecting to", url);
|
|
3396
3823
|
const ws = new WebSocket(url);
|
|
3397
3824
|
this.ws = ws;
|
|
3398
3825
|
ws.onopen = () => {
|
|
@@ -3402,7 +3829,7 @@ var CloudRelayClient = class {
|
|
|
3402
3829
|
}
|
|
3403
3830
|
this._isConnected = true;
|
|
3404
3831
|
this.reconnectDelay = INITIAL_RECONNECT_DELAY_MS;
|
|
3405
|
-
|
|
3832
|
+
transportLogger.log("[CloudRelayClient] Connected to relay");
|
|
3406
3833
|
ws.send(
|
|
3407
3834
|
JSON.stringify({
|
|
3408
3835
|
type: "device_register",
|
|
@@ -3420,7 +3847,7 @@ var CloudRelayClient = class {
|
|
|
3420
3847
|
}
|
|
3421
3848
|
};
|
|
3422
3849
|
ws.onerror = (err) => {
|
|
3423
|
-
|
|
3850
|
+
transportLogger.warn("[CloudRelayClient] WebSocket error:", err);
|
|
3424
3851
|
};
|
|
3425
3852
|
}
|
|
3426
3853
|
async onMessage(raw) {
|
|
@@ -3428,12 +3855,12 @@ var CloudRelayClient = class {
|
|
|
3428
3855
|
try {
|
|
3429
3856
|
msg = JSON.parse(raw);
|
|
3430
3857
|
} catch {
|
|
3431
|
-
|
|
3858
|
+
transportLogger.warn("[CloudRelayClient] Received non-JSON message");
|
|
3432
3859
|
return;
|
|
3433
3860
|
}
|
|
3434
3861
|
if (msg["type"] !== "tunnel_request") return;
|
|
3435
3862
|
if (typeof msg["id"] !== "string" || typeof msg["method"] !== "string" || typeof msg["path"] !== "string") {
|
|
3436
|
-
|
|
3863
|
+
transportLogger.warn("[CloudRelayClient] Malformed tunnel_request \u2014 missing required fields");
|
|
3437
3864
|
return;
|
|
3438
3865
|
}
|
|
3439
3866
|
const req = {
|
|
@@ -3474,7 +3901,7 @@ var CloudRelayClient = class {
|
|
|
3474
3901
|
body: bodyValue
|
|
3475
3902
|
};
|
|
3476
3903
|
} catch (err) {
|
|
3477
|
-
|
|
3904
|
+
transportLogger.error("[CloudRelayClient] Error handling tunneled request:", err);
|
|
3478
3905
|
return {
|
|
3479
3906
|
id: req.id,
|
|
3480
3907
|
status: 500,
|
|
@@ -3489,7 +3916,7 @@ var CloudRelayClient = class {
|
|
|
3489
3916
|
scheduleReconnect() {
|
|
3490
3917
|
if (this.stopped) return;
|
|
3491
3918
|
const delay = this.reconnectDelay;
|
|
3492
|
-
|
|
3919
|
+
transportLogger.log(`[CloudRelayClient] Reconnecting in ${delay}ms`);
|
|
3493
3920
|
this.reconnectTimer = setTimeout(() => {
|
|
3494
3921
|
this.reconnectTimer = null;
|
|
3495
3922
|
if (!this.stopped) {
|
|
@@ -3512,6 +3939,192 @@ function parseQueryParams(path) {
|
|
|
3512
3939
|
}
|
|
3513
3940
|
return result;
|
|
3514
3941
|
}
|
|
3942
|
+
|
|
3943
|
+
// src/modal/modal-detector.ts
|
|
3944
|
+
var ModalDetector = class {
|
|
3945
|
+
constructor() {
|
|
3946
|
+
/** Map preserves O(1) idempotent replace by id; ordering comes from detectedAt. */
|
|
3947
|
+
this.modals = /* @__PURE__ */ new Map();
|
|
3948
|
+
}
|
|
3949
|
+
pushModal(modal) {
|
|
3950
|
+
const existing = this.modals.get(modal.id);
|
|
3951
|
+
const detectedAt = existing ? existing.detectedAt : Date.now();
|
|
3952
|
+
const next = {
|
|
3953
|
+
id: modal.id,
|
|
3954
|
+
title: modal.title,
|
|
3955
|
+
type: modal.type ?? "modal",
|
|
3956
|
+
blocking: modal.blocking ?? true,
|
|
3957
|
+
dismissible: modal.dismissible ?? true,
|
|
3958
|
+
detectedAt
|
|
3959
|
+
};
|
|
3960
|
+
this.modals.set(modal.id, next);
|
|
3961
|
+
}
|
|
3962
|
+
/**
|
|
3963
|
+
* Remove a modal from the stack. Returns `true` if a modal with that id
|
|
3964
|
+
* was actually deleted, `false` if nothing matched (idempotent so repeated
|
|
3965
|
+
* calls don't throw, but callers — e.g. the HTTP test-hook endpoint — can
|
|
3966
|
+
* surface the miss to clients).
|
|
3967
|
+
*/
|
|
3968
|
+
dismissModal(id) {
|
|
3969
|
+
return this.modals.delete(id);
|
|
3970
|
+
}
|
|
3971
|
+
getSnapshotModalContext() {
|
|
3972
|
+
const active = this.getActive();
|
|
3973
|
+
const ctx = {
|
|
3974
|
+
modals: active,
|
|
3975
|
+
hasBlockingModal: active.some((m) => m.blocking),
|
|
3976
|
+
count: active.length
|
|
3977
|
+
};
|
|
3978
|
+
if (active.length > 0) {
|
|
3979
|
+
ctx.topModal = active[active.length - 1];
|
|
3980
|
+
}
|
|
3981
|
+
return ctx;
|
|
3982
|
+
}
|
|
3983
|
+
getActive() {
|
|
3984
|
+
return Array.from(this.modals.values()).sort((a, b) => a.detectedAt - b.detectedAt);
|
|
3985
|
+
}
|
|
3986
|
+
clear() {
|
|
3987
|
+
this.modals.clear();
|
|
3988
|
+
}
|
|
3989
|
+
};
|
|
3990
|
+
|
|
3991
|
+
// src/toast/toast-capture.ts
|
|
3992
|
+
var DEFAULT_MAX_RECENT = 50;
|
|
3993
|
+
var ToastCapture = class {
|
|
3994
|
+
constructor(config = {}) {
|
|
3995
|
+
this.active = /* @__PURE__ */ new Map();
|
|
3996
|
+
this.recent = [];
|
|
3997
|
+
this.totalCaptured = 0;
|
|
3998
|
+
this.maxRecent = config.maxRecent ?? DEFAULT_MAX_RECENT;
|
|
3999
|
+
}
|
|
4000
|
+
recordToast(toast) {
|
|
4001
|
+
if (this.active.has(toast.id)) {
|
|
4002
|
+
return;
|
|
4003
|
+
}
|
|
4004
|
+
const appearedAt = toast.appearedAt ?? Date.now();
|
|
4005
|
+
const entry = {
|
|
4006
|
+
id: toast.id,
|
|
4007
|
+
message: toast.message,
|
|
4008
|
+
level: toast.level ?? "info",
|
|
4009
|
+
appearedAt,
|
|
4010
|
+
visible: true,
|
|
4011
|
+
durationMs: 0
|
|
4012
|
+
};
|
|
4013
|
+
this.active.set(toast.id, entry);
|
|
4014
|
+
this.totalCaptured += 1;
|
|
4015
|
+
}
|
|
4016
|
+
dismissToast(id) {
|
|
4017
|
+
const entry = this.active.get(id);
|
|
4018
|
+
if (!entry) {
|
|
4019
|
+
return;
|
|
4020
|
+
}
|
|
4021
|
+
this.active.delete(id);
|
|
4022
|
+
const dismissedAt = Date.now();
|
|
4023
|
+
const dismissed = {
|
|
4024
|
+
...entry,
|
|
4025
|
+
dismissedAt,
|
|
4026
|
+
visible: false,
|
|
4027
|
+
durationMs: dismissedAt - entry.appearedAt
|
|
4028
|
+
};
|
|
4029
|
+
this.recent.push(dismissed);
|
|
4030
|
+
if (this.recent.length > this.maxRecent) {
|
|
4031
|
+
this.recent.shift();
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
getSnapshotToastContext() {
|
|
4035
|
+
const now = Date.now();
|
|
4036
|
+
const active = Array.from(this.active.values()).map((entry) => ({
|
|
4037
|
+
...entry,
|
|
4038
|
+
durationMs: now - entry.appearedAt
|
|
4039
|
+
}));
|
|
4040
|
+
return {
|
|
4041
|
+
active,
|
|
4042
|
+
recent: [...this.recent],
|
|
4043
|
+
totalCaptured: this.totalCaptured
|
|
4044
|
+
};
|
|
4045
|
+
}
|
|
4046
|
+
clear() {
|
|
4047
|
+
this.active.clear();
|
|
4048
|
+
this.recent = [];
|
|
4049
|
+
this.totalCaptured = 0;
|
|
4050
|
+
}
|
|
4051
|
+
};
|
|
4052
|
+
|
|
4053
|
+
// src/undo/undo-tracker.ts
|
|
4054
|
+
var DEFAULT_MAX_HISTORY = 50;
|
|
4055
|
+
function buildSummary(ctx) {
|
|
4056
|
+
if (!ctx.canUndo && !ctx.canRedo) {
|
|
4057
|
+
return "no undo available";
|
|
4058
|
+
}
|
|
4059
|
+
const parts = [];
|
|
4060
|
+
if (ctx.canUndo) {
|
|
4061
|
+
parts.push(`undo: '${ctx.undoDescription ?? "available"}'`);
|
|
4062
|
+
}
|
|
4063
|
+
if (ctx.canRedo) {
|
|
4064
|
+
parts.push(`redo: '${ctx.redoDescription ?? "available"}'`);
|
|
4065
|
+
}
|
|
4066
|
+
return parts.join(" / ");
|
|
4067
|
+
}
|
|
4068
|
+
var UndoTracker = class {
|
|
4069
|
+
constructor(config = {}) {
|
|
4070
|
+
this.actions = [];
|
|
4071
|
+
this.declaredState = null;
|
|
4072
|
+
this.maxHistory = config.maxHistory ?? DEFAULT_MAX_HISTORY;
|
|
4073
|
+
}
|
|
4074
|
+
recordAction(record) {
|
|
4075
|
+
this.actions.push(record);
|
|
4076
|
+
if (this.actions.length > this.maxHistory) {
|
|
4077
|
+
this.actions = this.actions.slice(-this.maxHistory);
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
setDeclaredState(state) {
|
|
4081
|
+
this.declaredState = state;
|
|
4082
|
+
}
|
|
4083
|
+
getActionHistory() {
|
|
4084
|
+
return this.actions.slice();
|
|
4085
|
+
}
|
|
4086
|
+
getSnapshotUndoContext() {
|
|
4087
|
+
const declared = this.declaredState;
|
|
4088
|
+
if (declared) {
|
|
4089
|
+
const summary2 = buildSummary({
|
|
4090
|
+
canUndo: declared.canUndo,
|
|
4091
|
+
canRedo: declared.canRedo,
|
|
4092
|
+
undoDescription: declared.undoDescription,
|
|
4093
|
+
redoDescription: declared.redoDescription
|
|
4094
|
+
});
|
|
4095
|
+
return {
|
|
4096
|
+
canUndo: declared.canUndo,
|
|
4097
|
+
canRedo: declared.canRedo,
|
|
4098
|
+
undoDescription: declared.undoDescription,
|
|
4099
|
+
redoDescription: declared.redoDescription,
|
|
4100
|
+
undoDepth: declared.undoDepth,
|
|
4101
|
+
redoDepth: declared.redoDepth,
|
|
4102
|
+
summary: summary2
|
|
4103
|
+
};
|
|
4104
|
+
}
|
|
4105
|
+
const canUndo = this.actions.some((a) => a.reversible !== false);
|
|
4106
|
+
const canRedo = false;
|
|
4107
|
+
const lastReversible = [...this.actions].reverse().find((a) => a.reversible !== false);
|
|
4108
|
+
const undoDescription = lastReversible?.description;
|
|
4109
|
+
const summary = buildSummary({ canUndo, canRedo, undoDescription });
|
|
4110
|
+
return {
|
|
4111
|
+
canUndo,
|
|
4112
|
+
canRedo,
|
|
4113
|
+
undoDescription,
|
|
4114
|
+
summary
|
|
4115
|
+
};
|
|
4116
|
+
}
|
|
4117
|
+
executeUndo() {
|
|
4118
|
+
return { ok: false, reason: "Not implemented in native" };
|
|
4119
|
+
}
|
|
4120
|
+
executeRedo() {
|
|
4121
|
+
return { ok: false, reason: "Not implemented in native" };
|
|
4122
|
+
}
|
|
4123
|
+
clear() {
|
|
4124
|
+
this.actions = [];
|
|
4125
|
+
this.declaredState = null;
|
|
4126
|
+
}
|
|
4127
|
+
};
|
|
3515
4128
|
var UIBridgeNativeContext = createContext(null);
|
|
3516
4129
|
function UIBridgeNativeProvider({
|
|
3517
4130
|
children,
|
|
@@ -3528,6 +4141,9 @@ function UIBridgeNativeProvider({
|
|
|
3528
4141
|
}) {
|
|
3529
4142
|
const registryRef = useRef(null);
|
|
3530
4143
|
const executorRef = useRef(null);
|
|
4144
|
+
const modalDetectorRef = useRef(null);
|
|
4145
|
+
const toastCaptureRef = useRef(null);
|
|
4146
|
+
const undoTrackerRef = useRef(null);
|
|
3531
4147
|
const [serverRunning, setServerRunning] = useState(false);
|
|
3532
4148
|
if (!registryRef.current) {
|
|
3533
4149
|
registryRef.current = new NativeUIBridgeRegistry({
|
|
@@ -3541,6 +4157,12 @@ function UIBridgeNativeProvider({
|
|
|
3541
4157
|
executorRef.current = createNativeActionExecutor(registry);
|
|
3542
4158
|
}
|
|
3543
4159
|
const executor = executorRef.current;
|
|
4160
|
+
if (!modalDetectorRef.current) modalDetectorRef.current = new ModalDetector();
|
|
4161
|
+
if (!toastCaptureRef.current) toastCaptureRef.current = new ToastCapture();
|
|
4162
|
+
if (!undoTrackerRef.current) undoTrackerRef.current = new UndoTracker();
|
|
4163
|
+
const modalDetector = modalDetectorRef.current;
|
|
4164
|
+
const toastCapture = toastCaptureRef.current;
|
|
4165
|
+
const undoTracker = undoTrackerRef.current;
|
|
3544
4166
|
const serverRef = useRef(null);
|
|
3545
4167
|
const eventBridgeRef = useRef(null);
|
|
3546
4168
|
const announcerRef = useRef(null);
|
|
@@ -3556,7 +4178,8 @@ function UIBridgeNativeProvider({
|
|
|
3556
4178
|
const server = createNativeServer(registry, executor, {
|
|
3557
4179
|
serverPort: config.serverPort || 8087,
|
|
3558
4180
|
cors: true,
|
|
3559
|
-
appInfo: config.appInfo
|
|
4181
|
+
appInfo: config.appInfo,
|
|
4182
|
+
testHooks: features.testHooks === true
|
|
3560
4183
|
});
|
|
3561
4184
|
server.setAdapter(serverAdapter);
|
|
3562
4185
|
if (navigationProvider) {
|
|
@@ -3610,7 +4233,9 @@ function UIBridgeNativeProvider({
|
|
|
3610
4233
|
}
|
|
3611
4234
|
}, [
|
|
3612
4235
|
features.server,
|
|
4236
|
+
features.testHooks,
|
|
3613
4237
|
config.serverPort,
|
|
4238
|
+
config.appInfo,
|
|
3614
4239
|
registry,
|
|
3615
4240
|
executor,
|
|
3616
4241
|
serverAdapter,
|
|
@@ -3644,7 +4269,8 @@ function UIBridgeNativeProvider({
|
|
|
3644
4269
|
const bareServer = createNativeServer(registry, executor, {
|
|
3645
4270
|
serverPort: config.serverPort || 8087,
|
|
3646
4271
|
cors: true,
|
|
3647
|
-
appInfo: config.appInfo
|
|
4272
|
+
appInfo: config.appInfo,
|
|
4273
|
+
testHooks: features.testHooks === true
|
|
3648
4274
|
});
|
|
3649
4275
|
if (navigationProvider) bareServer.setNavigationProvider(navigationProvider);
|
|
3650
4276
|
if (screenshotProvider) bareServer.setScreenshotProvider(screenshotProvider);
|
|
@@ -3729,6 +4355,22 @@ function UIBridgeNativeProvider({
|
|
|
3729
4355
|
lastRoute = current;
|
|
3730
4356
|
});
|
|
3731
4357
|
}, [routeProvider, registry]);
|
|
4358
|
+
useEffect(() => {
|
|
4359
|
+
registry.setEnrichers({ modalDetector, toastCapture, undoTracker });
|
|
4360
|
+
}, [registry, modalDetector, toastCapture, undoTracker]);
|
|
4361
|
+
useEffect(() => {
|
|
4362
|
+
const unsubscribe = executor.onActionExecuted((event) => {
|
|
4363
|
+
if (!event.success) return;
|
|
4364
|
+
undoTracker.recordAction({
|
|
4365
|
+
id: event.requestId ?? `action-${event.timestamp}`,
|
|
4366
|
+
type: event.action,
|
|
4367
|
+
targetId: event.elementId,
|
|
4368
|
+
timestamp: event.timestamp,
|
|
4369
|
+
reversible: true
|
|
4370
|
+
});
|
|
4371
|
+
});
|
|
4372
|
+
return unsubscribe;
|
|
4373
|
+
}, [executor, undoTracker]);
|
|
3732
4374
|
const on = useCallback(
|
|
3733
4375
|
(type, listener) => registry.on(type, listener),
|
|
3734
4376
|
[registry]
|
|
@@ -3743,6 +4385,9 @@ function UIBridgeNativeProvider({
|
|
|
3743
4385
|
config,
|
|
3744
4386
|
registry,
|
|
3745
4387
|
executor,
|
|
4388
|
+
modalDetector,
|
|
4389
|
+
toastCapture,
|
|
4390
|
+
undoTracker,
|
|
3746
4391
|
getElements,
|
|
3747
4392
|
getComponents,
|
|
3748
4393
|
createSnapshot,
|
|
@@ -3759,6 +4404,9 @@ function UIBridgeNativeProvider({
|
|
|
3759
4404
|
config,
|
|
3760
4405
|
registry,
|
|
3761
4406
|
executor,
|
|
4407
|
+
modalDetector,
|
|
4408
|
+
toastCapture,
|
|
4409
|
+
undoTracker,
|
|
3762
4410
|
getElements,
|
|
3763
4411
|
getComponents,
|
|
3764
4412
|
createSnapshot,
|
|
@@ -3853,7 +4501,7 @@ function useUIElement(options) {
|
|
|
3853
4501
|
registeredRef.current = false;
|
|
3854
4502
|
registeredIdRef.current = null;
|
|
3855
4503
|
setRegistered(false);
|
|
3856
|
-
}, [bridge,
|
|
4504
|
+
}, [bridge, id]);
|
|
3857
4505
|
const onLayout = useCallback(
|
|
3858
4506
|
(event) => {
|
|
3859
4507
|
const { x, y, width, height } = event.nativeEvent.layout;
|
|
@@ -4119,7 +4767,7 @@ function useUIComponent(options) {
|
|
|
4119
4767
|
const registeredComponent = useMemo(() => {
|
|
4120
4768
|
if (!bridge) return null;
|
|
4121
4769
|
return bridge.registry.getComponent(id) || null;
|
|
4122
|
-
}, [bridge, id
|
|
4770
|
+
}, [bridge, id]);
|
|
4123
4771
|
return {
|
|
4124
4772
|
registered: registeredRef.current,
|
|
4125
4773
|
executeAction,
|
|
@@ -4263,11 +4911,12 @@ function useAutoRegister(options = {}) {
|
|
|
4263
4911
|
[bridge, onUnregister]
|
|
4264
4912
|
);
|
|
4265
4913
|
useEffect(() => {
|
|
4914
|
+
const registeredIds = registeredIdsRef.current;
|
|
4266
4915
|
return () => {
|
|
4267
|
-
|
|
4916
|
+
registeredIds.forEach((id) => {
|
|
4268
4917
|
bridge?.registry.unregisterElement(id);
|
|
4269
4918
|
});
|
|
4270
|
-
|
|
4919
|
+
registeredIds.clear();
|
|
4271
4920
|
};
|
|
4272
4921
|
}, [bridge]);
|
|
4273
4922
|
return {
|
|
@@ -4276,7 +4925,38 @@ function useAutoRegister(options = {}) {
|
|
|
4276
4925
|
isEnabled: enabled && !!bridge?.registry
|
|
4277
4926
|
};
|
|
4278
4927
|
}
|
|
4928
|
+
function useUIBridgeModal(opts) {
|
|
4929
|
+
const { modalDetector } = useUIBridgeNative();
|
|
4930
|
+
const { id, title, type, blocking, dismissible } = opts;
|
|
4931
|
+
useEffect(() => {
|
|
4932
|
+
modalDetector.pushModal({ id, title, type, blocking, dismissible });
|
|
4933
|
+
return () => {
|
|
4934
|
+
modalDetector.dismissModal(id);
|
|
4935
|
+
};
|
|
4936
|
+
}, [modalDetector, id, title, type, blocking, dismissible]);
|
|
4937
|
+
}
|
|
4938
|
+
function useUIBridgeToast(opts) {
|
|
4939
|
+
const { toastCapture } = useUIBridgeNative();
|
|
4940
|
+
const { id, message, level } = opts;
|
|
4941
|
+
useEffect(() => {
|
|
4942
|
+
toastCapture.recordToast({ id, message, level });
|
|
4943
|
+
return () => {
|
|
4944
|
+
toastCapture.dismissToast(id);
|
|
4945
|
+
};
|
|
4946
|
+
}, [toastCapture, id, message, level]);
|
|
4947
|
+
}
|
|
4948
|
+
function useToastRecorder() {
|
|
4949
|
+
const { toastCapture } = useUIBridgeNative();
|
|
4950
|
+
const recordToast = useCallback(
|
|
4951
|
+
(toast) => {
|
|
4952
|
+
toastCapture.recordToast(toast);
|
|
4953
|
+
},
|
|
4954
|
+
[toastCapture]
|
|
4955
|
+
);
|
|
4956
|
+
const dismissToast = useCallback((id) => toastCapture.dismissToast(id), [toastCapture]);
|
|
4957
|
+
return { recordToast, dismissToast };
|
|
4958
|
+
}
|
|
4279
4959
|
|
|
4280
|
-
export { UIBridgeNativeProvider, useAutoRegister, useUIBridge, useUIBridgeNative, useUIBridgeNativeOptional, useUIBridgeNativeRequired, useUIBridgeRequired, useUIComponent, useUIComponentAction, useUIElement, useUIElementWithProps };
|
|
4960
|
+
export { UIBridgeNativeProvider, useAutoRegister, useToastRecorder, useUIBridge, useUIBridgeModal, useUIBridgeNative, useUIBridgeNativeOptional, useUIBridgeNativeRequired, useUIBridgeRequired, useUIBridgeToast, useUIComponent, useUIComponentAction, useUIElement, useUIElementWithProps };
|
|
4281
4961
|
//# sourceMappingURL=index.mjs.map
|
|
4282
4962
|
//# sourceMappingURL=index.mjs.map
|