@getuserfeedback/react-native 4.0.31 → 5.0.6

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.
Files changed (66) hide show
  1. package/README.md +12 -150
  2. package/dist/client.d.ts +64 -0
  3. package/dist/client.js +1 -0
  4. package/dist/core-webview-host.d.ts +0 -1
  5. package/dist/core-webview-host.js +2 -12
  6. package/dist/native-core-command-channel.d.ts +1 -0
  7. package/dist/native-core-command-channel.js +20 -4
  8. package/dist/native-provider-client-controller.d.ts +11 -0
  9. package/dist/native-provider-client-controller.js +38 -0
  10. package/dist/native-provider-client.d.ts +3 -17
  11. package/dist/native-provider-command-controller.d.ts +19 -0
  12. package/dist/native-provider-command-controller.js +79 -0
  13. package/dist/native-provider-flow-registry.d.ts +7 -0
  14. package/dist/native-provider-flow-registry.js +70 -0
  15. package/dist/native-provider.d.ts +32 -0
  16. package/dist/native-provider.js +88 -0
  17. package/dist/native-runtime-root.d.ts +2 -4
  18. package/dist/native-runtime-root.js +6 -3
  19. package/dist/native-view-host-projection.d.ts +1 -2
  20. package/dist/native-view-host-projection.js +22 -6
  21. package/dist/native-view-presentation-surface.js +6 -1
  22. package/dist/native-view-record-controller.d.ts +9 -3
  23. package/dist/native-view-record-controller.js +276 -50
  24. package/dist/native-widget-corner-radii.d.ts +1 -2
  25. package/dist/native-widget-corner-radii.js +1 -1
  26. package/dist/provider-command-helpers.d.ts +1 -11
  27. package/dist/provider-command-helpers.js +1 -38
  28. package/dist/public-api.d.ts +2 -1
  29. package/dist/public-api.js +2 -1
  30. package/dist/runtime-endpoints.d.ts +2 -0
  31. package/dist/runtime-endpoints.js +2 -0
  32. package/dist/version.js +1 -1
  33. package/dist/view-orchestration-runtime.js +11 -43
  34. package/dist/view-webview-host-controller.d.ts +1 -0
  35. package/dist/view-webview-host-controller.js +1 -0
  36. package/dist/view-webview-host.d.ts +1 -1
  37. package/dist/view-webview-host.js +3 -7
  38. package/dist/webview-document-host.d.ts +2 -3
  39. package/dist/webview-document-host.js +21 -33
  40. package/package.json +3 -3
  41. package/dist/host-viewport.d.ts +0 -3
  42. package/dist/host-viewport.js +0 -69
  43. package/dist/index.d.ts +0 -91
  44. package/dist/index.js +0 -613
  45. package/dist/provider-command-transport.d.ts +0 -52
  46. package/dist/provider-command-transport.js +0 -150
  47. package/dist/provider-message-queue.d.ts +0 -11
  48. package/dist/provider-message-queue.js +0 -23
  49. package/dist/provider-pending-commands.d.ts +0 -34
  50. package/dist/provider-pending-commands.js +0 -58
  51. package/dist/provider-widget-border-radius.d.ts +0 -11
  52. package/dist/provider-widget-border-radius.js +0 -102
  53. package/dist/provider-widget-frame-appearance.d.ts +0 -17
  54. package/dist/provider-widget-frame-appearance.js +0 -43
  55. package/dist/provider-widget-host-lifecycle.d.ts +0 -46
  56. package/dist/provider-widget-host-lifecycle.js +0 -138
  57. package/dist/provider-widget-host-overlay.d.ts +0 -58
  58. package/dist/provider-widget-host-overlay.js +0 -616
  59. package/dist/use-native-direct-command-session-controller.d.ts +0 -2
  60. package/dist/use-native-direct-command-session-controller.js +0 -21
  61. package/dist/webview-bridge-script.d.ts +0 -16
  62. package/dist/webview-bridge-script.js +0 -283
  63. package/dist/widget-host-lifecycle-machine.d.ts +0 -35
  64. package/dist/widget-host-lifecycle-machine.js +0 -37
  65. package/dist/widget-host.d.ts +0 -38
  66. package/dist/widget-host.js +0 -327
package/README.md CHANGED
@@ -1,170 +1,32 @@
1
1
  # @getuserfeedback/react-native
2
2
 
