@getuserfeedback/react-native 5.14.2 → 5.15.3
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/README.md +16 -0
- package/dist/native-color-scheme.d.ts +31 -0
- package/dist/native-color-scheme.js +44 -0
- package/dist/native-core-command-session.js +9 -1
- package/dist/native-flow-content.d.ts +8 -0
- package/dist/native-flow-content.js +188 -0
- package/dist/native-link-router-rpc.js +5 -1
- package/dist/native-provider-client-controller.d.ts +3 -0
- package/dist/native-provider-client-controller.js +20 -1
- package/dist/native-provider.js +47 -16
- package/dist/native-runtime-root.js +3 -1
- package/dist/native-view-host-projection.d.ts +2 -1
- package/dist/native-view-host-projection.js +9 -1
- package/dist/native-view-presentation-surface.js +29 -8
- package/dist/native-view-record-controller.d.ts +28 -0
- package/dist/native-view-record-controller.js +228 -41
- package/dist/provider-command-helpers.js +13 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -45,6 +45,22 @@ export function App() {
|
|
|
45
45
|
|
|
46
46
|
That is enough to receive flows published for automatic Widget delivery.
|
|
47
47
|
|
|
48
|
+
## Native color scheme
|
|
49
|
+
|
|
50
|
+
For flows using the native renderer, set `clientOptions.colorScheme` to
|
|
51
|
+
`"light"`, `"dark"`, or `"system"`. Omitted values and browser auto-detect
|
|
52
|
+
objects follow the device appearance on native (light when unavailable).
|
|
53
|
+
|
|
54
|
+
Scheme-only prop changes and `client.configure({ colorScheme: "dark" })`
|
|
55
|
+
repaint the SDK's default native palette without restarting the flow or
|
|
56
|
+
clearing its draft. An unchanged prop does not override a later configure call.
|
|
57
|
+
The selection belongs to that client: runtime recreation retains it, while a
|
|
58
|
+
replacement client (for example, a new API key) starts from its initial prop.
|
|
59
|
+
Native repaint does not wait for Core: configure still forwards to Core and
|
|
60
|
+
retains its existing promise/error behavior, but remote failure does not roll
|
|
61
|
+
back native appearance. WebView flows keep their existing scheme forwarding
|
|
62
|
+
and browser auto-detection behavior. This does not add a style override API.
|
|
63
|
+
|
|
48
64
|
- [React Native SDK guide](https://getuserfeedback.com/docs/get-started/react-native-sdk)
|
|
49
65
|
- [React Native SDK reference](https://getuserfeedback.com/docs/reference/react-native-sdk-reference)
|
|
50
66
|
- [Package versions](https://www.npmjs.com/package/@getuserfeedback/react-native?activeTab=versions)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const NativeColorSchemeContext: import("react").Context<import("@getuserfeedback/protocol/host").ColorSchemeConfig | undefined>;
|
|
2
|
+
export declare function useNativeColorScheme(): {
|
|
3
|
+
scheme: "dark" | "light";
|
|
4
|
+
palette: {
|
|
5
|
+
background: string;
|
|
6
|
+
text: string;
|
|
7
|
+
muted: string;
|
|
8
|
+
input: string;
|
|
9
|
+
border: string;
|
|
10
|
+
placeholder: string;
|
|
11
|
+
button: string;
|
|
12
|
+
buttonText: string;
|
|
13
|
+
accent: string;
|
|
14
|
+
disabled: string;
|
|
15
|
+
disabledText: string;
|
|
16
|
+
error: string;
|
|
17
|
+
} | {
|
|
18
|
+
background: string;
|
|
19
|
+
text: string;
|
|
20
|
+
muted: string;
|
|
21
|
+
input: string;
|
|
22
|
+
border: string;
|
|
23
|
+
placeholder: string;
|
|
24
|
+
button: string;
|
|
25
|
+
buttonText: string;
|
|
26
|
+
accent: string;
|
|
27
|
+
disabled: string;
|
|
28
|
+
disabledText: string;
|
|
29
|
+
error: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
// Host-local presentation only; never the acknowledged Core configuration.
|
|
3
|
+
export const NativeColorSchemeContext = createContext(undefined);
|
|
4
|
+
const palettes = {
|
|
5
|
+
light: {
|
|
6
|
+
background: "#ffffff",
|
|
7
|
+
text: "#17202a",
|
|
8
|
+
muted: "#475569",
|
|
9
|
+
input: "#ffffff",
|
|
10
|
+
border: "#64748b",
|
|
11
|
+
placeholder: "#64748b",
|
|
12
|
+
button: "#e7eefb",
|
|
13
|
+
buttonText: "#17202a",
|
|
14
|
+
accent: "#2563eb",
|
|
15
|
+
disabled: "#e5e7eb",
|
|
16
|
+
disabledText: "#4b5563",
|
|
17
|
+
error: "#aa0000",
|
|
18
|
+
},
|
|
19
|
+
dark: {
|
|
20
|
+
background: "#111827",
|
|
21
|
+
text: "#f3f4f6",
|
|
22
|
+
muted: "#cbd5e1",
|
|
23
|
+
input: "#1f2937",
|
|
24
|
+
border: "#94a3b8",
|
|
25
|
+
placeholder: "#94a3b8",
|
|
26
|
+
button: "#1e3a5f",
|
|
27
|
+
buttonText: "#f8fafc",
|
|
28
|
+
accent: "#93c5fd",
|
|
29
|
+
disabled: "#374151",
|
|
30
|
+
disabledText: "#cbd5e1",
|
|
31
|
+
error: "#fca5a5",
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
export function useNativeColorScheme() {
|
|
35
|
+
const requested = useContext(NativeColorSchemeContext);
|
|
36
|
+
const device = require("react-native").useColorScheme();
|
|
37
|
+
// Browser auto-detection has no DOM on native; use the device preference.
|
|
38
|
+
const scheme = requested === "light" || requested === "dark"
|
|
39
|
+
? requested
|
|
40
|
+
: device === "dark"
|
|
41
|
+
? "dark"
|
|
42
|
+
: "light";
|
|
43
|
+
return { scheme, palette: palettes[scheme] };
|
|
44
|
+
}
|
|
@@ -12,7 +12,15 @@ class NativeCoreCommandSessionClosedError extends Error {
|
|
|
12
12
|
function toCommandSettledDetail(settlement) {
|
|
13
13
|
var _a, _b;
|
|
14
14
|
if (settlement.ok) {
|
|
15
|
-
|
|
15
|
+
const detail = {
|
|
16
|
+
instanceId: (_a = settlement.instanceId) !== null && _a !== void 0 ? _a : null,
|
|
17
|
+
kind: settlement.kind,
|
|
18
|
+
ok: true,
|
|
19
|
+
requestId: settlement.requestId,
|
|
20
|
+
};
|
|
21
|
+
if (settlement.result !== undefined)
|
|
22
|
+
detail.result = settlement.result;
|
|
23
|
+
return detail;
|
|
16
24
|
}
|
|
17
25
|
return {
|
|
18
26
|
error: settlement.error,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { NativeViewPresentationMode } from "./native-view-presentation-surface.js";
|
|
2
|
+
import type { NativeViewRecord, NativeViewRecordController } from "./native-view-record-controller.js";
|
|
3
|
+
/** Thin native editor: drafts only. Core owns validation, navigation and submission. */
|
|
4
|
+
export declare function NativeFlowContent({ controller, presentationMode, record, }: {
|
|
5
|
+
controller: NativeViewRecordController;
|
|
6
|
+
presentationMode?: NativeViewPresentationMode;
|
|
7
|
+
record: NativeViewRecord;
|
|
8
|
+
}): import("react").ReactElement<Record<string, unknown>, string | import("react").JSXElementConstructor<any>>;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { createElement, useLayoutEffect, useRef, useState, } from "react";
|
|
2
|
+
import { useNativeColorScheme } from "./native-color-scheme.js";
|
|
3
|
+
/** Thin native editor: drafts only. Core owns validation, navigation and submission. */
|
|
4
|
+
export function NativeFlowContent({ controller, presentationMode = "overlay", record, }) {
|
|
5
|
+
const flow = record.nativeFlow;
|
|
6
|
+
if (!flow)
|
|
7
|
+
throw new Error("Native content requires a native allocation");
|
|
8
|
+
const { projection, interaction } = flow;
|
|
9
|
+
const { View, Text, TextInput, Pressable, ScrollView, useWindowDimensions, } = require("react-native");
|
|
10
|
+
const { height: windowHeight } = useWindowDimensions();
|
|
11
|
+
const { scheme, palette } = useNativeColorScheme();
|
|
12
|
+
const [draft, setDraft] = useState(projection.answers);
|
|
13
|
+
const rawDraft = useRef(draft);
|
|
14
|
+
const scroll = useRef(null);
|
|
15
|
+
const editorPage = useRef({
|
|
16
|
+
allocationId: record.allocationId,
|
|
17
|
+
nodeId: projection.nodeId,
|
|
18
|
+
});
|
|
19
|
+
const binding = useRef(null);
|
|
20
|
+
useLayoutEffect(() => {
|
|
21
|
+
const mounted = controller.attachNativeContent({
|
|
22
|
+
viewId: record.viewId,
|
|
23
|
+
allocationId: record.allocationId,
|
|
24
|
+
});
|
|
25
|
+
binding.current = mounted;
|
|
26
|
+
return () => {
|
|
27
|
+
binding.current = null;
|
|
28
|
+
mounted === null || mounted === void 0 ? void 0 : mounted.dispose();
|
|
29
|
+
};
|
|
30
|
+
}, [controller, record.viewId, record.allocationId]);
|
|
31
|
+
useLayoutEffect(() => {
|
|
32
|
+
var _a;
|
|
33
|
+
if (editorPage.current.allocationId === record.allocationId &&
|
|
34
|
+
editorPage.current.nodeId === projection.nodeId)
|
|
35
|
+
return;
|
|
36
|
+
editorPage.current = {
|
|
37
|
+
allocationId: record.allocationId,
|
|
38
|
+
nodeId: projection.nodeId,
|
|
39
|
+
};
|
|
40
|
+
rawDraft.current = projection.answers;
|
|
41
|
+
setDraft(projection.answers);
|
|
42
|
+
(_a = scroll.current) === null || _a === void 0 ? void 0 : _a.scrollTo({ x: 0, y: 0, animated: false });
|
|
43
|
+
}, [projection, record.allocationId]);
|
|
44
|
+
const update = (key, value) => {
|
|
45
|
+
const nextDraft = Object.assign(Object.assign({}, rawDraft.current), { [key]: value });
|
|
46
|
+
if (!controller.interactNativeFlow({
|
|
47
|
+
viewId: record.viewId,
|
|
48
|
+
allocationId: record.allocationId,
|
|
49
|
+
projection,
|
|
50
|
+
intent: "draft",
|
|
51
|
+
draft: nextDraft,
|
|
52
|
+
}))
|
|
53
|
+
return;
|
|
54
|
+
rawDraft.current = nextDraft;
|
|
55
|
+
setDraft(rawDraft.current);
|
|
56
|
+
};
|
|
57
|
+
const interact = (intent) => controller.interactNativeFlow({
|
|
58
|
+
viewId: record.viewId,
|
|
59
|
+
allocationId: record.allocationId,
|
|
60
|
+
projection,
|
|
61
|
+
intent,
|
|
62
|
+
draft: Object.assign({}, rawDraft.current),
|
|
63
|
+
});
|
|
64
|
+
const disabled = interaction === "action" || !record.isActive;
|
|
65
|
+
const button = (label, onPress, buttonDisabled = interaction !== "idle" || !record.isActive) => createElement(Pressable, {
|
|
66
|
+
key: label,
|
|
67
|
+
accessibilityRole: "button",
|
|
68
|
+
accessibilityLabel: label,
|
|
69
|
+
disabled: buttonDisabled,
|
|
70
|
+
onPress,
|
|
71
|
+
style: {
|
|
72
|
+
padding: 12,
|
|
73
|
+
backgroundColor: buttonDisabled ? palette.disabled : palette.button,
|
|
74
|
+
borderRadius: 6,
|
|
75
|
+
marginTop: 8,
|
|
76
|
+
},
|
|
77
|
+
}, createElement(Text, {
|
|
78
|
+
style: {
|
|
79
|
+
color: buttonDisabled ? palette.disabledText : palette.buttonText,
|
|
80
|
+
},
|
|
81
|
+
}, label));
|
|
82
|
+
return createElement(ScrollView, {
|
|
83
|
+
ref: scroll,
|
|
84
|
+
style: Object.assign(Object.assign({}, (presentationMode === "content"
|
|
85
|
+
? { flexGrow: 0, flexShrink: 1, maxHeight: windowHeight }
|
|
86
|
+
: { flex: 1, maxHeight: "100%" })), { width: "100%", backgroundColor: palette.background }),
|
|
87
|
+
keyboardShouldPersistTaps: "handled",
|
|
88
|
+
nestedScrollEnabled: presentationMode === "content",
|
|
89
|
+
// Report the actual viewport, never the window ceiling or inner content.
|
|
90
|
+
// Content mode can measure before its auto-height parent has any height.
|
|
91
|
+
onLayout: (event) => { var _a; return (_a = binding.current) === null || _a === void 0 ? void 0 : _a.onLayout(event.nativeEvent.layout); },
|
|
92
|
+
}, createElement(View, {
|
|
93
|
+
testID: "native-flow-renderer",
|
|
94
|
+
style: { padding: 24, gap: 12 },
|
|
95
|
+
}, projection.mode === "simulated"
|
|
96
|
+
? createElement(Text, {
|
|
97
|
+
testID: "native-flow-simulated",
|
|
98
|
+
style: { color: palette.muted },
|
|
99
|
+
}, "SIMULATED — no response is saved")
|
|
100
|
+
: null, ...projection.blocks.map((block) => {
|
|
101
|
+
var _a;
|
|
102
|
+
if (block.kind === "statement")
|
|
103
|
+
return createElement(View, { key: block.key }, ...block.paragraphs.map((text, index) => createElement(Text, { key: index, style: { color: palette.text } }, text)));
|
|
104
|
+
const value = draft[block.key];
|
|
105
|
+
const requiredLabel = block.required ? ", required" : "";
|
|
106
|
+
const selected = Array.isArray(value)
|
|
107
|
+
? ((_a = value[0]) !== null && _a !== void 0 ? _a : "")
|
|
108
|
+
: (value !== null && value !== void 0 ? value : "");
|
|
109
|
+
return createElement(View, { key: block.key, style: { gap: 6 } }, createElement(Text, { style: { color: palette.text } }, block.label + (block.required ? " *" : "")), block.description
|
|
110
|
+
? createElement(Text, { style: { color: palette.muted } }, block.description)
|
|
111
|
+
: null, block.kind === "text"
|
|
112
|
+
? createElement(TextInput, {
|
|
113
|
+
accessibilityLabel: block.label + requiredLabel,
|
|
114
|
+
accessibilityHint: block.description,
|
|
115
|
+
value: selected,
|
|
116
|
+
placeholder: block.placeholder,
|
|
117
|
+
placeholderTextColor: palette.placeholder,
|
|
118
|
+
keyboardAppearance: scheme,
|
|
119
|
+
selectionColor: palette.accent,
|
|
120
|
+
cursorColor: palette.accent,
|
|
121
|
+
editable: !disabled,
|
|
122
|
+
maxLength: 4000,
|
|
123
|
+
onChangeText: (text) => update(block.key, text),
|
|
124
|
+
style: {
|
|
125
|
+
color: disabled ? palette.disabledText : palette.text,
|
|
126
|
+
backgroundColor: disabled ? palette.disabled : palette.input,
|
|
127
|
+
borderWidth: 1,
|
|
128
|
+
borderColor: palette.border,
|
|
129
|
+
borderRadius: 4,
|
|
130
|
+
padding: 10,
|
|
131
|
+
},
|
|
132
|
+
})
|
|
133
|
+
: createElement(View, {
|
|
134
|
+
accessibilityRole: "radiogroup",
|
|
135
|
+
accessibilityLabel: block.label + requiredLabel,
|
|
136
|
+
accessibilityHint: block.description,
|
|
137
|
+
accessible: false,
|
|
138
|
+
}, ...block.options.map((option) => createElement(Pressable, {
|
|
139
|
+
key: option.value,
|
|
140
|
+
accessibilityRole: "radio",
|
|
141
|
+
accessibilityLabel: `${block.label}: ${option.label}${requiredLabel}`,
|
|
142
|
+
accessibilityHint: block.description,
|
|
143
|
+
accessible: true,
|
|
144
|
+
accessibilityState: {
|
|
145
|
+
checked: selected === option.value,
|
|
146
|
+
disabled,
|
|
147
|
+
},
|
|
148
|
+
disabled,
|
|
149
|
+
onPress: () => update(block.key, option.value),
|
|
150
|
+
style: { padding: 10 },
|
|
151
|
+
}, createElement(Text, {
|
|
152
|
+
style: {
|
|
153
|
+
color: disabled ? palette.disabledText : palette.text,
|
|
154
|
+
},
|
|
155
|
+
}, `${selected === option.value ? "●" : "○"} ${option.label}`)))), projection.errors[block.key]
|
|
156
|
+
? createElement(Text, { accessibilityRole: "alert", style: { color: palette.error } }, projection.errors[block.key])
|
|
157
|
+
: null);
|
|
158
|
+
}), ...projection.formErrors.map((text, index) => createElement(Text, {
|
|
159
|
+
key: `error-${index}`,
|
|
160
|
+
accessibilityRole: "alert",
|
|
161
|
+
style: { color: palette.error },
|
|
162
|
+
}, text)), ...projection.terminal.map((text, index) => createElement(Text, { key: `terminal-${index}`, style: { color: palette.text } }, text)), projection.status === "succeeded" &&
|
|
163
|
+
(projection.mode === "simulated" || projection.terminal.length === 0)
|
|
164
|
+
? createElement(Text, { testID: "native-flow-complete", style: { color: palette.text } }, projection.mode === "simulated"
|
|
165
|
+
? "SIMULATED completion — nothing saved"
|
|
166
|
+
: "Thank you")
|
|
167
|
+
: null, projection.canGoBack ? button("Back", () => interact("back")) : null, interaction === "draft-failed"
|
|
168
|
+
? button("Retry refresh", () => interact("draft"), !record.isActive)
|
|
169
|
+
: null, projection.status !== "succeeded"
|
|
170
|
+
? button(interaction === "action"
|
|
171
|
+
? "Working…"
|
|
172
|
+
: projection.status === "ready-to-submit"
|
|
173
|
+
? "Submit"
|
|
174
|
+
: "Continue", () => interact(projection.status === "ready-to-submit" ? "submit" : "continue"))
|
|
175
|
+
: null, projection.canClose
|
|
176
|
+
? createElement(Pressable, {
|
|
177
|
+
accessibilityRole: "button",
|
|
178
|
+
accessibilityLabel: "Close",
|
|
179
|
+
disabled: !record.isActive,
|
|
180
|
+
onPress: () => controller.closeNativeFlow(record),
|
|
181
|
+
style: { padding: 12 },
|
|
182
|
+
}, createElement(Text, {
|
|
183
|
+
style: {
|
|
184
|
+
color: !record.isActive ? palette.disabledText : palette.text,
|
|
185
|
+
},
|
|
186
|
+
}, "Close"))
|
|
187
|
+
: null));
|
|
188
|
+
}
|
|
@@ -138,7 +138,11 @@ export const createNativeLinkRouterRpcHandler = (input) => {
|
|
|
138
138
|
}
|
|
139
139
|
let handled = false;
|
|
140
140
|
try {
|
|
141
|
-
|
|
141
|
+
const linkRequest = { url: params.url };
|
|
142
|
+
if (params.target !== undefined) {
|
|
143
|
+
linkRequest.target = params.target;
|
|
144
|
+
}
|
|
145
|
+
invokeLinkRouter(router, linkRequest);
|
|
142
146
|
handled = true;
|
|
143
147
|
}
|
|
144
148
|
catch (error) {
|
|
@@ -4,10 +4,13 @@ import { type OpenRequestDispatcher } from "@getuserfeedback/sdk/internal/open-r
|
|
|
4
4
|
export declare function createNativeProviderClientController(input: {
|
|
5
5
|
instanceId: string;
|
|
6
6
|
onError?: (error: Error) => void;
|
|
7
|
+
initialColorScheme?: ConfigureOptions["colorScheme"];
|
|
7
8
|
}): {
|
|
8
9
|
client: import("./client.js").Client;
|
|
9
10
|
directSessionController: import("./native-direct-command-session-controller.js").NativeDirectCommandSessionController;
|
|
10
11
|
flowRegistry: import("./native-provider-flow-registry.js").NativeProviderFlowRegistry;
|
|
12
|
+
getNativeColorScheme: () => import("@getuserfeedback/protocol/host").ColorSchemeConfig | undefined;
|
|
13
|
+
subscribeNativeColorScheme: (listener: () => void) => () => void;
|
|
11
14
|
getRuntimeConfiguration: () => ConfigureOptions;
|
|
12
15
|
observeOpenRequest: (payload: Parameters<OpenRequestDispatcher["dispatch"]>[0]) => ReturnType<OpenRequestDispatcher["dispatch"]>;
|
|
13
16
|
dispose: () => void;
|
|
@@ -4,6 +4,9 @@ import { createNativeProviderClient, parseRequiredString, } from "./native-provi
|
|
|
4
4
|
import { createNativeProviderCommandController } from "./native-provider-command-controller.js";
|
|
5
5
|
import { createNativeProviderFlowRegistry } from "./native-provider-flow-registry.js";
|
|
6
6
|
export function createNativeProviderClientController(input) {
|
|
7
|
+
// Presentation belongs to this client, independently of Core acknowledgement.
|
|
8
|
+
let nativeColorScheme = input.initialColorScheme;
|
|
9
|
+
const colorSchemeListeners = new Set();
|
|
7
10
|
let runtimeConfiguration = {};
|
|
8
11
|
const updateRuntimeConfiguration = (updates) => {
|
|
9
12
|
runtimeConfiguration = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, runtimeConfiguration), (updates.auth === undefined ? {} : { auth: updates.auth })), (updates.capabilities === undefined
|
|
@@ -44,7 +47,15 @@ export function createNativeProviderClientController(input) {
|
|
|
44
47
|
catch (error) {
|
|
45
48
|
return commands.reject(error);
|
|
46
49
|
}
|
|
47
|
-
const settlement = await commands.runOperation((dispatch) =>
|
|
50
|
+
const settlement = await commands.runOperation((dispatch) => {
|
|
51
|
+
if (canonicalCommand.kind === "configure" &&
|
|
52
|
+
canonicalCommand.opts.colorScheme !== undefined) {
|
|
53
|
+
nativeColorScheme = canonicalCommand.opts.colorScheme;
|
|
54
|
+
for (const listener of colorSchemeListeners)
|
|
55
|
+
listener();
|
|
56
|
+
}
|
|
57
|
+
return dispatch(canonicalCommand);
|
|
58
|
+
}, options);
|
|
48
59
|
if (canonicalCommand.kind === "configure" && settlement !== undefined) {
|
|
49
60
|
updateRuntimeConfiguration(canonicalCommand.opts);
|
|
50
61
|
}
|
|
@@ -57,9 +68,17 @@ export function createNativeProviderClientController(input) {
|
|
|
57
68
|
client,
|
|
58
69
|
directSessionController: session,
|
|
59
70
|
flowRegistry: flows,
|
|
71
|
+
getNativeColorScheme: () => nativeColorScheme,
|
|
72
|
+
subscribeNativeColorScheme: (listener) => {
|
|
73
|
+
colorSchemeListeners.add(listener);
|
|
74
|
+
return () => {
|
|
75
|
+
colorSchemeListeners.delete(listener);
|
|
76
|
+
};
|
|
77
|
+
},
|
|
60
78
|
getRuntimeConfiguration: () => (Object.assign({}, runtimeConfiguration)),
|
|
61
79
|
observeOpenRequest,
|
|
62
80
|
dispose: () => {
|
|
81
|
+
colorSchemeListeners.clear();
|
|
63
82
|
flows.reset();
|
|
64
83
|
commands.dispose();
|
|
65
84
|
},
|
package/dist/native-provider.js
CHANGED
|
@@ -9,12 +9,13 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
9
9
|
}
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
|
-
import { parseInitOptions } from "@getuserfeedback/protocol";
|
|
12
|
+
import { DEFAULT_AUTO_DETECT_COLOR_SCHEME_ATTRS, parseInitOptions, } from "@getuserfeedback/protocol";
|
|
13
13
|
import { createUniqueId } from "@getuserfeedback/protocol/host";
|
|
14
14
|
import { createActionRegistry, } from "@getuserfeedback/sdk/internal/action-registry";
|
|
15
15
|
import { resolveLinkRouter } from "@getuserfeedback/sdk/internal/link-router";
|
|
16
|
-
import { createContext, createElement, Fragment, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
16
|
+
import { createContext, createElement, Fragment, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore, } from "react";
|
|
17
17
|
import { NativeStandardFlowContainer } from "./flow-container.js";
|
|
18
|
+
import { NativeColorSchemeContext } from "./native-color-scheme.js";
|
|
18
19
|
import { NativeFlowContainerContext, } from "./native-flow-container-context.js";
|
|
19
20
|
import { createNativeProviderClientController } from "./native-provider-client-controller.js";
|
|
20
21
|
import { REACT_NATIVE_PINNED_CORE_URL } from "./native-runtime-endpoints.js";
|
|
@@ -87,15 +88,17 @@ const toInitFlags = (flags) => {
|
|
|
87
88
|
const parseNativeInitOptions = (_a, hostActions) => {
|
|
88
89
|
var { actions: _actions, capabilities, flags, links: _links } = _a, clientOptions = __rest(_a, ["actions", "capabilities", "flags", "links"]);
|
|
89
90
|
const normalizedFlags = toInitFlags(flags);
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
: {}))
|
|
91
|
+
const initInput = Object.assign({}, clientOptions);
|
|
92
|
+
if (normalizedFlags !== undefined) {
|
|
93
|
+
initInput.flags = normalizedFlags;
|
|
94
|
+
}
|
|
95
|
+
if (hostActions !== undefined && hostActions.length > 0) {
|
|
96
|
+
initInput.hostActions = hostActions;
|
|
97
|
+
}
|
|
98
|
+
if (capabilities !== undefined) {
|
|
99
|
+
initInput.hostCapabilities = capabilities.map((capability) => typeof capability === "string" ? { key: capability } : Object.assign({}, capability));
|
|
100
|
+
}
|
|
101
|
+
const initOptions = parseInitOptions(initInput);
|
|
99
102
|
const hostCapabilities = initOptions.hostCapabilities;
|
|
100
103
|
const normalizedHostCapabilities = hostCapabilities === null || hostCapabilities === void 0 ? void 0 : hostCapabilities.map((capability) => ({
|
|
101
104
|
capability,
|
|
@@ -121,7 +124,9 @@ export function GetUserFeedbackProvider({ children, clientOptions, instanceId: i
|
|
|
121
124
|
const actionRegistry = useNativeActionRegistry(clientOptions.actions);
|
|
122
125
|
const linkRouter = useNativeLinkRouter(clientOptions.links);
|
|
123
126
|
const normalizedClientOptions = parseNativeInitOptions(clientOptions, actionRegistry.definitions);
|
|
124
|
-
const
|
|
127
|
+
const { colorScheme } = normalizedClientOptions, runtimeIdentityOptions = __rest(normalizedClientOptions, ["colorScheme"]);
|
|
128
|
+
const clientOptionsKey = toClientOptionsKey(runtimeIdentityOptions);
|
|
129
|
+
const colorSchemePropKey = toClientOptionsKey(colorScheme);
|
|
125
130
|
const coreUrl = (_b = (_a = normalizedClientOptions.runtimeEndpoints) === null || _a === void 0 ? void 0 : _a.coreUrl) !== null && _b !== void 0 ? _b : REACT_NATIVE_PINNED_CORE_URL;
|
|
126
131
|
const onErrorRef = useRef(onError);
|
|
127
132
|
onErrorRef.current = onError;
|
|
@@ -138,14 +143,40 @@ export function GetUserFeedbackProvider({ children, clientOptions, instanceId: i
|
|
|
138
143
|
apiKey: normalizedClientOptions.apiKey,
|
|
139
144
|
hostCapabilities: normalizedClientOptions.hostCapabilities,
|
|
140
145
|
});
|
|
141
|
-
const
|
|
146
|
+
const createControllerOwner = () => ({
|
|
142
147
|
identityKey: clientIdentityKey,
|
|
148
|
+
colorSchemePropKey,
|
|
143
149
|
controller: createNativeProviderClientController({
|
|
144
150
|
instanceId,
|
|
145
151
|
onError: reportError,
|
|
152
|
+
initialColorScheme: colorScheme,
|
|
146
153
|
}),
|
|
147
|
-
})
|
|
154
|
+
});
|
|
155
|
+
let [controllerOwner, setControllerOwner] = useState(createControllerOwner);
|
|
156
|
+
// Initialize the whole owner together, before committing a replacement client.
|
|
157
|
+
// Scheme-only prop changes update that owner without replacing it.
|
|
158
|
+
if (controllerOwner.identityKey !== clientIdentityKey) {
|
|
159
|
+
controllerOwner = createControllerOwner();
|
|
160
|
+
setControllerOwner(controllerOwner);
|
|
161
|
+
}
|
|
148
162
|
const controller = controllerOwner.controller;
|
|
163
|
+
const hostColorScheme = useSyncExternalStore(controller.subscribeNativeColorScheme, controller.getNativeColorScheme, controller.getNativeColorScheme);
|
|
164
|
+
useLayoutEffect(() => {
|
|
165
|
+
if (controllerOwner.colorSchemePropKey === colorSchemePropKey)
|
|
166
|
+
return;
|
|
167
|
+
controllerOwner.colorSchemePropKey = colorSchemePropKey;
|
|
168
|
+
// Prop changes share configure's forwarding path, not runtime identity.
|
|
169
|
+
// Unchanged props must not undo a later imperative configure.
|
|
170
|
+
void controller.client
|
|
171
|
+
.configure({
|
|
172
|
+
colorScheme: colorScheme !== null && colorScheme !== void 0 ? colorScheme : {
|
|
173
|
+
autoDetectColorScheme: [...DEFAULT_AUTO_DETECT_COLOR_SCHEME_ATTRS],
|
|
174
|
+
},
|
|
175
|
+
})
|
|
176
|
+
.catch(() => {
|
|
177
|
+
// The controller reports remote failures; local appearance is retained.
|
|
178
|
+
});
|
|
179
|
+
}, [colorScheme, colorSchemePropKey, controller, controllerOwner]);
|
|
149
180
|
const attachViewProjection = useCallback((nextProjection) => {
|
|
150
181
|
setProjection(nextProjection);
|
|
151
182
|
return () => setProjection((current) => current === nextProjection ? null : current);
|
|
@@ -197,7 +228,7 @@ export function GetUserFeedbackProvider({ children, clientOptions, instanceId: i
|
|
|
197
228
|
}, [controller]);
|
|
198
229
|
const getRuntimeConfiguration = useCallback(() => controller.getRuntimeConfiguration(), [controller]);
|
|
199
230
|
const handleHandleInvalidated = useCallback((message) => controller.onHandleInvalidated(message), [controller]);
|
|
200
|
-
return createElement(GetUserFeedbackContext.Provider, { value: controller.client }, createElement(NativeFlowContainerContext.Provider, { value: flowContainerContext }, createElement(Fragment, null, children, createElement(NativeStandardFlowContainer), createElement(NativeProviderRuntime, {
|
|
231
|
+
return createElement(NativeColorSchemeContext.Provider, { value: hostColorScheme }, createElement(GetUserFeedbackContext.Provider, { value: controller.client }, createElement(NativeFlowContainerContext.Provider, { value: flowContainerContext }, createElement(Fragment, null, children, createElement(NativeStandardFlowContainer), createElement(NativeProviderRuntime, {
|
|
201
232
|
attachViewProjection,
|
|
202
233
|
coreUrl,
|
|
203
234
|
coreWebViewComponent: webViewComponent,
|
|
@@ -213,7 +244,7 @@ export function GetUserFeedbackProvider({ children, clientOptions, instanceId: i
|
|
|
213
244
|
onHandleInvalidated: handleHandleInvalidated,
|
|
214
245
|
observeOpenRequest: controller.observeOpenRequest,
|
|
215
246
|
viewWebViewComponent: webViewComponent,
|
|
216
|
-
}))));
|
|
247
|
+
})))));
|
|
217
248
|
}
|
|
218
249
|
/**
|
|
219
250
|
* Returns the React Native SDK client from the nearest provider.
|
|
@@ -40,7 +40,9 @@ export function NativeRuntimeRoot({ attachViewProjection, coreUrl, coreWebViewCo
|
|
|
40
40
|
const onStartupTerminalErrorRef = useRef(onStartupTerminalError);
|
|
41
41
|
onStartupTerminalErrorRef.current = onStartupTerminalError;
|
|
42
42
|
const createStartupCommandRef = useRef(createStartupCommand);
|
|
43
|
-
|
|
43
|
+
useLayoutEffect(() => {
|
|
44
|
+
createStartupCommandRef.current = createStartupCommand;
|
|
45
|
+
}, [createStartupCommand]);
|
|
44
46
|
const hostActionsRef = useRef(hostActions);
|
|
45
47
|
hostActionsRef.current = hostActions;
|
|
46
48
|
const linkRouterRef = useRef(linkRouter);
|
|
@@ -11,9 +11,10 @@ export type NativeViewHostProjectionProps = {
|
|
|
11
11
|
export type NativeViewProjection = Readonly<Omit<NativeViewHostProjectionProps, "presentationMode"> & {
|
|
12
12
|
key: number;
|
|
13
13
|
}>;
|
|
14
|
-
export declare function NativeProjectedViewContent({ controller, onInvalidMessage, record, webViewComponent, }: {
|
|
14
|
+
export declare function NativeProjectedViewContent({ controller, onInvalidMessage, presentationMode, record, webViewComponent, }: {
|
|
15
15
|
controller: NativeViewRecordController;
|
|
16
16
|
onInvalidMessage?: (error: Error, value: unknown) => void;
|
|
17
|
+
presentationMode?: NativeViewPresentationMode;
|
|
17
18
|
record: NativeViewRecord;
|
|
18
19
|
webViewComponent?: ComponentType<Record<string, unknown>>;
|
|
19
20
|
}): ReactElement;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createElement, Fragment, useCallback, useEffect, useLayoutEffect, useRef, useSyncExternalStore, } from "react";
|
|
2
|
+
import { NativeFlowContent } from "./native-flow-content.js";
|
|
2
3
|
import { NativeViewPresentationSurface } from "./native-view-presentation-surface.js";
|
|
3
4
|
import { ViewWebViewHost } from "./view-webview-host.js";
|
|
4
|
-
export function NativeProjectedViewContent({ controller, onInvalidMessage, record, webViewComponent, }) {
|
|
5
|
+
export function NativeProjectedViewContent({ controller, onInvalidMessage, presentationMode, record, webViewComponent, }) {
|
|
5
6
|
const detachConnectionRef = useRef(null);
|
|
6
7
|
const detachConnection = useCallback(() => {
|
|
7
8
|
const detach = detachConnectionRef.current;
|
|
@@ -18,6 +19,12 @@ export function NativeProjectedViewContent({ controller, onInvalidMessage, recor
|
|
|
18
19
|
}
|
|
19
20
|
}, [controller, record.allocationId]);
|
|
20
21
|
useEffect(() => detachConnection, [detachConnection]);
|
|
22
|
+
if (record.nativeFlow)
|
|
23
|
+
return createElement(NativeFlowContent, {
|
|
24
|
+
controller,
|
|
25
|
+
record,
|
|
26
|
+
presentationMode,
|
|
27
|
+
});
|
|
21
28
|
return createElement(ViewWebViewHost, {
|
|
22
29
|
generation: record.generation,
|
|
23
30
|
onActivationRetryExhausted: (error) => {
|
|
@@ -76,6 +83,7 @@ function NativeViewHostProjectionEntry({ controller, nativeViewComponent, onInva
|
|
|
76
83
|
}, createElement(NativeProjectedViewContent, {
|
|
77
84
|
controller,
|
|
78
85
|
onInvalidMessage,
|
|
86
|
+
presentationMode,
|
|
79
87
|
record,
|
|
80
88
|
webViewComponent,
|
|
81
89
|
}));
|
|
@@ -24,13 +24,24 @@ const CONTENT_HOST_SURFACE_STYLE = {
|
|
|
24
24
|
maxHeight: "100%",
|
|
25
25
|
maxWidth: "100%",
|
|
26
26
|
};
|
|
27
|
+
const NATIVE_CONTENT_PREPARATION_STYLE = {
|
|
28
|
+
position: "absolute",
|
|
29
|
+
top: 0,
|
|
30
|
+
left: 0,
|
|
31
|
+
right: 0,
|
|
32
|
+
opacity: 0,
|
|
33
|
+
};
|
|
34
|
+
const NATIVE_CONTENT_FRAME_STYLE = {
|
|
35
|
+
flexShrink: 1,
|
|
36
|
+
width: "100%",
|
|
37
|
+
};
|
|
27
38
|
const TOP_PLACEMENT_STYLE = {
|
|
28
39
|
justifyContent: "flex-start",
|
|
29
40
|
};
|
|
30
41
|
const BOTTOM_PLACEMENT_STYLE = {
|
|
31
42
|
justifyContent: "flex-end",
|
|
32
43
|
};
|
|
33
|
-
const
|
|
44
|
+
const FILL_AVAILABLE_BOUNDS_STYLE = {
|
|
34
45
|
height: "100%",
|
|
35
46
|
width: "100%",
|
|
36
47
|
};
|
|
@@ -63,9 +74,11 @@ export function NativeViewPresentationSurface({ children, presentationMode = "ov
|
|
|
63
74
|
var _a;
|
|
64
75
|
const NativeView = nativeViewComponent !== null && nativeViewComponent !== void 0 ? nativeViewComponent : resolveNativeView();
|
|
65
76
|
const isPresented = record.isActive && record.size !== null;
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
const isNativeContent = presentationMode === "content" && !!record.nativeFlow;
|
|
78
|
+
const hostStyle = isNativeContent && !isPresented
|
|
79
|
+
? NATIVE_CONTENT_PREPARATION_STYLE
|
|
80
|
+
: presentationMode === "content" && isPresented
|
|
81
|
+
? Object.assign(Object.assign({}, CONTENT_HOST_SURFACE_STYLE), (isNativeContent ? NATIVE_CONTENT_FRAME_STYLE : {})) : Object.assign(Object.assign(Object.assign({}, HOST_SURFACE_STYLE), (((_a = record.size) === null || _a === void 0 ? void 0 : _a.placement) === "docked-top"
|
|
69
82
|
? TOP_PLACEMENT_STYLE
|
|
70
83
|
: BOTTOM_PLACEMENT_STYLE)), (isPresented
|
|
71
84
|
? PRESENTED_HOST_SURFACE_STYLE
|
|
@@ -78,16 +91,20 @@ export function NativeViewPresentationSurface({ children, presentationMode = "ov
|
|
|
78
91
|
testID: `gx-native-view-presentation-${record.viewId}`,
|
|
79
92
|
}, createElement(NativeViewPresentationFrame, {
|
|
80
93
|
children,
|
|
94
|
+
isNativeContent,
|
|
81
95
|
nativeViewComponent,
|
|
82
96
|
record,
|
|
83
97
|
transitionSchedule,
|
|
84
98
|
}));
|
|
85
99
|
}
|
|
86
|
-
function NativeViewPresentationFrame({ children, nativeViewComponent, record, transitionSchedule, }) {
|
|
100
|
+
function NativeViewPresentationFrame({ children, isNativeContent, nativeViewComponent, record, transitionSchedule, }) {
|
|
87
101
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
88
102
|
const NativeView = nativeViewComponent !== null && nativeViewComponent !== void 0 ? nativeViewComponent : resolveNativeView();
|
|
89
103
|
const isPresented = record.isActive && record.size !== null;
|
|
90
|
-
|
|
104
|
+
// Native size reports describe this frame's viewport, not a content-size
|
|
105
|
+
// request. Keep it host-relative so a smaller layout cannot cap later growth.
|
|
106
|
+
const hasMeasuredFrame = !record.nativeFlow &&
|
|
107
|
+
(isPresented || ((_a = record.size) === null || _a === void 0 ? void 0 : _a.layoutTransactionId) !== undefined);
|
|
91
108
|
const cornerRadii = toNativeWidgetCornerRadii((_b = record.size) === null || _b === void 0 ? void 0 : _b.frameAppearance);
|
|
92
109
|
const shadow = toNativeWidgetFrameShadow(((_d = (_c = record.size) === null || _c === void 0 ? void 0 : _c.frameAppearance) === null || _d === void 0 ? void 0 : _d.kind) === "page"
|
|
93
110
|
? record.size.frameAppearance.resolvedBoxShadow
|
|
@@ -108,9 +125,13 @@ function NativeViewPresentationFrame({ children, nativeViewComponent, record, tr
|
|
|
108
125
|
};
|
|
109
126
|
const targetStyles = {
|
|
110
127
|
border: Object.assign(Object.assign(Object.assign(Object.assign({}, VIEW_FRAME_BORDER_OVERLAY_STYLE), cornerRadiusStyle), borderStyle), frameOverlayShadowStyle),
|
|
111
|
-
content: Object.assign(Object.assign({},
|
|
128
|
+
content: Object.assign(Object.assign({}, (isNativeContent
|
|
129
|
+
? { flexShrink: 1, overflow: "hidden" }
|
|
130
|
+
: VIEW_FRAME_CONTENT_STYLE)), cornerRadiusStyle),
|
|
112
131
|
frame: hasMeasuredFrame
|
|
113
|
-
? Object.assign(Object.assign(Object.assign(Object.assign({}, MEASURED_VIEW_FRAME_STYLE), cornerRadiusStyle), frameShadowStyle), { height: (_g = record.size) === null || _g === void 0 ? void 0 : _g.height, width: (_h = record.size) === null || _h === void 0 ? void 0 : _h.width }) :
|
|
132
|
+
? Object.assign(Object.assign(Object.assign(Object.assign({}, MEASURED_VIEW_FRAME_STYLE), cornerRadiusStyle), frameShadowStyle), { height: (_g = record.size) === null || _g === void 0 ? void 0 : _g.height, width: (_h = record.size) === null || _h === void 0 ? void 0 : _h.width }) : isNativeContent
|
|
133
|
+
? NATIVE_CONTENT_FRAME_STYLE
|
|
134
|
+
: FILL_AVAILABLE_BOUNDS_STYLE,
|
|
114
135
|
};
|
|
115
136
|
const frame = hasMeasuredFrame && record.size
|
|
116
137
|
? {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type WidgetLayoutTransitionSchedule } from "@getuserfeedback/protocol/host";
|
|
2
|
+
import { type NativeFlowInteraction, type NativeFlowProjection } from "@getuserfeedback/protocol/internal/native-flow-session";
|
|
2
3
|
import type { BrowserNavigationAvailabilityPayload } from "@getuserfeedback/protocol/internal/navigation-rpc";
|
|
3
4
|
import { type EventViewSize } from "@getuserfeedback/protocol/internal/view-host-control-messages";
|
|
4
5
|
import type { OpenRequestDispatcher } from "@getuserfeedback/sdk/internal/open-request-dispatcher";
|
|
@@ -8,6 +9,11 @@ type NativeViewSizeReport = Omit<EventViewSize, "gen" | "t" | "viewId"> & {
|
|
|
8
9
|
transitionSchedule?: WidgetLayoutTransitionSchedule;
|
|
9
10
|
};
|
|
10
11
|
export type NativeViewRecord = Readonly<{
|
|
12
|
+
nativeFlow?: Readonly<{
|
|
13
|
+
projection: NativeFlowProjection;
|
|
14
|
+
interaction: "idle" | "draft" | "action" | "draft-failed";
|
|
15
|
+
pending: boolean;
|
|
16
|
+
}>;
|
|
11
17
|
allocationId: number;
|
|
12
18
|
flowId: string | null;
|
|
13
19
|
generation: number;
|
|
@@ -18,6 +24,21 @@ export type NativeViewRecord = Readonly<{
|
|
|
18
24
|
viewId: string;
|
|
19
25
|
}>;
|
|
20
26
|
export type NativeViewRecordController = {
|
|
27
|
+
attachNativeContent(input: {
|
|
28
|
+
allocationId: number;
|
|
29
|
+
viewId: string;
|
|
30
|
+
}): NativeContentBinding | null;
|
|
31
|
+
interactNativeFlow(input: {
|
|
32
|
+
allocationId: number;
|
|
33
|
+
viewId: string;
|
|
34
|
+
projection: NativeFlowProjection;
|
|
35
|
+
intent: NativeFlowInteraction["intent"];
|
|
36
|
+
draft: NativeFlowInteraction["draft"];
|
|
37
|
+
}): boolean;
|
|
38
|
+
closeNativeFlow(input: {
|
|
39
|
+
allocationId: number;
|
|
40
|
+
viewId: string;
|
|
41
|
+
}): void;
|
|
21
42
|
attachViewConnection(input: {
|
|
22
43
|
allocationId: number;
|
|
23
44
|
connection: ViewWebViewHostConnection;
|
|
@@ -41,6 +62,13 @@ export type NativeViewRecordController = {
|
|
|
41
62
|
getSnapshot(): readonly NativeViewRecord[];
|
|
42
63
|
subscribe(listener: () => void): () => void;
|
|
43
64
|
};
|
|
65
|
+
type NativeContentBinding = {
|
|
66
|
+
onLayout(size: {
|
|
67
|
+
width: number;
|
|
68
|
+
height: number;
|
|
69
|
+
}): void;
|
|
70
|
+
dispose(): void;
|
|
71
|
+
};
|
|
44
72
|
type NativeOpenRequestObserver = (payload: Parameters<OpenRequestDispatcher["dispatch"]>[0]) => ReturnType<OpenRequestDispatcher["dispatch"]>;
|
|
45
73
|
export declare function createNativeViewRecordController(input: {
|
|
46
74
|
coreConnection: CoreWebViewHostConnection;
|
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
import { createWidgetLayoutTransitionSchedule, } from "@getuserfeedback/protocol/host";
|
|
2
2
|
import { coreViewHostActionSchema, } from "@getuserfeedback/protocol/internal/core-view-host-actions-v3";
|
|
3
|
+
import { actionPrerenderNativeFlowSchema, NATIVE_FLOW_SESSION_RPC_METHOD, nativeFlowInteractionSchema, nativeFlowProjectionSchema, } from "@getuserfeedback/protocol/internal/native-flow-session";
|
|
4
|
+
import { createGeneration } from "@getuserfeedback/protocol/internal/v3-generation";
|
|
5
|
+
import { buildRpcRequest, rpcRequestSchema, rpcResponseSchema, } from "@getuserfeedback/protocol/internal/v3-rpc-messages";
|
|
3
6
|
import { actionRequestCloseViewSchema, actionViewConnectSchema, buildEventLayoutTransactionCommitted, buildEventViewSize, eventViewSizeSchema, } from "@getuserfeedback/protocol/internal/view-host-control-messages";
|
|
4
7
|
import { buildEventViewActivated, buildEventViewActivationDecided, buildEventViewAllocationFailed, buildEventViewClosed, buildEventViewCloseFailed, buildEventViewPrerendered, } from "@getuserfeedback/protocol/internal/view-host-lifecycle-events-v3";
|
|
5
8
|
import { bindViewHostControlEnvelope, bindViewToCoreRelayEnvelope, parseCoreToViewRelayEnvelope, } from "@getuserfeedback/protocol/internal/view-relay-contract";
|
|
6
9
|
import { createViewPreparationReadinessRegistry, createViewPreparationRegistry, } from "./view-orchestration-runtime.js";
|
|
10
|
+
// RPC IDs are UUIDs, unlike command IDs whose non-WebCrypto fallback is prefixed.
|
|
11
|
+
// This is live request correlation, not an authentication token.
|
|
12
|
+
function createNativeInteractionRpcId() {
|
|
13
|
+
var _a, _b;
|
|
14
|
+
if (typeof ((_a = globalThis.crypto) === null || _a === void 0 ? void 0 : _a.randomUUID) === "function")
|
|
15
|
+
return globalThis.crypto.randomUUID();
|
|
16
|
+
const bytes = new Uint8Array(16);
|
|
17
|
+
if (typeof ((_b = globalThis.crypto) === null || _b === void 0 ? void 0 : _b.getRandomValues) === "function")
|
|
18
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
19
|
+
else
|
|
20
|
+
for (let index = 0; index < bytes.length; index++)
|
|
21
|
+
bytes[index] = Math.floor(Math.random() * 256);
|
|
22
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
23
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
24
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
25
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
26
|
+
}
|
|
7
27
|
const createNativeViewReadinessRegistry = () => createViewPreparationReadinessRegistry();
|
|
8
28
|
class NativeViewCleanupError extends Error {
|
|
9
29
|
constructor(resourceErrors, observerErrors, viewId) {
|
|
@@ -63,6 +83,10 @@ export function createNativeViewRecordController(input) {
|
|
|
63
83
|
if (entries.get(entry.viewId) !== entry) {
|
|
64
84
|
return false;
|
|
65
85
|
}
|
|
86
|
+
if (entry.nativeInteraction &&
|
|
87
|
+
entry.nativeInteraction.kind !== "draft-failed")
|
|
88
|
+
clearTimeout(entry.nativeInteraction.timeout);
|
|
89
|
+
entry.nativeInteraction = undefined;
|
|
66
90
|
const detachViewConnection = entry.viewConnectionDetach;
|
|
67
91
|
const resourceErrors = [];
|
|
68
92
|
const observerErrors = [];
|
|
@@ -226,11 +250,18 @@ export function createNativeViewRecordController(input) {
|
|
|
226
250
|
const handlePrerender = (action) => {
|
|
227
251
|
var _a;
|
|
228
252
|
var _b;
|
|
253
|
+
const srcdoc = action.t === "getuserfeedback:action:prerenderView" ? action.srcdoc : "";
|
|
254
|
+
if (srcdoc === undefined) {
|
|
255
|
+
failCoreConnection(new Error("Native prerenderView requires srcdoc; hosted URLs are unsupported"), "Unsupported native view document source");
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
229
258
|
const existing = entries.get(action.viewId);
|
|
230
259
|
if (existing) {
|
|
231
260
|
if (existing.generation !== action.gen ||
|
|
232
261
|
existing.instanceId !== action.instanceId ||
|
|
233
|
-
existing.flowRunId !== action.flowRunId
|
|
262
|
+
existing.flowRunId !== action.flowRunId ||
|
|
263
|
+
!!existing.nativeProjection !==
|
|
264
|
+
(action.t === "getuserfeedback:action:prerenderNativeFlow")) {
|
|
234
265
|
failCoreConnection(new Error(`Conflicting native projection binding ${action.viewId}`), "Conflicting native projection binding");
|
|
235
266
|
}
|
|
236
267
|
return;
|
|
@@ -240,31 +271,11 @@ export function createNativeViewRecordController(input) {
|
|
|
240
271
|
return;
|
|
241
272
|
}
|
|
242
273
|
nextAllocationId += 1;
|
|
243
|
-
const entry = {
|
|
244
|
-
|
|
245
|
-
flowId: ((_a = action.openRequest) === null || _a === void 0 ? void 0 : _a.source) === "command"
|
|
274
|
+
const entry = Object.assign(Object.assign({}, (action.t === "getuserfeedback:action:prerenderNativeFlow"
|
|
275
|
+
? { nativeProjection: action.projection }
|
|
276
|
+
: {})), { allocationId: nextAllocationId, flowId: ((_a = action.openRequest) === null || _a === void 0 ? void 0 : _a.source) === "command"
|
|
246
277
|
? ((_b = action.openRequest.flowId) !== null && _b !== void 0 ? _b : null)
|
|
247
|
-
: null,
|
|
248
|
-
generation: action.gen,
|
|
249
|
-
flowRunId: action.flowRunId,
|
|
250
|
-
instanceId: action.instanceId,
|
|
251
|
-
isActive: false,
|
|
252
|
-
isClosing: false,
|
|
253
|
-
isPreparationCommitted: false,
|
|
254
|
-
isPresentationAcknowledged: false,
|
|
255
|
-
isViewConnected: false,
|
|
256
|
-
isViewRelayDraining: false,
|
|
257
|
-
layoutReportRevision: 0,
|
|
258
|
-
pendingCoreToViewMessages: [],
|
|
259
|
-
preparation,
|
|
260
|
-
retainedSize: null,
|
|
261
|
-
size: null,
|
|
262
|
-
srcdoc: action.srcdoc,
|
|
263
|
-
committedLayoutReportOccurrence: null,
|
|
264
|
-
viewConnection: null,
|
|
265
|
-
viewConnectionDetach: null,
|
|
266
|
-
viewId: action.viewId,
|
|
267
|
-
};
|
|
278
|
+
: null, generation: action.gen, flowRunId: action.flowRunId, instanceId: action.instanceId, isActive: false, isClosing: false, isPreparationCommitted: false, isPresentationAcknowledged: false, isViewConnected: false, isViewRelayDraining: false, layoutReportRevision: 0, pendingCoreToViewMessages: [], preparation, retainedSize: null, size: null, srcdoc, committedLayoutReportOccurrence: null, viewConnection: null, viewConnectionDetach: null, viewId: action.viewId });
|
|
268
279
|
entries.set(action.viewId, entry);
|
|
269
280
|
try {
|
|
270
281
|
preparation.addCleanup(() => {
|
|
@@ -383,6 +394,39 @@ export function createNativeViewRecordController(input) {
|
|
|
383
394
|
if (coreConnectionFailed || disposed) {
|
|
384
395
|
return;
|
|
385
396
|
}
|
|
397
|
+
const nativeAction = actionPrerenderNativeFlowSchema.safeParse(value);
|
|
398
|
+
if (nativeAction.success &&
|
|
399
|
+
nativeAction.data.gen === input.coreConnection.generation) {
|
|
400
|
+
handlePrerender(nativeAction.data);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const response = rpcResponseSchema.safeParse(value);
|
|
404
|
+
if (response.success &&
|
|
405
|
+
response.data.gen === input.coreConnection.generation &&
|
|
406
|
+
response.data.viewId) {
|
|
407
|
+
const entry = entries.get(response.data.viewId);
|
|
408
|
+
const interaction = entry === null || entry === void 0 ? void 0 : entry.nativeInteraction;
|
|
409
|
+
if (entry &&
|
|
410
|
+
interaction &&
|
|
411
|
+
interaction.kind !== "draft-failed" &&
|
|
412
|
+
interaction.rpcId === response.data.rpcId &&
|
|
413
|
+
entry.nativeProjection &&
|
|
414
|
+
!entry.isClosing &&
|
|
415
|
+
entry.isActive) {
|
|
416
|
+
const result = response.data.ok
|
|
417
|
+
? nativeFlowProjectionSchema.safeParse(response.data.result)
|
|
418
|
+
: null;
|
|
419
|
+
if (!(result === null || result === void 0 ? void 0 : result.success)) {
|
|
420
|
+
interaction.fail();
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
clearTimeout(interaction.timeout);
|
|
424
|
+
entry.nativeInteraction = undefined;
|
|
425
|
+
entry.nativeProjection = result.data;
|
|
426
|
+
notify();
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
386
430
|
const action = coreViewHostActionSchema.safeParse(value);
|
|
387
431
|
if (action.success && action.data.gen === input.coreConnection.generation) {
|
|
388
432
|
switch (action.data.t) {
|
|
@@ -403,12 +447,12 @@ export function createNativeViewRecordController(input) {
|
|
|
403
447
|
});
|
|
404
448
|
if (relay) {
|
|
405
449
|
const entry = entries.get(relay.viewId);
|
|
406
|
-
if (entry) {
|
|
450
|
+
if (entry && !entry.nativeProjection) {
|
|
407
451
|
enqueueCoreToViewMessage(entry, relay.message);
|
|
408
452
|
}
|
|
409
453
|
}
|
|
410
454
|
};
|
|
411
|
-
const
|
|
455
|
+
const completePreparationReadiness = (entry, size) => {
|
|
412
456
|
if (disposed || entries.get(entry.viewId) !== entry) {
|
|
413
457
|
return;
|
|
414
458
|
}
|
|
@@ -460,6 +504,144 @@ export function createNativeViewRecordController(input) {
|
|
|
460
504
|
};
|
|
461
505
|
const stopCoreTransport = input.coreConnection.transport.subscribe(handleCoreMessage);
|
|
462
506
|
return {
|
|
507
|
+
attachNativeContent({ allocationId, viewId }) {
|
|
508
|
+
const entry = entries.get(viewId);
|
|
509
|
+
if (disposed ||
|
|
510
|
+
coreConnectionFailed ||
|
|
511
|
+
!(entry === null || entry === void 0 ? void 0 : entry.nativeProjection) ||
|
|
512
|
+
entry.allocationId !== allocationId ||
|
|
513
|
+
entry.isClosing)
|
|
514
|
+
return null;
|
|
515
|
+
const binding = {
|
|
516
|
+
onLayout(size) {
|
|
517
|
+
var _a, _b;
|
|
518
|
+
if (disposed ||
|
|
519
|
+
coreConnectionFailed ||
|
|
520
|
+
entries.get(viewId) !== entry ||
|
|
521
|
+
entry.isClosing ||
|
|
522
|
+
entry.nativeContentBinding !== binding)
|
|
523
|
+
return;
|
|
524
|
+
if (!Number.isFinite(size.width) ||
|
|
525
|
+
!Number.isFinite(size.height) ||
|
|
526
|
+
size.width <= 0 ||
|
|
527
|
+
size.height <= 0 ||
|
|
528
|
+
size.width > 4096 ||
|
|
529
|
+
size.height > 4096)
|
|
530
|
+
return;
|
|
531
|
+
if (((_a = entry.size) === null || _a === void 0 ? void 0 : _a.width) === size.width &&
|
|
532
|
+
((_b = entry.size) === null || _b === void 0 ? void 0 : _b.height) === size.height)
|
|
533
|
+
return;
|
|
534
|
+
entry.size = Object.assign({}, size);
|
|
535
|
+
entry.layoutReportRevision += 1;
|
|
536
|
+
try {
|
|
537
|
+
if (!entry.isPreparationCommitted)
|
|
538
|
+
completePreparationReadiness(entry, entry.size);
|
|
539
|
+
else
|
|
540
|
+
input.coreConnection.transport.postMessage(buildEventViewSize(Object.assign({ gen: entry.generation, viewId }, entry.size)));
|
|
541
|
+
notify();
|
|
542
|
+
}
|
|
543
|
+
catch (error) {
|
|
544
|
+
if (entry.isPreparationCommitted)
|
|
545
|
+
failCoreConnection(error, "Native content readiness failed");
|
|
546
|
+
else
|
|
547
|
+
failAllocation(entry, error);
|
|
548
|
+
}
|
|
549
|
+
},
|
|
550
|
+
dispose() {
|
|
551
|
+
if (entry.nativeContentBinding !== binding)
|
|
552
|
+
return;
|
|
553
|
+
entry.nativeContentBinding = undefined;
|
|
554
|
+
entry.size = null;
|
|
555
|
+
if (!disposed && entries.get(viewId) === entry)
|
|
556
|
+
notify();
|
|
557
|
+
},
|
|
558
|
+
};
|
|
559
|
+
entry.nativeContentBinding = binding;
|
|
560
|
+
return binding;
|
|
561
|
+
},
|
|
562
|
+
interactNativeFlow({ allocationId, viewId, projection, intent, draft }) {
|
|
563
|
+
var _a, _b;
|
|
564
|
+
const entry = entries.get(viewId);
|
|
565
|
+
if (disposed ||
|
|
566
|
+
coreConnectionFailed ||
|
|
567
|
+
!entry ||
|
|
568
|
+
entry.allocationId !== allocationId ||
|
|
569
|
+
entry.isClosing ||
|
|
570
|
+
!entry.isActive ||
|
|
571
|
+
!entry.isPreparationCommitted ||
|
|
572
|
+
!entry.nativeContentBinding ||
|
|
573
|
+
!entry.nativeProjection ||
|
|
574
|
+
(intent === "draft"
|
|
575
|
+
? ((_a = entry.nativeInteraction) === null || _a === void 0 ? void 0 : _a.kind) === "action" ||
|
|
576
|
+
entry.nativeProjection.nodeId !== projection.nodeId
|
|
577
|
+
: entry.nativeInteraction !== undefined ||
|
|
578
|
+
entry.nativeProjection !== projection))
|
|
579
|
+
return false;
|
|
580
|
+
const parsed = nativeFlowInteractionSchema.safeParse({
|
|
581
|
+
flowRunId: entry.flowRunId,
|
|
582
|
+
nodeId: projection.nodeId,
|
|
583
|
+
intent,
|
|
584
|
+
draft,
|
|
585
|
+
});
|
|
586
|
+
if (!parsed.success)
|
|
587
|
+
return false;
|
|
588
|
+
const rpcId = createNativeInteractionRpcId();
|
|
589
|
+
const currentProjection = entry.nativeProjection;
|
|
590
|
+
if (((_b = entry.nativeInteraction) === null || _b === void 0 ? void 0 : _b.kind) === "draft")
|
|
591
|
+
clearTimeout(entry.nativeInteraction.timeout);
|
|
592
|
+
// Match the ordinary RPC budget. Correlation already rejects late replies;
|
|
593
|
+
// Timeout or a failed reply restores editing for an explicit manual retry.
|
|
594
|
+
const settleFailure = () => {
|
|
595
|
+
var _a, _b;
|
|
596
|
+
if (disposed ||
|
|
597
|
+
entries.get(viewId) !== entry ||
|
|
598
|
+
((_a = entry.nativeInteraction) === null || _a === void 0 ? void 0 : _a.kind) === "draft-failed" ||
|
|
599
|
+
((_b = entry.nativeInteraction) === null || _b === void 0 ? void 0 : _b.rpcId) !== rpcId)
|
|
600
|
+
return;
|
|
601
|
+
clearTimeout(entry.nativeInteraction.timeout);
|
|
602
|
+
entry.nativeInteraction =
|
|
603
|
+
intent === "draft" ? { kind: "draft-failed" } : undefined;
|
|
604
|
+
entry.nativeProjection = Object.assign(Object.assign({}, currentProjection), { answers: Object.assign(Object.assign({}, currentProjection.answers), parsed.data.draft), formErrors: ["Something went wrong. Please try again."] });
|
|
605
|
+
notify();
|
|
606
|
+
};
|
|
607
|
+
entry.nativeInteraction = {
|
|
608
|
+
kind: intent === "draft" ? "draft" : "action",
|
|
609
|
+
rpcId,
|
|
610
|
+
fail: settleFailure,
|
|
611
|
+
timeout: setTimeout(settleFailure, 10000),
|
|
612
|
+
};
|
|
613
|
+
try {
|
|
614
|
+
notify();
|
|
615
|
+
input.coreConnection.transport.postMessage(rpcRequestSchema.parse(buildRpcRequest({
|
|
616
|
+
gen: entry.generation,
|
|
617
|
+
viewId,
|
|
618
|
+
rpcId,
|
|
619
|
+
idemKey: rpcId,
|
|
620
|
+
method: NATIVE_FLOW_SESSION_RPC_METHOD,
|
|
621
|
+
params: parsed.data,
|
|
622
|
+
})));
|
|
623
|
+
}
|
|
624
|
+
catch (error) {
|
|
625
|
+
failCoreConnection(error, "Native interaction delivery failed");
|
|
626
|
+
}
|
|
627
|
+
return true;
|
|
628
|
+
},
|
|
629
|
+
closeNativeFlow({ allocationId, viewId }) {
|
|
630
|
+
var _a;
|
|
631
|
+
const entry = entries.get(viewId);
|
|
632
|
+
if (disposed ||
|
|
633
|
+
coreConnectionFailed ||
|
|
634
|
+
!entry ||
|
|
635
|
+
entry.allocationId !== allocationId ||
|
|
636
|
+
!((_a = entry.nativeProjection) === null || _a === void 0 ? void 0 : _a.canClose) ||
|
|
637
|
+
!entry.isActive)
|
|
638
|
+
return;
|
|
639
|
+
closeViewFromRequest(entry, {
|
|
640
|
+
t: "getuserfeedback:action:requestCloseView",
|
|
641
|
+
gen: createGeneration(input.coreConnection.generation),
|
|
642
|
+
viewId,
|
|
643
|
+
});
|
|
644
|
+
},
|
|
463
645
|
failViewAllocation({ allocationId, error, viewId }) {
|
|
464
646
|
const entry = entries.get(viewId);
|
|
465
647
|
if (!entry || entry.allocationId !== allocationId) {
|
|
@@ -515,9 +697,11 @@ export function createNativeViewRecordController(input) {
|
|
|
515
697
|
entry.isClosing ||
|
|
516
698
|
!entry.isActive ||
|
|
517
699
|
!entry.isPreparationCommitted ||
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
700
|
+
(entry.nativeProjection
|
|
701
|
+
? !entry.nativeContentBinding
|
|
702
|
+
: !entry.isViewConnected ||
|
|
703
|
+
entry.viewConnection === null ||
|
|
704
|
+
entry.viewConnectionDetach === null) ||
|
|
521
705
|
entry.size === null ||
|
|
522
706
|
entry.isPresentationAcknowledged) {
|
|
523
707
|
return;
|
|
@@ -540,6 +724,7 @@ export function createNativeViewRecordController(input) {
|
|
|
540
724
|
const { viewId } = connection;
|
|
541
725
|
const entry = entries.get(viewId);
|
|
542
726
|
if (!entry ||
|
|
727
|
+
entry.nativeProjection ||
|
|
543
728
|
entry.allocationId !== allocationId ||
|
|
544
729
|
connection.generation !== input.coreConnection.generation) {
|
|
545
730
|
return null;
|
|
@@ -584,7 +769,7 @@ export function createNativeViewRecordController(input) {
|
|
|
584
769
|
return;
|
|
585
770
|
}
|
|
586
771
|
try {
|
|
587
|
-
|
|
772
|
+
completePreparationReadiness(entry, size);
|
|
588
773
|
}
|
|
589
774
|
catch (error) {
|
|
590
775
|
failCoreConnection(error, "Native view readiness delivery failed");
|
|
@@ -777,16 +962,18 @@ export function createNativeViewRecordController(input) {
|
|
|
777
962
|
};
|
|
778
963
|
}
|
|
779
964
|
function toRecord(entry) {
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
965
|
+
var _a, _b, _c;
|
|
966
|
+
var _d;
|
|
967
|
+
return Object.freeze(Object.assign(Object.assign({}, (entry.nativeProjection
|
|
968
|
+
? {
|
|
969
|
+
nativeFlow: {
|
|
970
|
+
projection: entry.nativeProjection,
|
|
971
|
+
interaction: (_d = (_a = entry.nativeInteraction) === null || _a === void 0 ? void 0 : _a.kind) !== null && _d !== void 0 ? _d : "idle",
|
|
972
|
+
pending: ((_b = entry.nativeInteraction) === null || _b === void 0 ? void 0 : _b.kind) === "draft" ||
|
|
973
|
+
((_c = entry.nativeInteraction) === null || _c === void 0 ? void 0 : _c.kind) === "action",
|
|
974
|
+
},
|
|
975
|
+
}
|
|
976
|
+
: {})), { allocationId: entry.allocationId, flowId: entry.flowId, generation: entry.generation, isActive: entry.isActive, layoutReportRevision: entry.layoutReportRevision, size: entry.size, srcdoc: entry.srcdoc, viewId: entry.viewId }));
|
|
790
977
|
}
|
|
791
978
|
function toViewSizeReport(event, context) {
|
|
792
979
|
var _a;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { createCommandRequestId, REACT_NATIVE_WEBVIEW_TRANSPORT_CAPABILITIES, } from "@getuserfeedback/protocol/host";
|
|
2
|
+
import { NATIVE_FLOW_SESSION_PROTOCOL } from "@getuserfeedback/protocol/internal/native-flow-session";
|
|
2
3
|
import { REACT_NATIVE_SDK_VERSION } from "./version.js";
|
|
3
4
|
export const REACT_NATIVE_CLIENT_META = {
|
|
4
5
|
clientName: "@getuserfeedback/react-native",
|
|
5
6
|
clientVersion: REACT_NATIVE_SDK_VERSION,
|
|
6
7
|
loader: "sdk",
|
|
7
8
|
protocolCapabilities: [
|
|
9
|
+
NATIVE_FLOW_SESSION_PROTOCOL,
|
|
8
10
|
REACT_NATIVE_WEBVIEW_TRANSPORT_CAPABILITIES.LAYOUT_TRANSITION_POLICY,
|
|
9
11
|
REACT_NATIVE_WEBVIEW_TRANSPORT_CAPABILITIES.LAYOUT_TRANSITION_SCHEDULE,
|
|
10
12
|
],
|
|
@@ -18,5 +20,15 @@ export function createProviderCommandEnvelope({ clientMeta, command, idempotency
|
|
|
18
20
|
const resolvedIdempotencyKey = (idempotencyKey === null || idempotencyKey === void 0 ? void 0 : idempotencyKey.trim()) || resolvedRequestId;
|
|
19
21
|
const resolvedCommand = clientMeta !== undefined
|
|
20
22
|
? Object.assign(Object.assign({}, command), { opts: Object.assign(Object.assign({}, command.opts), { clientMeta }) }) : command;
|
|
21
|
-
|
|
23
|
+
const envelope = {
|
|
24
|
+
version: "1",
|
|
25
|
+
command: resolvedCommand,
|
|
26
|
+
idempotencyKey: resolvedIdempotencyKey,
|
|
27
|
+
instanceId: resolvedInstanceId,
|
|
28
|
+
requestId: resolvedRequestId,
|
|
29
|
+
};
|
|
30
|
+
if (clientMeta !== undefined) {
|
|
31
|
+
envelope.clientMeta = clientMeta;
|
|
32
|
+
}
|
|
33
|
+
return envelope;
|
|
22
34
|
}
|
package/dist/version.js
CHANGED
|
@@ -3,4 +3,4 @@ const reactNativeSdkVersion = typeof __GX_REACT_NATIVE_SDK_VERSION__ === "string
|
|
|
3
3
|
: "";
|
|
4
4
|
// Build scripts patch this fallback to the package version for published artifacts.
|
|
5
5
|
// Source-linked workspace usage keeps the local fallback.
|
|
6
|
-
export const REACT_NATIVE_SDK_VERSION = reactNativeSdkVersion.length > 0 ? reactNativeSdkVersion : "5.
|
|
6
|
+
export const REACT_NATIVE_SDK_VERSION = reactNativeSdkVersion.length > 0 ? reactNativeSdkVersion : "5.15.3";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getuserfeedback/react-native",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.15.3",
|
|
4
4
|
"description": "getuserfeedback React Native SDK",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"getuserfeedback",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"lint": "biome check ."
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@getuserfeedback/protocol": "^6.0.
|
|
58
|
-
"@getuserfeedback/sdk": "^0.19.
|
|
57
|
+
"@getuserfeedback/protocol": "^6.0.3",
|
|
58
|
+
"@getuserfeedback/sdk": "^0.19.4",
|
|
59
59
|
"robot3": "^1.2.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|