@getuserfeedback/react-native 5.15.3 → 5.15.12

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/client.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { AppEventExternalId, PublicCommandPayload } from "@getuserfeedback/protocol";
2
2
  import type { ActionRegistration, ActionsConfig, CapabilitiesInput, ConfigureOptions, EventOptions, EventProperties, FlagsInput, GraphOperations, InitOptions, LinksConfig, OpenRequestedCallback } from "@getuserfeedback/sdk";
3
- export type { ActionsConfig, CapabilitiesInput, ConfigureOptions, EventOptions, EventProperties, FlagsInput, GraphOperations, GraphRelationship, GraphRelationshipRef, LinkRequest, LinkRouter, LinksConfig, OpenRequestedCallback, OpenRequestedEvent, TrackOptions, } from "@getuserfeedback/sdk";
3
+ export type { ActionsConfig, CapabilitiesInput, ConfigureOptions, EventOptions, EventProperties, FlagsInput, GraphEndpointIdentity, GraphOperations, GraphRelationship, GraphRelationshipRef, LinkRequest, LinkRouter, LinksConfig, OpenRequestedCallback, OpenRequestedEvent, TrackOptions, } from "@getuserfeedback/sdk";
4
4
  type IdentifyTraits = Record<string, unknown>;
5
5
  type OpenCommand = Extract<PublicCommandPayload, {
6
6
  kind: "open";
@@ -10,7 +10,7 @@ var __rest = (this && this.__rest) || function (s, e) {
10
10
  return t;
11
11
  };
12
12
  import { createElement, useCallback, useContext, useLayoutEffect, useRef, useSyncExternalStore, } from "react";
13
- import { NativeFlowContainerContext } from "./native-flow-container-context.js";
13
+ import { NativeFlowContainerContext, } from "./native-flow-container-context.js";
14
14
  import { NativeViewHostProjection, } from "./native-view-host-projection.js";
15
15
  const EMPTY_RECORDS = Object.freeze([]);
16
16
  const subscribeNoop = () => () => { };
@@ -63,7 +63,7 @@ export function FlowContent() {
63
63
  throw new Error("FlowContent must be used inside GetUserFeedbackProvider");
64
64
  }
65
65
  const ownerRef = useRef(null);
66
- (_a = ownerRef.current) !== null && _a !== void 0 ? _a : (ownerRef.current = {});
66
+ (_a = ownerRef.current) !== null && _a !== void 0 ? _a : (ownerRef.current = Symbol("native FlowContent owner"));
67
67
  const owner = ownerRef.current;
68
68
  useLayoutEffect(() => context.registerContent(owner), [context.registerContent, owner]);
69
69
  return context.contentOwner === owner
@@ -1,17 +1,18 @@
1
1
  import type { Client } from "./client.js";
2
2
  import type { NativeProviderFlowRegistry } from "./native-provider-flow-registry.js";
3
3
  import type { NativeViewProjection } from "./native-view-host-projection.js";
4
+ export type NativeFlowContentOwner = symbol;
4
5
  export type NativeFlowContainerContextValue = Readonly<{
5
6
  client: Client;
6
- contentOwner: object | null;
7
+ contentOwner: NativeFlowContentOwner | null;
7
8
  flowRegistry: NativeProviderFlowRegistry;
8
9
  projection: NativeViewProjection | null;
9
- registerContent: (owner: object) => () => void;
10
+ registerContent: (owner: NativeFlowContentOwner) => () => void;
10
11
  }>;
11
12
  export declare const NativeFlowContainerContext: import("react").Context<Readonly<{
12
13
  client: Client;
13
- contentOwner: object | null;
14
+ contentOwner: NativeFlowContentOwner | null;
14
15
  flowRegistry: NativeProviderFlowRegistry;
15
16
  projection: NativeViewProjection | null;
16
- registerContent: (owner: object) => () => void;
17
+ registerContent: (owner: NativeFlowContentOwner) => () => void;
17
18
  }> | null>;
@@ -1,5 +1,6 @@
1
1
  import { createElement, useLayoutEffect, useRef, useState, } from "react";
2
2
  import { useNativeColorScheme } from "./native-color-scheme.js";
3
+ import { resolveNativeRadioErrors, resolveNativeRadioIntent, } from "./native-radio-decisions.js";
3
4
  /** Thin native editor: drafts only. Core owns validation, navigation and submission. */
4
5
  export function NativeFlowContent({ controller, presentationMode = "overlay", record, }) {
5
6
  const flow = record.nativeFlow;
@@ -62,8 +63,27 @@ export function NativeFlowContent({ controller, presentationMode = "overlay", re
62
63
  draft: Object.assign({}, rawDraft.current),
63
64
  });
64
65
  const disabled = interaction === "action" || !record.isActive;
65
- const button = (label, onPress, buttonDisabled = interaction !== "idle" || !record.isActive) => createElement(Pressable, {
66
- key: label,
66
+ const fields = projection.blocks.filter((block) => block.kind !== "statement");
67
+ // Match web usePageStatus: whitespace and nonempty arrays count as filled.
68
+ const isValueEmpty = (value) => value === null ||
69
+ value === undefined ||
70
+ (typeof value === "string" && value.length === 0) ||
71
+ (Array.isArray(value) && value.length === 0);
72
+ const hasEmptyRequired = fields.some((field) => field.required && isValueEmpty(draft[field.key]));
73
+ const localIntent = resolveNativeRadioIntent(projection, draft);
74
+ const errors = resolveNativeRadioErrors(projection, draft);
75
+ const primaryIntent = localIntent !== null && localIntent !== void 0 ? localIntent : (projection.status === "ready-to-submit" ? "submit" : "continue");
76
+ const showSubmit = primaryIntent === "submit";
77
+ const actionsWaiting = interaction !== "idle" && !(localIntent && interaction === "draft");
78
+ const showSkip = fields.length > 0 &&
79
+ fields.every((field) => !field.required && isValueEmpty(draft[field.key]));
80
+ const primaryLabel = showSubmit ? "Submit" : showSkip ? "Skip" : "Continue";
81
+ const primaryDisabled = actionsWaiting ||
82
+ !record.isActive ||
83
+ hasEmptyRequired ||
84
+ (showSubmit && fields.some((field) => Boolean(errors[field.key])));
85
+ const button = (label, onPress, buttonDisabled = actionsWaiting || !record.isActive, key = label) => createElement(Pressable, {
86
+ key,
67
87
  accessibilityRole: "button",
68
88
  accessibilityLabel: label,
69
89
  disabled: buttonDisabled,
@@ -152,8 +172,8 @@ export function NativeFlowContent({ controller, presentationMode = "overlay", re
152
172
  style: {
153
173
  color: disabled ? palette.disabledText : palette.text,
154
174
  },
155
- }, `${selected === option.value ? "●" : "○"} ${option.label}`)))), projection.errors[block.key]
156
- ? createElement(Text, { accessibilityRole: "alert", style: { color: palette.error } }, projection.errors[block.key])
175
+ }, `${selected === option.value ? "●" : "○"} ${option.label}`)))), errors[block.key]
176
+ ? createElement(Text, { accessibilityRole: "alert", style: { color: palette.error } }, errors[block.key])
157
177
  : null);