3
- React Native bindings for getuserfeedback.
3
+ In-product surveys for React Native apps, from [getuserfeedback.com](https://getuserfeedback.com).
4
4
 
5
- This package hosts the existing getuserfeedback WebView runtime from React Native and exposes a provider/hook API for enqueueing widget commands from non-ejected Expo or React Native apps.
5
+ ## Get started
6
6
 
7
- ## Install
7
+ Install the SDK and its WebView peer dependency:
8
8
 
9
9
  ```bash
10
10
  npm install @getuserfeedback/react-native react-native-webview
11
11
  ```
12
12
 
13
- Requires React, React Native, and `react-native-webview`.
14
-
15
- ## API
16
-
17
- - `GetUserFeedbackProvider`
18
- - `useGetUserFeedback`
19
- - `WidgetHost`
20
- - `useGetUserFeedbackNative`
13
+ Add the provider near the root of your app. You can find your Public API key in
14
+ [Widget settings](https://getuserfeedback.com/app/_/settings/widget).
21
15
 
22
16
  ```tsx
23
- import {
24
- GetUserFeedbackProvider,
25
- useGetUserFeedback,
26
- } from "@getuserfeedback/react-native";
27
-
28
- function FeedbackActions() {
29
- const feedback = useGetUserFeedback();
30
-
31
- return (
32
- <Button
33
- title="Send feedback"
34
- onPress={() => {
35
- void feedback.flow("flow_123").open();
36
- }}
37
- />
38
- );
39
- }
17
+ import { GetUserFeedbackProvider } from "@getuserfeedback/react-native";
40
18
 
41
19
  export function App() {
42
20
  return (
43
- <GetUserFeedbackProvider
44
- initOptions={{ apiKey: "YOUR_API_KEY" }}
45
- loaderUrl="https://cdn.example.com/loader.js"
46
- >
47
- <FeedbackActions />
21
+ <GetUserFeedbackProvider clientOptions={{ apiKey: "YOUR_API_KEY" }}>
22
+ <YourApp />
48
23
  </GetUserFeedbackProvider>
49
24
  );
50
25
  }
51
26
  ```
52
27
 
53
- `GetUserFeedbackProvider` renders `WidgetHost` internally, auto-enqueues `init`, measures the React Native window for the hosted WebView viewport, shows the hosted WebView inside a native bottom sheet while a flow is loading or open, and resolves command promises when the WebView transport reports command settlement. Pass either `loaderUrl` or a custom WebView `source` so the hosted WebView has a loader runtime to execute commands. Use `onCommandError` to receive automatic setup failures from `init` or `configure`.
54
-
55
- Custom `source` documents must implement the current WebView transport contract. Provider-owned presentation is driven by `instance:view-host:snapshots-changed`; the legacy aggregate `instance:flow:state-changed:instance` event does not control the native sheet.
56
-
57
- `WidgetHost` remains exported for advanced hosts that need direct WebView control.
58
-
59
- The host message types are re-exported from `@getuserfeedback/protocol/webview-transport` through package-owned aliases so the React Native package stays aligned with the shared WebView transport contract.
60
-
61
- ## Local Expo smoke test
62
-
63
- Use the repo sandbox when testing a non-ejected Expo app against the local
64
- loader/core/API stack:
65
-
66
- ```bash
67
- bun run start:sandbox
68
- ```
69
-
70
- Create a React Native manifest:
71
-
72
- ```bash
73
- curl -sS -X POST http://127.0.0.1:3710/api/sandbox/react-native-flow-assignment
74
- ```
75
-
76
- For a physical device, use an address the device can reach:
77
-
78
- ```bash
79
- curl -sS -X POST "http://127.0.0.1:3710/api/sandbox/react-native-flow-assignment?publicHost=192.168.1.10"
80
- ```
81
-
82
- Then pass `manifest.initOptions` and `manifest.loaderUrl` to the provider:
28
+ That's it. Flows configured for automatic Widget delivery now appear based on
29
+ the targeting rules you set in the dashboard.
83
30
 
84
- ```tsx
85
- import {
86
- type GetUserFeedbackProviderProps,
87
- GetUserFeedbackProvider,
88
- useGetUserFeedback,
89
- } from "@getuserfeedback/react-native";
90
- import { Button, View } from "react-native";
91
-
92
- const manifest = {
93
- // Paste the sandbox manifest while manually testing on device.
94
- initOptions: {
95
- apiKey: "gx_sandbox_...",
96
- defaultConsent: ["analytics.measurement", "analytics.storage"],
97
- disableTelemetry: true,
98
- enableDebug: true,
99
- runtimeEndpoints: {
100
- apiUrl: "http://192.168.1.10:3712/v1",
101
- coreUrl: "http://192.168.1.10:3711/widget/core/v1/core.html",
102
- },
103
- },
104
- loaderUrl: "http://192.168.1.10:3711/widget/loader/v1/gx_sandbox_.../loader.js",
105
- commands: {
106
- identify: {
107
- userId: "sandbox-user-...",
108
- traits: { email: "sandbox-user-...@example.test" },
109
- },
110
- open: { flowId: "sur_..." },
111
- track: {
112
- name: "Checkout Started",
113
- properties: { source: "react-native-sandbox" },
114
- },
115
- },
116
- } satisfies {
117
- commands: {
118
- identify: { traits: Record<string, unknown>; userId: string };
119
- open: { flowId: string };
120
- track: { name: string; properties: Record<string, unknown> };
121
- };
122
- initOptions: GetUserFeedbackProviderProps["initOptions"];
123
- loaderUrl: string;
124
- };
125
-
126
- function SandboxActions() {
127
- const feedback = useGetUserFeedback();
128
-
129
- return (
130
- <View>
131
- <Button
132
- title="Identify"
133
- onPress={() => {
134
- void feedback.identify(
135
- manifest.commands.identify.userId,
136
- manifest.commands.identify.traits,
137
- );
138
- }}
139
- />
140
- <Button
141
- title="Track"
142
- onPress={() => {
143
- void feedback.track(
144
- manifest.commands.track.name,
145
- manifest.commands.track.properties,
146
- );
147
- }}
148
- />
149
- <Button
150
- title="Open"
151
- onPress={() => {
152
- void feedback.flow(manifest.commands.open.flowId).open();
153
- }}
154
- />
155
- </View>
156
- );
157
- }
158
-
159
- export function App() {
160
- return (
161
- <GetUserFeedbackProvider
162
- initOptions={manifest.initOptions}
163
- loaderUrl={manifest.loaderUrl}
164
- onCommandError={console.error}
165
- >
166
- <SandboxActions />
167
- </GetUserFeedbackProvider>
168
- );
169
- }
170
- ```
31
+ For user identity, manual flow controls, and troubleshooting, read the
32
+ [React Native SDK guide](https://getuserfeedback.com/docs/get-started/react-native-sdk).
@@ -0,0 +1,64 @@
1
+ import type { AppEventExternalId, AppEventJsonValue, PublicCommandPayload } from "@getuserfeedback/protocol";
2
+ import type { ConfigureOptions, InitOptions } from "@getuserfeedback/sdk";
3
+ type IdentifyTraits = Record<string, unknown>;
4
+ type TrackProperties = Record<string, AppEventJsonValue>;
5
+ type OpenCommand = Extract<PublicCommandPayload, {
6
+ kind: "open";
7
+ }>;
8
+ type PrerenderCommand = Extract<PublicCommandPayload, {
9
+ kind: "prerender";
10
+ }>;
11
+ type IdentifyOptions = {
12
+ externalIds?: AppEventExternalId[];
13
+ };
14
+ type TrackOptions = {
15
+ externalIds?: AppEventExternalId[];
16
+ };
17
+ type OpenFlowOptions = Pick<OpenCommand, "hideCloseButton" | "metadata">;
18
+ type PrerenderFlowOptions = Pick<PrerenderCommand, "hideCloseButton">;
19
+ type HostContext = Extract<PublicCommandPayload, {
20
+ kind: "updateHostContext";
21
+ }>["context"];
22
+ type CommandOptions = {
23
+ idempotencyKey?: string;
24
+ };
25
+ /** React Native provider initialization options. */
26
+ export type ClientOptions = InitOptions;
27
+ /** Operations for one on-demand flow lifecycle. */
28
+ export interface FlowRun {
29
+ readonly flowId: string;
30
+ /** Opens the flow. The flow must be published with Widget / On-demand delivery. */
31
+ open: (options?: OpenFlowOptions) => Promise<void>;
32
+ /** Loads the flow's network resources before it opens. */
33
+ prefetch: () => Promise<void>;
34
+ /** Loads the flow and warms its UI before it opens. */
35
+ prerender: (options?: PrerenderFlowOptions) => Promise<void>;
36
+ /** Closes this flow lifecycle. */
37
+ close: () => Promise<void>;
38
+ }
39
+ /**
40
+ * Public client exposed by `useGetUserFeedback`.
41
+ *
42
+ * @see https://getuserfeedback.com/docs/get-started/react-native-sdk
43
+ */
44
+ export interface Client {
45
+ readonly instanceId: string;
46
+ /** Updates consent and other client configuration. */
47
+ configure: (opts: ConfigureOptions, options?: CommandOptions) => Promise<void>;
48
+ identify: {
49
+ (userId: string, traits?: IdentifyTraits, options?: IdentifyOptions): Promise<void>;
50
+ (traits: IdentifyTraits, placeholder: undefined, options?: IdentifyOptions): Promise<void>;
51
+ (traits: IdentifyTraits, options?: IdentifyOptions): Promise<void>;
52
+ };
53
+ /** Records a product event for targeting and analysis. */
54
+ track: (eventName: string, properties?: TrackProperties, options?: TrackOptions) => Promise<void>;
55
+ /** Returns the controller for one on-demand flow lifecycle. */
56
+ flow: (flowId: string) => FlowRun;
57
+ /** Closes every open flow owned by this provider. */
58
+ close: () => Promise<void>;
59
+ /** Resets widget state and the current user identity. */
60
+ reset: (options?: CommandOptions) => Promise<void>;
61
+ updateHostContext: (context: HostContext, options?: CommandOptions) => Promise<void>;
62
+ emitHostSignal: (name: string, data?: unknown, options?: CommandOptions) => Promise<void>;
63
+ }
64
+ export {};
package/dist/client.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -7,6 +7,5 @@ export type CoreWebViewHostProps = {
7
7
  onDisconnected?: () => void;
8
8
  onInvalidMessage?: (error: Error, value: unknown) => void;
9
9
  webViewComponent?: ComponentType<Record<string, unknown>>;
10
- webViewProps?: Record<string, unknown>;
11
10
  };
12
11
  export declare function CoreWebViewHost({ coreUrl, ...props }: CoreWebViewHostProps): ReactElement;
@@ -14,15 +14,7 @@ import { buildCoreWebViewBridgeScript, parseCoreWebViewBridgeMessage, toExternal
14
14
  import { createCoreWebViewHostController, } from "./core-webview-host-controller.js";
15
15
  import { WebViewDocumentHost } from "./webview-document-host.js";
16
16
  let nextBridgeId = 0;
17
- const CORE_WEBVIEW_HOST_STYLE = {
18
- height: 1,
19
- left: -10000,
20
- opacity: 0,
21
- position: "absolute",
22
- top: 0,
23
- width: 1,
24
- };
25
- function CoreWebViewHostDocument({ externalCoreUrl, onConnected, onActivationRetryExhausted, onDisconnected, onInvalidMessage, webViewComponent, webViewProps, }) {
17
+ function CoreWebViewHostDocument({ externalCoreUrl, onConnected, onActivationRetryExhausted, onDisconnected, onInvalidMessage, webViewComponent, }) {
26
18
  const bridgeIdRef = useRef(null);
27
19
  if (bridgeIdRef.current === null) {
28
20
  nextBridgeId += 1;
@@ -55,15 +47,13 @@ function CoreWebViewHostDocument({ externalCoreUrl, onConnected, onActivationRet
55
47
  const controller = controllerRef.current;
56
48
  const buildDocumentStartScript = useMemo(() => () => buildCoreWebViewBridgeScript({ bridgeId, phase: "documentStart" }), [bridgeId]);
57
49
  const source = useMemo(() => ({ uri: externalCoreUrl }), [externalCoreUrl]);
58
- const resolvedWebViewProps = useMemo(() => (Object.assign(Object.assign({}, webViewProps), { containerStyle: CORE_WEBVIEW_HOST_STYLE, pointerEvents: "none", style: CORE_WEBVIEW_HOST_STYLE })), [webViewProps]);
59
50
  return createElement((WebViewDocumentHost), {
60
- bridgeId,
61
51
  buildDocumentStartScript,
62
52
  controller,
63
53
  parseMessage: parseCoreWebViewBridgeMessage,
64
54
  source,
65
55
  webViewComponent,
66
- webViewProps: resolvedWebViewProps,
56
+ webViewRole: "core",
67
57
  onInvalidMessage,
68
58
  });
69
59
  }
@@ -14,5 +14,6 @@ export declare function createNativeCoreCommandChannel(input: {
14
14
  coreConnection: CoreWebViewHostConnection;
15
15
  onHandleInvalidated?: (message: EventHandleInvalidated) => void;
16
16
  onInvalidMessage?: (error: Error, value: unknown) => void;
17
+ onSessionFailure?: (error: Error) => void;
17
18
  }): OwnedNativeCoreCommandChannel;
18
19
  export {};
@@ -1,20 +1,36 @@
1
1
  import { coreCommandEnvelopeSchema, } from "@getuserfeedback/protocol";
2
2
  import { eventHandleInvalidatedSchema, } from "@getuserfeedback/protocol/internal/core-handle-invalidation";
3
3
  import { eventCommandSettledSchema, } from "@getuserfeedback/protocol/internal/core-host-command-settlement";
4
+ import { CORE_SESSION_FAILED_CODE, eventCoreSessionFailedSchema, } from "@getuserfeedback/protocol/internal/core-session-failure";
4
5
  const DISPOSED_MESSAGE = "Core command channel is disposed";
5
6
  const isRecord = (value) => typeof value === "object" && value !== null;
6
7
  export function createNativeCoreCommandChannel(input) {
7
8
  const listeners = new Set();
8
9
  let disposed = false;
9
10
  const handleMessage = (value) => {
10
- var _a, _b, _c;
11
+ var _a, _b, _c, _d, _e;
11
12
  if (disposed || !isRecord(value)) {
12
13
  return;
13
14
  }
15
+ if (value.t === "getuserfeedback:event:error" &&
16
+ value.code === CORE_SESSION_FAILED_CODE) {
17
+ const parsed = eventCoreSessionFailedSchema.safeParse(value);
18
+ if (!parsed.success) {
19
+ (_a = input.onInvalidMessage) === null || _a === void 0 ? void 0 : _a.call(input, parsed.error instanceof Error
20
+ ? parsed.error
21
+ : new Error("Invalid Core session failure message"), value);
22
+ return;
23
+ }
24
+ if (parsed.data.gen !== input.coreConnection.generation) {
25
+ return;
26
+ }
27
+ (_b = input.onSessionFailure) === null || _b === void 0 ? void 0 : _b.call(input, new Error(parsed.data.details.message));
28
+ return;
29
+ }
14
30
  if (value.t === "getuserfeedback:event:commandSettled") {
15
31
  const parsed = eventCommandSettledSchema.safeParse(value);
16
32
  if (!parsed.success) {
17
- (_a = input.onInvalidMessage) === null || _a === void 0 ? void 0 : _a.call(input, parsed.error instanceof Error
33
+ (_c = input.onInvalidMessage) === null || _c === void 0 ? void 0 : _c.call(input, parsed.error instanceof Error
18
34
  ? parsed.error
19
35
  : new Error("Invalid core command settlement message"), value);
20
36
  return;
@@ -32,7 +48,7 @@ export function createNativeCoreCommandChannel(input) {
32
48
  }
33
49
  const parsed = eventHandleInvalidatedSchema.safeParse(value);
34
50
  if (!parsed.success) {
35
- (_b = input.onInvalidMessage) === null || _b === void 0 ? void 0 : _b.call(input, parsed.error instanceof Error
51
+ (_d = input.onInvalidMessage) === null || _d === void 0 ? void 0 : _d.call(input, parsed.error instanceof Error
36
52
  ? parsed.error
37
53
  : new Error("Invalid core handle invalidation message"), value);
38
54
  return;
@@ -40,7 +56,7 @@ export function createNativeCoreCommandChannel(input) {
40
56
  if (parsed.data.gen !== input.coreConnection.generation) {
41
57
  return;
42
58
  }
43
- (_c = input.onHandleInvalidated) === null || _c === void 0 ? void 0 : _c.call(input, parsed.data);
59
+ (_e = input.onHandleInvalidated) === null || _e === void 0 ? void 0 : _e.call(input, parsed.data);
44
60
  };
45
61
  const unsubscribeTransport = input.coreConnection.transport.subscribe(handleMessage);
46
62
  const channel = {
@@ -0,0 +1,11 @@
1
+ import type { EventHandleInvalidated } from "@getuserfeedback/protocol/internal/core-handle-invalidation";
2
+ export declare function createNativeProviderClientController(input: {
3
+ instanceId: string;
4
+ onError?: (error: Error) => void;
5
+ }): {
6
+ client: import("./native-provider-client.js").NativeProviderClient<void>;
7
+ directSessionController: import("./native-direct-command-session-controller.js").NativeDirectCommandSessionController;
8
+ dispose: () => void;
9
+ onHandleInvalidated: (message: EventHandleInvalidated) => void;
10
+ onSessionEnded: () => void;
11
+ };
@@ -0,0 +1,38 @@
1
+ import { createNativeProviderClient, parseRequiredString, } from "./native-provider-client.js";
2
+ import { createNativeProviderCommandController } from "./native-provider-command-controller.js";
3
+ import { createNativeProviderFlowRegistry } from "./native-provider-flow-registry.js";
4
+ export function createNativeProviderClientController(input) {
5
+ const commands = createNativeProviderCommandController(input);
6
+ const flows = createNativeProviderFlowRegistry(commands);
7
+ const session = commands.directSessionController;
8
+ const client = createNativeProviderClient({
9
+ dispatchCommand: (command, options) => {
10
+ if (command.kind === "close" || command.kind === "reset") {
11
+ return commands.runBarrier(command.kind, flows.reset, options);
12
+ }
13
+ return commands
14
+ .runOperation((dispatch) => dispatch(command), options)
15
+ .then(() => undefined);
16
+ },
17
+ flow: (flowId) => flows.flow(parseRequiredString("flowId", flowId)),
18
+ instanceId: input.instanceId,
19
+ });
20
+ return {
21
+ client,
22
+ directSessionController: session,
23
+ dispose: () => {
24
+ flows.reset();
25
+ commands.dispose();
26
+ },
27
+ onHandleInvalidated: (message) => {
28
+ if (message.instanceId === input.instanceId &&
29
+ message.handleKind === "flow")
30
+ flows.invalidate(message.handleId);
31
+ },
32
+ onSessionEnded: () => {
33
+ session.rejectUntilAttached(new Error("Core session ended"));
34
+ void commands.runBarrier(null, flows.reset);
35
+ session.beginAttachmentAttempt();
36
+ },
37
+ };
38
+ }
@@ -1,13 +1,8 @@
1
1
  import type { AppEventExternalId, AppEventJsonValue, PublicCommandPayload } from "@getuserfeedback/protocol";
2
2
  import type { ConfigureOptions } from "@getuserfeedback/sdk";
3
+ import type { FlowRun } from "./client.js";
3
4
  type IdentifyTraits = Record<string, unknown>;
4
5
  type TrackProperties = Record<string, AppEventJsonValue>;
5
- type OpenCommand = Extract<PublicCommandPayload, {
6
- kind: "open";
7
- }>;
8
- type PrerenderCommand = Extract<PublicCommandPayload, {
9
- kind: "prerender";
10
- }>;
11
6
  type NativeProviderClientCommand = Extract<PublicCommandPayload, {
12
7
  kind: "configure" | "identify" | "track" | "close" | "reset" | "updateHostContext" | "emitHostSignal";
13
8
  }>;
@@ -17,21 +12,12 @@ type IdentifyOptions = {
17
12
  type TrackOptions = {
18
13
  externalIds?: AppEventExternalId[];
19
14
  };
20
- type OpenFlowOptions = Pick<OpenCommand, "hideCloseButton" | "metadata">;
21
- type PrerenderFlowOptions = Pick<PrerenderCommand, "hideCloseButton">;
22
15
  type HostContext = Extract<PublicCommandPayload, {
23
16
  kind: "updateHostContext";
24
17
  }>["context"];
25
18
  interface NativeProviderCommandOptions {
26
19
  idempotencyKey?: string;
27
20
  }
28
- export interface ReactNativeFlowRun {
29
- readonly flowId: string;
30
- open: (options?: OpenFlowOptions) => Promise<void>;
31
- prefetch: () => Promise<void>;
32
- prerender: (options?: PrerenderFlowOptions) => Promise<void>;
33
- close: () => Promise<void>;
34
- }
35
21
  export interface NativeProviderClient<TResult> {
36
22
  readonly instanceId: string;
37
23
  configure: (opts: ConfigureOptions, options?: NativeProviderCommandOptions) => Promise<TResult>;
@@ -41,7 +27,7 @@ export interface NativeProviderClient<TResult> {
41
27
  (traits: IdentifyTraits, options?: IdentifyOptions): Promise<TResult>;
42
28
  };
43
29
  track: (eventName: string, properties?: TrackProperties, options?: TrackOptions) => Promise<TResult>;
44
- flow: (flowId: string) => ReactNativeFlowRun;
30
+ flow: (flowId: string) => FlowRun;
45
31
  close: () => Promise<TResult>;
46
32
  reset: (options?: NativeProviderCommandOptions) => Promise<TResult>;
47
33
  updateHostContext: (context: HostContext, options?: NativeProviderCommandOptions) => Promise<TResult>;
@@ -49,7 +35,7 @@ export interface NativeProviderClient<TResult> {
49
35
  }
50
36
  type NativeProviderClientOptions<TResult> = {
51
37
  dispatchCommand: (command: NativeProviderClientCommand, options?: NativeProviderCommandOptions) => Promise<TResult>;
52
- flow: (flowId: string) => ReactNativeFlowRun;
38
+ flow: (flowId: string) => FlowRun;
53
39
  instanceId: string;
54
40
  };
55
41
  export declare const parseRequiredString: (name: string, value?: string) => string;
@@ -0,0 +1,19 @@
1
+ import type { CommandSettledSuccessDetail } from "@getuserfeedback/protocol/host";
2
+ import type { NativeDirectCommandEnvelope } from "./native-direct-command-session.js";
3
+ type Command = NativeDirectCommandEnvelope["command"];
4
+ type CommandOptions = {
5
+ idempotencyKey?: string;
6
+ };
7
+ type Dispatch = (command: Command) => Promise<CommandSettledSuccessDetail>;
8
+ type RunOperation = <T>(run: (dispatch: Dispatch) => Promise<T>, options?: CommandOptions) => Promise<T | undefined>;
9
+ export declare function createNativeProviderCommandController(input: {
10
+ instanceId: string;
11
+ onError?: (error: Error) => void;
12
+ }): {
13
+ directSessionController: import("./native-direct-command-session-controller.js").NativeDirectCommandSessionController;
14
+ dispose: () => void;
15
+ reject: (error: unknown) => Promise<never>;
16
+ runBarrier: (kind: "close" | "reset" | null, onGenerationAdvanced: () => void, options?: CommandOptions) => Promise<void>;
17
+ runOperation: RunOperation;
18
+ };
19
+ export {};
@@ -0,0 +1,79 @@
1
+ import { parsePublicCommand } from "@getuserfeedback/protocol";
2
+ import { createCommandEnvelopeWithInstanceId } from "@getuserfeedback/protocol/host";
3
+ import { createClientTransportAdmission } from "@getuserfeedback/sdk/internal/client-transport-admission";
4
+ import { createNativeDirectCommandSessionController } from "./native-direct-command-session-controller.js";
5
+ import { REACT_NATIVE_CLIENT_META } from "./provider-command-helpers.js";
6
+ const parseIdempotencyKey = (options) => {
7
+ var _a;
8
+ const idempotencyKey = (_a = options === null || options === void 0 ? void 0 : options.idempotencyKey) === null || _a === void 0 ? void 0 : _a.trim();
9
+ if ((options === null || options === void 0 ? void 0 : options.idempotencyKey) !== undefined && !idempotencyKey)
10
+ throw new Error("idempotencyKey must be a non-empty string");
11
+ return idempotencyKey;
12
+ };
13
+ export function createNativeProviderCommandController(input) {
14
+ const directSessionController = createNativeDirectCommandSessionController();
15
+ const admission = createClientTransportAdmission();
16
+ const report = (value) => {
17
+ var _a;
18
+ const error = value instanceof Error ? value : new Error(String(value));
19
+ try {
20
+ (_a = input.onError) === null || _a === void 0 ? void 0 : _a.call(input, error);
21
+ }
22
+ catch (_b) { }
23
+ return error;
24
+ };
25
+ const observe = (promise) => promise.catch((error) => Promise.reject(report(error)));
26
+ const dispatch = (command, idempotencyKey) => {
27
+ const envelope = createCommandEnvelopeWithInstanceId({
28
+ clientMeta: REACT_NATIVE_CLIENT_META,
29
+ command: parsePublicCommand(command),
30
+ idempotencyKey,
31
+ instanceId: input.instanceId,
32
+ });
33
+ return directSessionController.session.dispatch(envelope);
34
+ };
35
+ const runOperation = (run, options) => {
36
+ let idempotencyKey;
37
+ try {
38
+ idempotencyKey = parseIdempotencyKey(options);
39
+ }
40
+ catch (error) {
41
+ return observe(Promise.reject(error));
42
+ }
43
+ return observe(admission
44
+ .runOperation(({ attemptHandoff }) => run((command) => attemptHandoff({
45
+ handOff: () => ({
46
+ settlement: dispatch(command, idempotencyKey),
47
+ }),
48
+ prepare: () => { },
49
+ })))
50
+ .then((result) => result.status === "settled" ? result.result : undefined));
51
+ };
52
+ const runBarrier = (kind, onGenerationAdvanced, options) => {
53
+ let idempotencyKey;
54
+ try {
55
+ idempotencyKey = parseIdempotencyKey(options);
56
+ }
57
+ catch (error) {
58
+ return observe(Promise.reject(error));
59
+ }
60
+ return observe(admission
61
+ .runBarrier({
62
+ handOff: () => ({
63
+ settlement: kind === null
64
+ ? Promise.resolve()
65
+ : dispatch({ kind }, idempotencyKey).then(() => undefined),
66
+ }),
67
+ onGenerationAdvanced,
68
+ prepare: () => { },
69
+ })
70
+ .then(() => undefined));
71
+ };
72
+ return {
73
+ directSessionController,
74
+ dispose: () => directSessionController.dispose(new Error("Native provider client has been disposed")),
75
+ reject: (error) => Promise.reject(report(error)),
76
+ runBarrier,
77
+ runOperation,
78
+ };
79
+ }
@@ -0,0 +1,7 @@
1
+ import type { FlowRun } from "./client.js";
2
+ import type { createNativeProviderCommandController } from "./native-provider-command-controller.js";
3
+ export declare function createNativeProviderFlowRegistry(commands: ReturnType<typeof createNativeProviderCommandController>): {
4
+ flow: (flowId: string) => FlowRun;
5
+ invalidate: (handleId: string) => void;
6
+ reset: () => void;
7
+ };
@@ -0,0 +1,70 @@
1
+ import { parsePublicCommand } from "@getuserfeedback/protocol";
2
+ import { CommandSettlementError, createFlowCommandHandleController, getFlowHandleFromSettlementDetail, } from "@getuserfeedback/protocol/host";
3
+ import { createFlowOpenRequestController } from "@getuserfeedback/sdk/internal/flow-open-request-controller";
4
+ const isStaleHandleError = (error) => error instanceof CommandSettlementError
5
+ ? error.code === "STALE_HANDLE"
6
+ : error instanceof Error && error.message.includes("flowHandleId is stale");
7
+ export function createNativeProviderFlowRegistry(commands) {
8
+ const create = (flowId) => {
9
+ const openRequests = createFlowOpenRequestController();
10
+ const handle = createFlowCommandHandleController({
11
+ getHandleIdFromSettlement: (settlement) => { var _a; return (_a = getFlowHandleFromSettlementDetail(settlement)) === null || _a === void 0 ? void 0 : _a.flowHandleId; },
12
+ isStaleHandleError,
13
+ });
14
+ const allocation = (command) => commands
15
+ .runOperation((dispatch) => handle.runCommand((flowHandleId) => (Object.assign(Object.assign({}, command), { flowHandleId })), dispatch))
16
+ .then(() => undefined);
17
+ const prepare = (build, start = allocation) => {
18
+ try {
19
+ return start(parsePublicCommand(build()));
20
+ }
21
+ catch (error) {
22
+ return commands.reject(error);
23
+ }
24
+ };
25
+ const run = {
26
+ flowId,
27
+ open: (options) => prepare(() => ({
28
+ flowId,
29
+ hideCloseButton: options === null || options === void 0 ? void 0 : options.hideCloseButton,
30
+ kind: "open",
31
+ metadata: options === null || options === void 0 ? void 0 : options.metadata,
32
+ }), (command) => openRequests.run(() => allocation(command))),
33
+ prefetch: () => prepare(() => ({
34
+ flowId,
35
+ kind: "prefetch",
36
+ })),
37
+ prerender: (options) => prepare(() => ({
38
+ flowId,
39
+ hideCloseButton: options === null || options === void 0 ? void 0 : options.hideCloseButton,
40
+ kind: "prerender",
41
+ })),
42
+ close: () => {
43
+ openRequests.clear();
44
+ return commands
45
+ .runOperation((dispatch) => handle.runResolvedHandleCommand((flowHandleId) => ({ flowHandleId, kind: "close" }), async (command) => void (await dispatch(command))))
46
+ .then(() => undefined);
47
+ },
48
+ };
49
+ return { clearPendingOpen: openRequests.clear, handle, run };
50
+ };
51
+ const controllers = new Map();
52
+ return {
53
+ flow: (flowId) => {
54
+ var _a;
55
+ const current = (_a = controllers.get(flowId)) !== null && _a !== void 0 ? _a : create(flowId);
56
+ controllers.set(flowId, current);
57
+ return current.run;
58
+ },
59
+ invalidate: (handleId) => {
60
+ for (const controller of controllers.values())
61
+ controller.handle.invalidateResolvedHandle(handleId);
62
+ },
63
+ reset: () => {
64
+ for (const controller of controllers.values()) {
65
+ controller.clearPendingOpen();
66
+ controller.handle.reset();
67
+ }
68
+ },
69
+ };
70
+ }