@getuserfeedback/react-native 5.3.3 → 5.4.0

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
- import type { CapabilitiesInput, ConfigureOptions, EventOptions, EventProperties, GraphOperations, InitOptions } from "@getuserfeedback/sdk";
3
- export type { EventOptions, EventProperties, TrackOptions, } from "@getuserfeedback/sdk";
2
+ import type { CapabilitiesInput, ConfigureOptions, EventOptions, EventProperties, GraphOperations, InitOptions, OpenRequestedCallback } from "@getuserfeedback/sdk";
3
+ export type { EventOptions, EventProperties, OpenRequestedCallback, OpenRequestedEvent, TrackOptions, } from "@getuserfeedback/sdk";
4
4
  type IdentifyTraits = Record<string, unknown>;
5
5
  type OpenCommand = Extract<PublicCommandPayload, {
6
6
  kind: "open";
@@ -65,6 +65,22 @@ export interface Client {
65
65
  flow: (flowId: string) => FlowRun;
66
66
  /** Closes every open flow owned by this provider. */
67
67
  close: () => Promise<void>;
68
+ /**
69
+ * Subscribe to explicit SDK and client-side targeting open requests, or
70
+ * suppress them, for example when the user should not be interrupted.
71
+ * Prevention applies only to that presentation attempt; it does not create a
72
+ * public retry schedule.
73
+ *
74
+ * @example Suppress open requests while the user is busy
75
+ * ```ts
76
+ * client.onOpenRequested((event) => {
77
+ * if (userIsBusy()) {
78
+ * event.preventDefault();
79
+ * }
80
+ * });
81
+ * ```
82
+ */
83
+ onOpenRequested: (callback: OpenRequestedCallback) => () => void;
68
84
  /** Resets widget state, authentication, and the current user identity. */
69
85
  reset: (options?: CommandOptions) => Promise<void>;
70
86
  /** Updates app navigation and environment context used by targeting. */
@@ -1,4 +1,4 @@
1
- import { CORE_HOST_BOOTSTRAP_GLOBAL_KEY, CORE_HOST_BOOTSTRAP_PROTOCOL_VERSION, CORE_HOST_BOOTSTRAP_READY_EVENT_NAME, CORE_HOST_BOOTSTRAP_REQUIRED_FRAGMENT, } from "@getuserfeedback/protocol/internal/core-host-bootstrap";
1
+ import { CORE_HOST_BOOTSTRAP_GLOBAL_KEY, CORE_HOST_BOOTSTRAP_PROTOCOL_VERSION, CORE_HOST_BOOTSTRAP_READY_EVENT_NAME, CORE_HOST_BOOTSTRAP_REQUIRED_FRAGMENT, } from "@getuserfeedback/protocol/internal/core-host-bootstrap-v3";
2
2
  import { parseWebViewConnectionBridgeMessage, } from "./webview-connection-bridge-message.js";
3
3
  import { buildWebViewConnectionBridgeScript, } from "./webview-connection-bridge-script.js";
4
4
  import { buildWebViewConnectionHostMessageScript, serializeForJavaScript, } from "./webview-connection-host-message-script.js";
@@ -1,5 +1,6 @@
1
1
  import type { EventHandleInvalidated } from "@getuserfeedback/protocol/internal/core-handle-invalidation";
2
2
  import type { ConfigureOptions } from "@getuserfeedback/sdk";
3
+ import { type OpenRequestDispatcher } from "@getuserfeedback/sdk/internal/open-request-dispatcher";
3
4
  export declare function createNativeProviderClientController(input: {
4
5
  instanceId: string;
5
6
  onError?: (error: Error) => void;
@@ -7,6 +8,7 @@ export declare function createNativeProviderClientController(input: {
7
8
  client: import("./client.js").Client;
8
9
  directSessionController: import("./native-direct-command-session-controller.js").NativeDirectCommandSessionController;
9
10
  getRuntimeConfiguration: () => ConfigureOptions;
11
+ observeOpenRequest: (payload: Parameters<OpenRequestDispatcher["dispatch"]>[0]) => ReturnType<OpenRequestDispatcher["dispatch"]>;
10
12
  dispose: () => void;
11
13
  onHandleInvalidated: (message: EventHandleInvalidated) => void;
12
14
  onSessionEnded: () => void;
@@ -1,4 +1,5 @@
1
1
  import { parsePublicCommand } from "@getuserfeedback/protocol";
2
+ import { createOpenRequestDispatcher, } from "@getuserfeedback/sdk/internal/open-request-dispatcher";
2
3
  import { createNativeProviderClient, parseRequiredString, } from "./native-provider-client.js";
3
4
  import { createNativeProviderCommandController } from "./native-provider-command-controller.js";
4
5
  import { createNativeProviderFlowRegistry } from "./native-provider-flow-registry.js";
@@ -14,6 +15,19 @@ export function createNativeProviderClientController(input) {
14
15
  const commands = createNativeProviderCommandController(input);
15
16
  const flows = createNativeProviderFlowRegistry(commands);
16
17
  const session = commands.directSessionController;
18
+ const openRequestDispatcher = createOpenRequestDispatcher();
19
+ const reportOpenRequestObserverError = (error) => {
20
+ var _a;
21
+ try {
22
+ (_a = input.onError) === null || _a === void 0 ? void 0 : _a.call(input, error instanceof Error ? error : new Error(String(error)));
23
+ }
24
+ catch (_b) {
25
+ // Error observers cannot become open-request authority.
26
+ }
27
+ };
28
+ const observeOpenRequest = (payload) => openRequestDispatcher.dispatch(payload, {
29
+ onListenerError: reportOpenRequestObserverError,
30
+ });
17
31
  const client = createNativeProviderClient({
18
32
  dispatchCommand: async (command, options) => {
19
33
  if (command.kind === "close" || command.kind === "reset") {
@@ -37,11 +51,13 @@ export function createNativeProviderClientController(input) {
37
51
  },
38
52
  flow: (flowId) => flows.flow(parseRequiredString("flowId", flowId)),
39
53
  instanceId: input.instanceId,
54
+ openRequestDispatcher,
40
55
  });
41
56
  return {
42
57
  client,
43
58
  directSessionController: session,
44
59
  getRuntimeConfiguration: () => (Object.assign({}, runtimeConfiguration)),
60
+ observeOpenRequest,
45
61
  dispose: () => {
46
62
  flows.reset();
47
63
  commands.dispose();
@@ -1,4 +1,5 @@
1
1
  import type { PublicCommandPayload } from "@getuserfeedback/protocol";
2
+ import { type OpenRequestDispatcher } from "@getuserfeedback/sdk/internal/open-request-dispatcher";
2
3
  import type { Client } from "./client.js";
3
4
  type NativeProviderClientCommand = Extract<PublicCommandPayload, {
4
5
  kind: "configure" | "identify" | "track" | "close" | "reset" | "updateHostContext" | "emitHostSignal";
@@ -8,7 +9,8 @@ type NativeProviderClientOptions = {
8
9
  dispatchCommand: (command: NativeProviderClientCommand, options?: NativeProviderCommandOptions) => Promise<void>;
9
10
  flow: Client["flow"];
10
11
  instanceId: string;
12
+ openRequestDispatcher?: OpenRequestDispatcher;
11
13
  };
12
14
  export declare const parseRequiredString: (name: string, value?: string) => string;
13
- export declare function createNativeProviderClient({ dispatchCommand, flow, instanceId, }: NativeProviderClientOptions): Client;
15
+ export declare function createNativeProviderClient({ dispatchCommand, flow, instanceId, openRequestDispatcher, }: NativeProviderClientOptions): Client;
14
16
  export {};
@@ -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 { createOpenRequestDispatcher, } from "@getuserfeedback/sdk/internal/open-request-dispatcher";
3
4
  export const parseRequiredString = (name, value) => {
4
5
  const normalized = value === null || value === void 0 ? void 0 : value.trim();
5
6
  if (!normalized) {
@@ -34,7 +35,7 @@ const toIdentifyCommandPayload = (identifyInput, traitsOrOptions, options) => {
34
35
  options: options !== null && options !== void 0 ? options : traitsOrOptions,
35
36
  };
36
37
  };
37
- export function createNativeProviderClient({ dispatchCommand, flow, instanceId, }) {
38
+ export function createNativeProviderClient({ dispatchCommand, flow, instanceId, openRequestDispatcher = createOpenRequestDispatcher(), }) {
38
39
  const track = (eventName, properties, options) => {
39
40
  return dispatchCommand(buildCustomerOccurrenceCommand({
40
41
  type: "track",
@@ -61,6 +62,7 @@ export function createNativeProviderClient({ dispatchCommand, flow, instanceId,
61
62
  },
62
63
  flow,
63
64
  close: () => dispatchCommand({ kind: "close" }),
65
+ onOpenRequested: openRequestDispatcher.subscribe,
64
66
  reset: (options) => dispatchCommand({ kind: "reset" }, options),
65
67
  updateHostContext: (context, options) => dispatchCommand({ kind: "updateHostContext", context }, options),
66
68
  emitHostSignal: (name, data, options) => dispatchCommand(Object.assign({ kind: "emitHostSignal", name: parseRequiredString("name", name) }, (data === undefined ? {} : { data })), options),
@@ -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 { Client, ClientOptions, EventOptions, EventProperties, FlowRun, TrackOptions, } from "./client.js";
3
+ export type { Client, ClientOptions, EventOptions, EventProperties, FlowRun, OpenRequestedCallback, OpenRequestedEvent, TrackOptions, } from "./client.js";
4
4
  export type { UseFlowContainerReturn } from "./flow-container.js";
5
5
  export { FlowContent, useFlowContainer } from "./flow-container.js";
6
6
  /**
@@ -146,6 +146,7 @@ export function GetUserFeedbackProvider({ children, clientOptions, instanceId: i
146
146
  onCommandSessionChange: handleCommandSessionChange,
147
147
  onError: reportError,
148
148
  onHandleInvalidated: handleHandleInvalidated,
149
+ observeOpenRequest: controller.observeOpenRequest,
149
150
  viewWebViewComponent: webViewComponent,
150
151
  }))));
151
152
  }
@@ -5,4 +5,4 @@
5
5
  * docs/specs/2026-08-02-flow-interaction-model.md (Projection protocol and
6
6
  * rollout).
7
7
  */
8
- export declare const REACT_NATIVE_PINNED_CORE_URL: "https://cdn.getuserfeedback.com/widget/core/v3/core.html";
8
+ export declare const REACT_NATIVE_PINNED_CORE_URL: "https://cdn.getuserfeedback.com/widget/core/v6/core.html";
@@ -5,4 +5,4 @@
5
5
  * docs/specs/2026-08-02-flow-interaction-model.md (Projection protocol and
6
6
  * rollout).
7
7
  */
8
- export const REACT_NATIVE_PINNED_CORE_URL = "https://cdn.getuserfeedback.com/widget/core/v3/core.html";
8
+ export const REACT_NATIVE_PINNED_CORE_URL = "https://cdn.getuserfeedback.com/widget/core/v6/core.html";
@@ -1,6 +1,7 @@
1
1
  import type { CoreCommandEnvelope } from "@getuserfeedback/protocol";
2
2
  import type { EventHandleInvalidated } from "@getuserfeedback/protocol/internal/core-handle-invalidation";
3
3
  import type { ConfigureOptions, InitOptions } from "@getuserfeedback/sdk";
4
+ import type { OpenRequestDispatcher } from "@getuserfeedback/sdk/internal/open-request-dispatcher";
4
5
  import { type ReactElement } from "react";
5
6
  import { type CoreWebViewHostProps } from "./core-webview-host.js";
6
7
  import { type NativeCoreCommandSession } from "./native-core-command-session.js";
@@ -15,6 +16,7 @@ export type NativeRuntimeRootProps = {
15
16
  onError?: (error: Error) => void;
16
17
  onHandleInvalidated?: (message: EventHandleInvalidated) => void;
17
18
  onInvalidMessage?: (error: Error, value: unknown) => void;
19
+ observeOpenRequest?: (payload: Parameters<OpenRequestDispatcher["dispatch"]>[0]) => ReturnType<OpenRequestDispatcher["dispatch"]>;
18
20
  onStartupTerminalError?: (error: Error) => void;
19
21
  startupCommandSettlementTimeoutMs?: number;
20
22
  attachViewProjection?: (projection: NativeViewProjection) => () => void;
@@ -34,6 +36,6 @@ type NativeProviderRuntimeProps = Omit<NativeRuntimeRootProps, "createStartupCom
34
36
  getRuntimeConfiguration?: () => ConfigureOptions;
35
37
  onCommandSessionChange?: (session: NativeDirectCommandSession | null) => void;
36
38
  };
37
- export declare function NativeRuntimeRoot({ attachViewProjection, coreUrl, coreWebViewComponent, createStartupCommand, onCommandSessionChange, onError, onHandleInvalidated, onInvalidMessage, onStartupTerminalError, startupCommandSettlementTimeoutMs, viewNativeViewComponent, viewWebViewComponent, }: NativeRuntimeRootProps): ReactElement;
39
+ export declare function NativeRuntimeRoot({ attachViewProjection, coreUrl, coreWebViewComponent, createStartupCommand, onCommandSessionChange, onError, onHandleInvalidated, onInvalidMessage, observeOpenRequest, onStartupTerminalError, startupCommandSettlementTimeoutMs, viewNativeViewComponent, viewWebViewComponent, }: NativeRuntimeRootProps): ReactElement;
38
40
  export declare function NativeProviderRuntime({ directSessionController, getRuntimeConfiguration, initOptions, instanceId, onCommandSessionChange, onError, ...runtimeProps }: NativeProviderRuntimeProps): ReactElement;
39
41
  export {};
@@ -20,7 +20,7 @@ import { createNativeViewRecordController, } from "./native-view-record-controll
20
20
  import { createProviderCommandEnvelope, REACT_NATIVE_CLIENT_META, } from "./provider-command-helpers.js";
21
21
  const DEFAULT_STARTUP_COMMAND_SETTLEMENT_TIMEOUT_MS = 30000;
22
22
  const getEmptyRuntimeConfiguration = () => ({});
23
- export function NativeRuntimeRoot({ attachViewProjection, coreUrl, coreWebViewComponent, createStartupCommand, onCommandSessionChange, onError, onHandleInvalidated, onInvalidMessage, onStartupTerminalError, startupCommandSettlementTimeoutMs = DEFAULT_STARTUP_COMMAND_SETTLEMENT_TIMEOUT_MS, viewNativeViewComponent, viewWebViewComponent, }) {
23
+ export function NativeRuntimeRoot({ attachViewProjection, coreUrl, coreWebViewComponent, createStartupCommand, onCommandSessionChange, onError, onHandleInvalidated, onInvalidMessage, observeOpenRequest, onStartupTerminalError, startupCommandSettlementTimeoutMs = DEFAULT_STARTUP_COMMAND_SETTLEMENT_TIMEOUT_MS, viewNativeViewComponent, viewWebViewComponent, }) {
24
24
  const [coreHostRevision, setCoreHostRevision] = useState(0);
25
25
  const [session, setSession] = useState(null);
26
26
  const sessionRef = useRef(null);
@@ -34,6 +34,8 @@ export function NativeRuntimeRoot({ attachViewProjection, coreUrl, coreWebViewCo
34
34
  onHandleInvalidatedRef.current = onHandleInvalidated;
35
35
  const onInvalidMessageRef = useRef(onInvalidMessage);
36
36
  onInvalidMessageRef.current = onInvalidMessage;
37
+ const observeOpenRequestRef = useRef(observeOpenRequest);
38
+ observeOpenRequestRef.current = observeOpenRequest;
37
39
  const onStartupTerminalErrorRef = useRef(onStartupTerminalError);
38
40
  onStartupTerminalErrorRef.current = onStartupTerminalError;
39
41
  const createStartupCommandRef = useRef(createStartupCommand);
@@ -81,6 +83,7 @@ export function NativeRuntimeRoot({ attachViewProjection, coreUrl, coreWebViewCo
81
83
  const controller = createNativeViewRecordController({
82
84
  coreConnection,
83
85
  onError: reportError,
86
+ observeOpenRequest: (payload) => { var _a, _b; return (_b = (_a = observeOpenRequestRef.current) === null || _a === void 0 ? void 0 : _a.call(observeOpenRequestRef, payload)) !== null && _b !== void 0 ? _b : { allowed: true }; },
84
87
  });
85
88
  const commandChannelOwner = createNativeCoreCommandChannel({
86
89
  coreConnection,
@@ -1,4 +1,5 @@
1
1
  import { type EventViewSize } from "@getuserfeedback/protocol/internal/view-host-control-messages";
2
+ import type { OpenRequestDispatcher } from "@getuserfeedback/sdk/internal/open-request-dispatcher";
2
3
  import type { CoreWebViewHostConnection } from "./core-webview-host-controller.js";
3
4
  import type { ViewWebViewHostConnection } from "./view-webview-host-controller.js";
4
5
  type NativeViewSizeReport = Omit<EventViewSize, "gen" | "t" | "viewId">;
@@ -35,8 +36,10 @@ export type NativeViewRecordController = {
35
36
  getSnapshot(): readonly NativeViewRecord[];
36
37
  subscribe(listener: () => void): () => void;
37
38
  };
39
+ type NativeOpenRequestObserver = (payload: Parameters<OpenRequestDispatcher["dispatch"]>[0]) => ReturnType<OpenRequestDispatcher["dispatch"]>;
38
40
  export declare function createNativeViewRecordController(input: {
39
41
  coreConnection: CoreWebViewHostConnection;
40
42
  onError: (error: Error) => void;
43
+ observeOpenRequest?: NativeOpenRequestObserver;
41
44
  }): NativeViewRecordController;
42
45
  export {};
@@ -1,6 +1,6 @@
1
- import { coreViewHostActionSchema, } from "@getuserfeedback/protocol/internal/core-view-host-actions";
1
+ import { coreViewHostActionSchema, } from "@getuserfeedback/protocol/internal/core-view-host-actions-v3";
2
2
  import { actionRequestCloseViewSchema, actionViewConnectSchema, buildEventLayoutTransactionCommitted, buildEventViewSize, eventViewSizeSchema, } from "@getuserfeedback/protocol/internal/view-host-control-messages";
3
- import { buildEventViewActivated, buildEventViewAllocationFailed, buildEventViewClosed, buildEventViewCloseFailed, buildEventViewPrerendered, } from "@getuserfeedback/protocol/internal/view-host-lifecycle-events";
3
+ import { buildEventViewActivated, buildEventViewActivationDecided, buildEventViewAllocationFailed, buildEventViewClosed, buildEventViewCloseFailed, buildEventViewPrerendered, } from "@getuserfeedback/protocol/internal/view-host-lifecycle-events-v3";
4
4
  import { bindViewHostControlEnvelope, bindViewToCoreRelayEnvelope, parseCoreToViewRelayEnvelope, } from "@getuserfeedback/protocol/internal/view-relay-contract";
5
5
  import { createViewPreparationReadinessRegistry, createViewPreparationRegistry, } from "./view-orchestration-runtime.js";
6
6
  const createNativeViewReadinessRegistry = () => createViewPreparationReadinessRegistry();
@@ -157,6 +157,21 @@ export function createNativeViewRecordController(input) {
157
157
  failCoreConnection(error, "Native view close failure delivery failed");
158
158
  }
159
159
  };
160
+ const postViewActivationDecision = (action, allowed) => {
161
+ try {
162
+ input.coreConnection.transport.postMessage(buildEventViewActivationDecided({
163
+ gen: action.gen,
164
+ viewId: action.viewId,
165
+ activationAttemptId: action.activationAttemptId,
166
+ allowed,
167
+ }));
168
+ return true;
169
+ }
170
+ catch (error) {
171
+ failCoreConnection(error, "Native view activation decision delivery failed");
172
+ return false;
173
+ }
174
+ };
160
175
  const failAllocation = (entry, allocationError) => {
161
176
  const resourceErrors = [];
162
177
  const observerErrors = [];
@@ -255,7 +270,9 @@ export function createNativeViewRecordController(input) {
255
270
  failAllocation(entry, error);
256
271
  }
257
272
  };
273
+ // Governing contract: docs/specs/2026-08-29-sdk-open-request-interception.md.
258
274
  const handleActivate = (action) => {
275
+ var _a;
259
276
  const entry = entries.get(action.viewId);
260
277
  if (!entry ||
261
278
  entry.instanceId !== action.instanceId ||
@@ -263,6 +280,27 @@ export function createNativeViewRecordController(input) {
263
280
  failCoreConnection(new Error(`Unknown native projection binding ${action.viewId}`), "Unknown native projection binding");
264
281
  return;
265
282
  }
283
+ let allowed = true;
284
+ if (((_a = action.openRequest) === null || _a === void 0 ? void 0 : _a.flowId) && input.observeOpenRequest) {
285
+ const payload = Object.assign({ instanceId: action.instanceId, source: action.openRequest.source, flowId: action.openRequest.flowId }, (action.openRequest.source === "command"
286
+ ? {
287
+ flowHandleId: action.openRequest.flowHandleId,
288
+ hideCloseButton: action.openRequest.hideCloseButton,
289
+ }
290
+ : {}));
291
+ try {
292
+ allowed = input.observeOpenRequest(payload).allowed;
293
+ }
294
+ catch (error) {
295
+ reportError(error, "Native open request observer failed");
296
+ }
297
+ }
298
+ if (!postViewActivationDecision(action, allowed)) {
299
+ return;
300
+ }
301
+ if (!allowed) {
302
+ return;
303
+ }
266
304
  if (!entry.isActive) {
267
305
  entry.isActive = true;
268
306
  notify();
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.3.3";
6
+ export const REACT_NATIVE_SDK_VERSION = reactNativeSdkVersion.length > 0 ? reactNativeSdkVersion : "5.4.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getuserfeedback/react-native",
3
- "version": "5.3.3",
3
+ "version": "5.4.0",
4
4
  "description": "getuserfeedback React Native SDK",
5
5
  "keywords": [
6
6
  "getuserfeedback",
@@ -44,8 +44,8 @@
44
44
  "lint": "biome check ."
45
45
  },
46
46
  "dependencies": {
47
- "@getuserfeedback/protocol": "^5.3.1",
48
- "@getuserfeedback/sdk": "^0.16.0",
47
+ "@getuserfeedback/protocol": "^5.4.0",
48
+ "@getuserfeedback/sdk": "^0.16.2",
49
49
  "robot3": "^1.2.0"
50
50
  },
51
51
  "peerDependencies": {