158
178
  }), ...projection.formErrors.map((text, index) => createElement(Text, {
159
179
  key: `error-${index}`,
@@ -164,15 +184,11 @@ export function NativeFlowContent({ controller, presentationMode = "overlay", re
164
184
  ? createElement(Text, { testID: "native-flow-complete", style: { color: palette.text } }, projection.mode === "simulated"
165
185
  ? "SIMULATED completion — nothing saved"
166
186
  : "Thank you")
167
- : null, projection.canGoBack ? button("Back", () => interact("back")) : null, interaction === "draft-failed"
187
+ : null, interaction === "draft-failed"
168
188
  ? button("Retry refresh", () => interact("draft"), !record.isActive)
169
189
  : 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
190
+ ? button(primaryLabel, () => interact(primaryIntent), primaryDisabled, "primary-action")
191
+ : null, projection.canGoBack ? button("Back", () => interact("back")) : null, projection.canClose
176
192
  ? createElement(Pressable, {
177
193
  accessibilityRole: "button",
178
194
  accessibilityLabel: "Close",
@@ -1,5 +1,6 @@
1
1
  import { assertNoSegmentExternalIdsInValueArgument } from "@getuserfeedback/protocol/host";
2
2
  import { buildCustomerOccurrenceCommand } from "@getuserfeedback/sdk/internal/customer-occurrence-command";
3
+ import { toGraphRelationshipProperties } from "@getuserfeedback/sdk/internal/graph-relationship-properties";
3
4
  import { createOpenRequestDispatcher, } from "@getuserfeedback/sdk/internal/open-request-dispatcher";
4
5
  export const parseRequiredString = (name, value) => {
5
6
  const normalized = value === null || value === void 0 ? void 0 : value.trim();
@@ -57,8 +58,8 @@ export function createNativeProviderClient({ dispatchCommand, flow, instanceId,
57
58
  track,
58
59
  screen,
59
60
  graph: {
60
- connect: (relationship) => track("gx.graph.relationship.connected", relationship),
61
- disconnect: (relationship) => track("gx.graph.relationship.disconnected", relationship),
61
+ connect: (relationship) => track("gx.graph.relationship.connected", toGraphRelationshipProperties(relationship)),
62
+ disconnect: (relationship) => track("gx.graph.relationship.disconnected", toGraphRelationshipProperties(relationship)),
62
63
  },
63
64
  flow,
64
65
  close: () => dispatchCommand({ kind: "close" }),
@@ -1,6 +1,6 @@
1
1
  import { type ComponentType, type ReactElement, type ReactNode } from "react";
2
2
  import type { Client, ClientOptions } from "./client.js";
3
- export type { ActionsConfig, CapabilitiesInput, Client, ClientOptions, ConfigureOptions, EventOptions, EventProperties, FlagsInput, FlowRun, GraphOperations, GraphRelationship, GraphRelationshipRef, LinkRequest, LinkRouter, LinksConfig, NativeActionRegistration, OpenFlowOptions, OpenRequestedCallback, OpenRequestedEvent, PrerenderFlowOptions, TrackOptions, } from "./client.js";
3
+ export type { ActionsConfig, CapabilitiesInput, Client, ClientOptions, ConfigureOptions, EventOptions, EventProperties, FlagsInput, FlowRun, GraphEndpointIdentity, GraphOperations, GraphRelationship, GraphRelationshipRef, LinkRequest, LinkRouter, LinksConfig, NativeActionRegistration, OpenFlowOptions, OpenRequestedCallback, OpenRequestedEvent, PrerenderFlowOptions, TrackOptions, } from "./client.js";
4
4
  export type { UseFlowContainerReturn } from "./flow-container.js";
5
5
  export { FlowContent, useFlowContainer } from "./flow-container.js";
6
6
  export type { UseFlowOpenOptions, UseFlowOptions, UseFlowReturn, } from "./use-flow.js";
@@ -0,0 +1,5 @@
1
+ import type { NativeFlowInteraction, NativeFlowProjection } from "@getuserfeedback/protocol/internal/native-flow-session";
2
+ /** Exact lookup only. Core owns normalization, branching and action validation. */
3
+ export declare function resolveNativeRadioIntent(projection: NativeFlowProjection, draft: NativeFlowInteraction["draft"]): "continue" | "submit" | undefined;
4
+ /** Feedback belongs to the answer Core validated, not a later local choice. */
5
+ export declare function resolveNativeRadioErrors(projection: NativeFlowProjection, draft: NativeFlowInteraction["draft"]): Record<string, string>;
@@ -0,0 +1,29 @@
1
+ /** Exact lookup only. Core owns normalization, branching and action validation. */
2
+ export function resolveNativeRadioIntent(projection, draft) {
3
+ var _a;
4
+ const table = projection.radioDecisions;
5
+ if (!table ||
6
+ Object.keys(draft).length !== 1 ||
7
+ !Object.hasOwn(draft, table.fieldKey))
8
+ return undefined;
9
+ const value = draft[table.fieldKey];
10
+ if (value !== null && typeof value !== "string")
11
+ return undefined;
12
+ return (_a = table.entries.find((entry) => entry.value === value)) === null || _a === void 0 ? void 0 : _a.intent;
13
+ }
14
+ /** Feedback belongs to the answer Core validated, not a later local choice. */
15
+ export function resolveNativeRadioErrors(projection, draft) {
16
+ const field = projection.blocks.find((block) => block.kind === "radio");
17
+ if (!resolveNativeRadioIntent(projection, draft) || (field === null || field === void 0 ? void 0 : field.kind) !== "radio")
18
+ return projection.errors;
19
+ const value = draft[field.key];
20
+ // Core certifies literal options (including authored empty strings) unchanged
21
+ // by normalization. Other values retain the asynchronous feedback policy.
22
+ if (typeof value !== "string" ||
23
+ !field.options.some((option) => option.value === value) ||
24
+ value === projection.answers[field.key])
25
+ return projection.errors;
26
+ const errors = Object.assign({}, projection.errors);
27
+ delete errors[field.key];
28
+ return errors;
29
+ }
@@ -6,6 +6,7 @@ import { buildRpcRequest, rpcRequestSchema, rpcResponseSchema, } from "@getuserf
6
6
  import { actionRequestCloseViewSchema, actionViewConnectSchema, buildEventLayoutTransactionCommitted, buildEventViewSize, eventViewSizeSchema, } from "@getuserfeedback/protocol/internal/view-host-control-messages";
7
7
  import { buildEventViewActivated, buildEventViewActivationDecided, buildEventViewAllocationFailed, buildEventViewClosed, buildEventViewCloseFailed, buildEventViewPrerendered, } from "@getuserfeedback/protocol/internal/view-host-lifecycle-events-v3";
8
8
  import { bindViewHostControlEnvelope, bindViewToCoreRelayEnvelope, parseCoreToViewRelayEnvelope, } from "@getuserfeedback/protocol/internal/view-relay-contract";
9
+ import { resolveNativeRadioErrors, resolveNativeRadioIntent, } from "./native-radio-decisions.js";
9
10
  import { createViewPreparationReadinessRegistry, createViewPreparationRegistry, } from "./view-orchestration-runtime.js";
10
11
  // RPC IDs are UUIDs, unlike command IDs whose non-WebCrypto fallback is prefixed.
11
12
  // This is live request correlation, not an authentication token.
@@ -272,7 +273,10 @@ export function createNativeViewRecordController(input) {
272
273
  }
273
274
  nextAllocationId += 1;
274
275
  const entry = Object.assign(Object.assign({}, (action.t === "getuserfeedback:action:prerenderNativeFlow"
275
- ? { nativeProjection: action.projection }
276
+ ? {
277
+ nativeProjection: action.projection,
278
+ nativeRadioProjections: new WeakSet([action.projection]),
279
+ }
276
280
  : {})), { allocationId: nextAllocationId, flowId: ((_a = action.openRequest) === null || _a === void 0 ? void 0 : _a.source) === "command"
277
281
  ? ((_b = action.openRequest.flowId) !== null && _b !== void 0 ? _b : null)
278
282
  : 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 });
@@ -391,6 +395,7 @@ export function createNativeViewRecordController(input) {
391
395
  drainCoreToViewMessages(entry);
392
396
  };
393
397
  const handleCoreMessage = (value) => {
398
+ var _a;
394
399
  if (coreConnectionFailed || disposed) {
395
400
  return;
396
401
  }
@@ -421,8 +426,13 @@ export function createNativeViewRecordController(input) {
421
426
  return;
422
427
  }
423
428
  clearTimeout(interaction.timeout);
429
+ if (interaction.kind !== "draft" ||
430
+ entry.nativeProjection.nodeId !== result.data.nodeId) {
431
+ entry.nativeRadioProjections = new WeakSet();
432
+ }
424
433
  entry.nativeInteraction = undefined;
425
434
  entry.nativeProjection = result.data;
435
+ (_a = entry.nativeRadioProjections) === null || _a === void 0 ? void 0 : _a.add(result.data);
426
436
  notify();
427
437
  return;
428
438
  }
@@ -560,8 +570,14 @@ export function createNativeViewRecordController(input) {
560
570
  return binding;
561
571
  },
562
572
  interactNativeFlow({ allocationId, viewId, projection, intent, draft }) {
563
- var _a, _b;
573
+ var _a, _b, _c, _d, _e;
564
574
  const entry = entries.get(viewId);
575
+ const current = entry === null || entry === void 0 ? void 0 : entry.nativeProjection;
576
+ const currentIntent = current && resolveNativeRadioIntent(current, draft);
577
+ const presentedIntent = resolveNativeRadioIntent(projection, draft);
578
+ const local = currentIntent !== undefined && presentedIntent !== undefined;
579
+ const radioLifetime = !(projection.radioDecisions || (current === null || current === void 0 ? void 0 : current.radioDecisions)) ||
580
+ ((_a = entry === null || entry === void 0 ? void 0 : entry.nativeRadioProjections) === null || _a === void 0 ? void 0 : _a.has(projection));
565
581
  if (disposed ||
566
582
  coreConnectionFailed ||
567
583
  !entry ||
@@ -571,11 +587,15 @@ export function createNativeViewRecordController(input) {
571
587
  !entry.isPreparationCommitted ||
572
588
  !entry.nativeContentBinding ||
573
589
  !entry.nativeProjection ||
590
+ !radioLifetime ||
574
591
  (intent === "draft"
575
- ? ((_a = entry.nativeInteraction) === null || _a === void 0 ? void 0 : _a.kind) === "action" ||
592
+ ? ((_b = entry.nativeInteraction) === null || _b === void 0 ? void 0 : _b.kind) === "action" ||
576
593
  entry.nativeProjection.nodeId !== projection.nodeId
577
- : entry.nativeInteraction !== undefined ||
578
- entry.nativeProjection !== projection))
594
+ : local
595
+ ? ((_c = entry.nativeInteraction) === null || _c === void 0 ? void 0 : _c.kind) === "action" ||
596
+ ((_d = entry.nativeInteraction) === null || _d === void 0 ? void 0 : _d.kind) === "draft-failed"
597
+ : entry.nativeInteraction !== undefined ||
598
+ entry.nativeProjection !== projection))
579
599
  return false;
580
600
  const parsed = nativeFlowInteractionSchema.safeParse({
581
601
  flowRunId: entry.flowRunId,
@@ -585,9 +605,21 @@ export function createNativeViewRecordController(input) {
585
605
  });
586
606
  if (!parsed.success)
587
607
  return false;
608
+ if (local && intent !== "draft" && intent !== "back") {
609
+ const field = entry.nativeProjection.blocks.find((block) => block.kind === "radio");
610
+ if (intent !== currentIntent ||
611
+ intent !== presentedIntent ||
612
+ ((field === null || field === void 0 ? void 0 : field.kind) === "radio" &&
613
+ field.required &&
614
+ (draft[field.key] === null || draft[field.key] === ""))) {
615
+ return false;
616
+ }
617
+ }
588
618
  const rpcId = createNativeInteractionRpcId();
589
619
  const currentProjection = entry.nativeProjection;
590
- if (((_b = entry.nativeInteraction) === null || _b === void 0 ? void 0 : _b.kind) === "draft")
620
+ if (intent !== "draft")
621
+ entry.nativeRadioProjections = new WeakSet();
622
+ if (((_e = entry.nativeInteraction) === null || _e === void 0 ? void 0 : _e.kind) === "draft")
591
623
  clearTimeout(entry.nativeInteraction.timeout);
592
624
  // Match the ordinary RPC budget. Correlation already rejects late replies;
593
625
  // Timeout or a failed reply restores editing for an explicit manual retry.
@@ -601,7 +633,8 @@ export function createNativeViewRecordController(input) {
601
633
  clearTimeout(entry.nativeInteraction.timeout);
602
634
  entry.nativeInteraction =
603
635
  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."] });
636
+ entry.nativeProjection = Object.assign(Object.assign({}, currentProjection), { errors: resolveNativeRadioErrors(currentProjection, parsed.data.draft), answers: Object.assign(Object.assign({}, currentProjection.answers), parsed.data.draft), formErrors: ["Something went wrong. Please try again."] });
637
+ entry.nativeRadioProjections = new WeakSet([entry.nativeProjection]);
605
638
  notify();
606
639
  };
607
640
  entry.nativeInteraction = {
@@ -38,7 +38,10 @@ export function resolveNativeWidgetBoxShadowSupport() {
38
38
  const reactNativeModule = require("react-native");
39
39
  // React Native exposes renderer capability to JavaScript process-wide.
40
40
  // There is no stable public API for querying the renderer of one View.
41
- const fabricUIManager = Reflect.get(globalThis, "nativeFabricUIManager");
41
+ const runtimeGlobal = globalThis;
42
+ const fabricUIManager = "nativeFabricUIManager" in runtimeGlobal
43
+ ? runtimeGlobal.nativeFabricUIManager
44
+ : undefined;
42
45
  return toNativeWidgetBoxShadowSupport({
43
46
  hasFabricUIManager: fabricUIManager !== undefined && fabricUIManager !== null,
44
47
  platform: {
@@ -1,5 +1,5 @@
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
+ import { NATIVE_FLOW_RADIO_DECISIONS_PROTOCOL, NATIVE_FLOW_SESSION_PROTOCOL, } from "@getuserfeedback/protocol/internal/native-flow-session";
3
3
  import { REACT_NATIVE_SDK_VERSION } from "./version.js";
4
4
  export const REACT_NATIVE_CLIENT_META = {
5
5
  clientName: "@getuserfeedback/react-native",
@@ -7,6 +7,7 @@ export const REACT_NATIVE_CLIENT_META = {
7
7
  loader: "sdk",
8
8
  protocolCapabilities: [
9
9
  NATIVE_FLOW_SESSION_PROTOCOL,
10
+ NATIVE_FLOW_RADIO_DECISIONS_PROTOCOL,
10
11
  REACT_NATIVE_WEBVIEW_TRANSPORT_CAPABILITIES.LAYOUT_TRANSITION_POLICY,
11
12
  REACT_NATIVE_WEBVIEW_TRANSPORT_CAPABILITIES.LAYOUT_TRANSITION_SCHEDULE,
12
13
  ],
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.15.3";
6
+ export const REACT_NATIVE_SDK_VERSION = reactNativeSdkVersion.length > 0 ? reactNativeSdkVersion : "5.15.12";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getuserfeedback/react-native",
3
- "version": "5.15.3",
3
+ "version": "5.15.12",
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.3",
58
- "@getuserfeedback/sdk": "^0.19.4",
57
+ "@getuserfeedback/protocol": "^6.1.1",
58
+ "@getuserfeedback/sdk": "^0.19.12",
59
59
  "robot3": "^1.2.0"
60
60
  },
61
61
  "peerDependencies": {