@getuserfeedback/react-native 5.11.0 → 5.14.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/README.md CHANGED
@@ -1,17 +1,35 @@
1
1
  # @getuserfeedback/react-native
2
2
 
3
- In-product surveys for React Native apps, from [getuserfeedback.com](https://getuserfeedback.com).
3
+ Add in-product surveys, forms, and messages to React Native applications with
4
+ [getuserfeedback.com](https://getuserfeedback.com).
4
5
 
5
6
  ## Get started
6
7
 
7
- Install the SDK and its WebView peer dependency:
8
+ Install the SDK and its WebView peer dependency.
9
+
10
+ For Expo projects:
11
+
12
+ ```bash
13
+ npm install @getuserfeedback/react-native
14
+ npx expo install react-native-webview
15
+ ```
16
+
17
+ For other React Native projects:
8
18
 
9
19
  ```bash
10
20
  npm install @getuserfeedback/react-native react-native-webview
11
21
  ```
12
22
 
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).
23
+ Then link the native dependency in bare iOS projects:
24
+
25
+ ```bash
26
+ npx pod-install
27
+ ```
28
+
29
+ Requires React 18, React Native 0.73, and React Native WebView 13 or later.
30
+
31
+ Mount the provider near the root of your app using the Public API key from
32
+ [Widget settings](https://getuserfeedback.com/app/_/settings/widget):
15
33
 
16
34
  ```tsx
17
35
  import { GetUserFeedbackProvider } from "@getuserfeedback/react-native";
@@ -25,8 +43,9 @@ export function App() {
25
43
  }
26
44
  ```
27
45
 
28
- That's it. Flows configured for automatic Widget delivery now appear based on
29
- the targeting rules you set in the dashboard.
46
+ That is enough to receive flows published for automatic Widget delivery.
30
47
 
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).
48
+ - [React Native SDK guide](https://getuserfeedback.com/docs/get-started/react-native-sdk)
49
+ - [React Native SDK reference](https://getuserfeedback.com/docs/reference/react-native-sdk-reference)
50
+ - [Package versions](https://www.npmjs.com/package/@getuserfeedback/react-native?activeTab=versions)
51
+ - [Support](mailto:hello@getuserfeedback.com)
package/dist/client.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { AppEventExternalId, PublicCommandPayload } from "@getuserfeedback/protocol";
2
- import type { ActionRegistration, CapabilitiesInput, ConfigureOptions, EventOptions, EventProperties, FlagsInput, GraphOperations, InitOptions, OpenRequestedCallback } from "@getuserfeedback/sdk";
3
- export type { CapabilitiesInput, ConfigureOptions, EventOptions, EventProperties, FlagsInput, GraphOperations, GraphRelationship, GraphRelationshipRef, OpenRequestedCallback, OpenRequestedEvent, TrackOptions, } from "@getuserfeedback/sdk";
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";
4
4
  type IdentifyTraits = Record<string, unknown>;
5
5
  type OpenCommand = Extract<PublicCommandPayload, {
6
6
  kind: "open";
@@ -13,9 +13,6 @@ type IdentifyOptions = {
13
13
  };
14
14
  export type OpenFlowOptions = Pick<OpenCommand, "hideCloseButton" | "metadata">;
15
15
  export type PrerenderFlowOptions = Pick<PrerenderCommand, "hideCloseButton">;
16
- type HostContext = Extract<PublicCommandPayload, {
17
- kind: "updateHostContext";
18
- }>["context"];
19
16
  type CommandOptions = {
20
17
  /**
21
18
  * @deprecated Omit this option. The SDK owns command identity and transport
@@ -29,8 +26,13 @@ export type NativeActionRegistration = Extract<ActionRegistration, {
29
26
  }>;
30
27
  /** React Native provider initialization options. */
31
28
  export type ClientOptions = Omit<InitOptions, "capabilities" | "clientMeta" | "flags" | "hostActions" | "hostCapabilities" | "runtimeEndpoints"> & {
32
- /** Widget actions implemented by the current app version. */
33
- actions?: NativeActionRegistration[] | undefined;
29
+ /** Routes authored HTTP(S) links through the native app. */
30
+ links?: LinksConfig | undefined;
31
+ /**
32
+ * Widget actions implemented by the current app version. Prefer the grouped
33
+ * configuration; the registration array remains for compatibility.
34
+ */
35
+ actions?: ActionsConfig | NativeActionRegistration[] | undefined;
34
36
  /** Capabilities supported by the current app version. */
35
37
  capabilities?: CapabilitiesInput | undefined;
36
38
  /** Feature flag evaluations supplied during initialization. */
@@ -71,19 +73,35 @@ export interface Client {
71
73
  */
72
74
  readonly instanceId: string;
73
75
  /**
74
- * Updates authentication, consent, and other client configuration.
76
+ * Updates authentication, consent, and other client configuration. Await the
77
+ * returned promise or handle its rejection when applying runtime updates.
75
78
  *
76
79
  * @see https://getuserfeedback.com/docs/security-and-privacy/jwt-auth
77
80
  */
78
81
  configure: (opts: ConfigureOptions, options?: CommandOptions) => Promise<void>;
82
+ /**
83
+ * Associates the current user with a user ID, traits, and optional external
84
+ * IDs. Some invalid arguments throw synchronously; other validation or
85
+ * command-processing failures reject the returned promise.
86
+ *
87
+ * @see https://getuserfeedback.com/docs/guides/identity-resolution
88
+ */
79
89
  identify: {
80
90
  (userId: string, traits?: IdentifyTraits, options?: IdentifyOptions): Promise<void>;
81
91
  (traits: IdentifyTraits, placeholder: undefined, options?: IdentifyOptions): Promise<void>;
82
92
  (traits: IdentifyTraits, options?: IdentifyOptions): Promise<void>;
83
93
  };
84
- /** Records a product event for targeting and analysis. */
94
+ /**
95
+ * Records a product event for targeting and analysis. Some invalid arguments
96
+ * throw synchronously; other validation or command-processing failures reject
97
+ * the returned promise.
98
+ */
85
99
  track: (eventName: string, properties?: EventProperties, options?: EventOptions) => Promise<void>;
86
- /** Records a named application screen for targeting and analysis. */
100
+ /**
101
+ * Records a named application screen for targeting and analysis. Some invalid
102
+ * arguments throw synchronously; other validation or command-processing
103
+ * failures reject the returned promise.
104
+ */
87
105
  screen: (name: string, properties?: EventProperties, options?: EventOptions) => Promise<void>;
88
106
  /** Records directed relationship observations through the analytics pipeline. */
89
107
  graph: GraphOperations;
@@ -107,17 +125,6 @@ export interface Client {
107
125
  * ```
108
126
  */
109
127
  onOpenRequested: (callback: OpenRequestedCallback) => () => void;
110
- /** Resets widget state, authentication, and the current user identity. */
128
+ /** Closes all open flows, clears JWT authentication, and resets user identity. */
111
129
  reset: (options?: CommandOptions) => Promise<void>;
112
- /**
113
- * Updates current-route and host-supplied targeting context. URL and page
114
- * fields can enrich events and consent-gated telemetry; `env` and `storage`
115
- * are local-targeting inputs only.
116
- */
117
- updateHostContext: (context: HostContext, options?: CommandOptions) => Promise<void>;
118
- /**
119
- * Emits an ephemeral signal for local engagement targeting. This does not
120
- * persist a customer occurrence; use `track()` or `screen()` for that.
121
- */
122
- emitHostSignal: (name: string, data?: unknown, options?: CommandOptions) => Promise<void>;
123
130
  }
@@ -47,7 +47,7 @@ function CoreWebViewHostDocument({ externalCoreUrl, onConnected, onActivationRet
47
47
  const controller = controllerRef.current;
48
48
  const buildDocumentStartScript = useMemo(() => () => buildCoreWebViewBridgeScript({ bridgeId, phase: "documentStart" }), [bridgeId]);
49
49
  const source = useMemo(() => ({ uri: externalCoreUrl }), [externalCoreUrl]);
50
- return createElement((WebViewDocumentHost), {
50
+ return createElement(WebViewDocumentHost, {
51
51
  buildDocumentStartScript,
52
52
  controller,
53
53
  parseMessage: parseCoreWebViewBridgeMessage,
@@ -22,10 +22,7 @@ const renderProjection = (projection, presentationMode) => {
22
22
  return createElement(NativeViewHostProjection, Object.assign(Object.assign({}, props), { key,
23
23
  presentationMode }));
24
24
  };
25
- const useRecords = (projection) => {
26
- var _a, _b, _c;
27
- return useSyncExternalStore((_a = projection === null || projection === void 0 ? void 0 : projection.controller.subscribe) !== null && _a !== void 0 ? _a : subscribeNoop, (_b = projection === null || projection === void 0 ? void 0 : projection.controller.getSnapshot) !== null && _b !== void 0 ? _b : getEmptyRecords, (_c = projection === null || projection === void 0 ? void 0 : projection.controller.getSnapshot) !== null && _c !== void 0 ? _c : getEmptyRecords);
28
- };
25
+ const useRecords = (projection) => { var _a, _b, _c; return useSyncExternalStore((_a = projection === null || projection === void 0 ? void 0 : projection.controller.subscribe) !== null && _a !== void 0 ? _a : subscribeNoop, (_b = projection === null || projection === void 0 ? void 0 : projection.controller.getSnapshot) !== null && _b !== void 0 ? _b : getEmptyRecords, (_c = projection === null || projection === void 0 ? void 0 : projection.controller.getSnapshot) !== null && _c !== void 0 ? _c : getEmptyRecords); };
29
26
  export function NativeStandardFlowContainer() {
30
27
  const context = useContext(NativeFlowContainerContext);
31
28
  return (context === null || context === void 0 ? void 0 : context.contentOwner) === null
@@ -3,6 +3,7 @@ import { type EventHandleInvalidated } from "@getuserfeedback/protocol/internal/
3
3
  import { type EventCommandSettled } from "@getuserfeedback/protocol/internal/core-host-command-settlement";
4
4
  import type { CoreWebViewHostConnection } from "./core-webview-host-controller.js";
5
5
  import { createNativeHostActionRpcHandler } from "./native-host-action-rpc.js";
6
+ import { createNativeLinkRouterRpcHandler } from "./native-link-router-rpc.js";
6
7
  export type NativeCoreCommandChannel = {
7
8
  post: (envelope: CoreCommandEnvelope) => void;
8
9
  subscribe: (listener: (message: EventCommandSettled) => void) => () => void;
@@ -19,6 +20,12 @@ export declare function createNativeCoreCommandChannel(input: {
19
20
  instanceId: string;
20
21
  subscribeToViewActivity: (listener: () => void) => () => void;
21
22
  };
23
+ linkRouter?: {
24
+ getActiveViewIds: () => readonly string[];
25
+ getRouter: Parameters<typeof createNativeLinkRouterRpcHandler>[0]["getRouter"];
26
+ instanceId: string;
27
+ };
28
+ now?: () => number;
22
29
  onHandleInvalidated?: (message: EventHandleInvalidated) => void;
23
30
  onHostActionError?: (error: Error) => void;
24
31
  onInvalidMessage?: (error: Error, value: unknown) => void;
@@ -4,6 +4,7 @@ import { eventCommandSettledSchema, } from "@getuserfeedback/protocol/internal/c
4
4
  import { CORE_SESSION_FAILED_CODE, eventCoreSessionFailedSchema, } from "@getuserfeedback/protocol/internal/core-session-failure";
5
5
  import { buildRpcResponse, rpcRequestSchema, } from "@getuserfeedback/protocol/internal/v3-rpc-messages";
6
6
  import { createNativeHostActionRpcHandler } from "./native-host-action-rpc.js";
7
+ import { createNativeLinkRouterRpcHandler } from "./native-link-router-rpc.js";
7
8
  const DISPOSED_MESSAGE = "Core command channel is disposed";
8
9
  const isRecord = (value) => typeof value === "object" && value !== null;
9
10
  export function createNativeCoreCommandChannel(input) {
@@ -11,6 +12,7 @@ export function createNativeCoreCommandChannel(input) {
11
12
  const listeners = new Set();
12
13
  let disposed = false;
13
14
  const hostActions = input.hostActions;
15
+ const linkRouter = input.linkRouter;
14
16
  const hostActionRpcHandler = hostActions
15
17
  ? createNativeHostActionRpcHandler({
16
18
  generation: input.coreConnection.generation,
@@ -31,6 +33,17 @@ export function createNativeCoreCommandChannel(input) {
31
33
  }
32
34
  activeViewIds = nextActiveViewIds;
33
35
  });
36
+ const linkRouterRpcHandler = linkRouter
37
+ ? createNativeLinkRouterRpcHandler({
38
+ generation: input.coreConnection.generation,
39
+ getRouter: linkRouter.getRouter,
40
+ instanceId: linkRouter.instanceId,
41
+ isViewActive: (viewId) => linkRouter.getActiveViewIds().includes(viewId),
42
+ now: input.now,
43
+ onError: input.onHostActionError,
44
+ postToCore: input.coreConnection.transport.postMessage,
45
+ })
46
+ : null;
34
47
  const handleMessage = (value) => {
35
48
  var _a, _b, _c, _d, _e, _f;
36
49
  if (disposed || !isRecord(value)) {
@@ -45,6 +58,9 @@ export function createNativeCoreCommandChannel(input) {
45
58
  if (hostActionRpcHandler === null || hostActionRpcHandler === void 0 ? void 0 : hostActionRpcHandler.handle(parsed.data)) {
46
59
  return;
47
60
  }
61
+ if (linkRouterRpcHandler === null || linkRouterRpcHandler === void 0 ? void 0 : linkRouterRpcHandler.handle(parsed.data)) {
62
+ return;
63
+ }
48
64
  let result;
49
65
  if (parsed.data.method === TRAFFIC_TELEMETRY_RPC_METHOD) {
50
66
  const params = trafficTelemetryRpcParamsSchema.safeParse(parsed.data.params);
@@ -161,6 +177,7 @@ export function createNativeCoreCommandChannel(input) {
161
177
  disposed = true;
162
178
  unsubscribeViewActivity === null || unsubscribeViewActivity === void 0 ? void 0 : unsubscribeViewActivity();
163
179
  hostActionRpcHandler === null || hostActionRpcHandler === void 0 ? void 0 : hostActionRpcHandler.dispose();
180
+ linkRouterRpcHandler === null || linkRouterRpcHandler === void 0 ? void 0 : linkRouterRpcHandler.dispose();
164
181
  unsubscribeTransport();
165
182
  listeners.clear();
166
183
  },
@@ -1,14 +1,17 @@
1
1
  import type { Client } from "./client.js";
2
+ import type { NativeProviderFlowRegistry } from "./native-provider-flow-registry.js";
2
3
  import type { NativeViewProjection } from "./native-view-host-projection.js";
3
4
  export type NativeFlowContainerContextValue = Readonly<{
4
5
  client: Client;
5
6
  contentOwner: object | null;
7
+ flowRegistry: NativeProviderFlowRegistry;
6
8
  projection: NativeViewProjection | null;
7
9
  registerContent: (owner: object) => () => void;
8
10
  }>;
9
11
  export declare const NativeFlowContainerContext: import("react").Context<Readonly<{
10
12
  client: Client;
11
13
  contentOwner: object | null;
14
+ flowRegistry: NativeProviderFlowRegistry;
12
15
  projection: NativeViewProjection | null;
13
16
  registerContent: (owner: object) => () => void;
14
17
  }> | null>;
@@ -0,0 +1,14 @@
1
+ import type { LinkRouter } from "@getuserfeedback/protocol/host";
2
+ import { type RpcRequest } from "@getuserfeedback/protocol/internal/v3-rpc-messages";
3
+ export declare const createNativeLinkRouterRpcHandler: (input: {
4
+ generation: number;
5
+ getRouter: () => LinkRouter | undefined;
6
+ instanceId: string;
7
+ isViewActive: (viewId: string) => boolean;
8
+ now?: () => number;
9
+ onError?: (error: Error) => void;
10
+ postToCore: (message: unknown) => void;
11
+ }) => {
12
+ dispose: () => void;
13
+ handle: (request: RpcRequest) => boolean;
14
+ };
@@ -0,0 +1,163 @@
1
+ import { buildFlowActionSucceededV1 } from "@getuserfeedback/protocol";
2
+ import { buildEventFlowActionSucceeded } from "@getuserfeedback/protocol/internal/host-core-flow-action-succeeded";
3
+ import { BROWSER_NAVIGATION_AVAILABILITY_RPC_METHOD, BROWSER_NAVIGATION_OPEN_RPC_METHOD, toBrowserNavigationOpenParams, } from "@getuserfeedback/protocol/internal/navigation-rpc";
4
+ import { buildRpcResponse, } from "@getuserfeedback/protocol/internal/v3-rpc-messages";
5
+ import { invokeLinkRouter } from "@getuserfeedback/sdk/internal/link-router";
6
+ const MAX_SETTLED_NAVIGATION_INVOCATIONS = 256;
7
+ export const createNativeLinkRouterRpcHandler = (input) => {
8
+ const invocations = new Map();
9
+ let disposed = false;
10
+ const reportError = (error) => {
11
+ var _a;
12
+ try {
13
+ (_a = input.onError) === null || _a === void 0 ? void 0 : _a.call(input, error instanceof Error ? error : new Error(String(error)));
14
+ }
15
+ catch (_b) {
16
+ // Diagnostics cannot change navigation settlement.
17
+ }
18
+ };
19
+ const postToCore = (message) => {
20
+ try {
21
+ input.postToCore(message);
22
+ }
23
+ catch (error) {
24
+ reportError(error);
25
+ }
26
+ };
27
+ const respond = (request, handled) => {
28
+ postToCore(buildRpcResponse({
29
+ gen: request.gen,
30
+ ok: true,
31
+ result: { handled },
32
+ rpcId: request.rpcId,
33
+ viewId: request.viewId,
34
+ }));
35
+ };
36
+ const reportSuccess = (request, params) => {
37
+ var _a;
38
+ const definition = params.definition;
39
+ const invocationId = params.invocationId;
40
+ const seed = params.flowActionSucceededSeed;
41
+ if (!definition || !invocationId || !seed)
42
+ return;
43
+ try {
44
+ postToCore(buildEventFlowActionSucceeded({
45
+ event: buildFlowActionSucceededV1({
46
+ actionInvocationId: invocationId,
47
+ actionKey: definition.key,
48
+ actionKind: seed.actionKind,
49
+ actionPlacementId: seed.actionPlacementId,
50
+ actionVersion: definition.version,
51
+ flowId: seed.flowId,
52
+ flowRunId: seed.flowRunId,
53
+ flowSurface: "widget",
54
+ flowVersionId: seed.flowVersionId,
55
+ flowVersionNumber: seed.flowVersionNumber,
56
+ }),
57
+ gen: request.gen,
58
+ instanceId: input.instanceId,
59
+ timestamp: ((_a = input.now) !== null && _a !== void 0 ? _a : Date.now)(),
60
+ }));
61
+ }
62
+ catch (error) {
63
+ reportError(error);
64
+ }
65
+ };
66
+ const trimSettledInvocations = () => {
67
+ if (invocations.size <= MAX_SETTLED_NAVIGATION_INVOCATIONS)
68
+ return;
69
+ for (const [invocationId, invocation] of invocations) {
70
+ if (invocation.handled === undefined)
71
+ continue;
72
+ invocations.delete(invocationId);
73
+ return;
74
+ }
75
+ };
76
+ return {
77
+ dispose: () => {
78
+ disposed = true;
79
+ invocations.clear();
80
+ },
81
+ handle: (request) => {
82
+ if (disposed)
83
+ return false;
84
+ if (request.method === BROWSER_NAVIGATION_AVAILABILITY_RPC_METHOD) {
85
+ postToCore(buildRpcResponse({
86
+ gen: request.gen,
87
+ ok: true,
88
+ result: { available: input.getRouter() !== undefined },
89
+ rpcId: request.rpcId,
90
+ viewId: request.viewId,
91
+ }));
92
+ return true;
93
+ }
94
+ if (request.method !== BROWSER_NAVIGATION_OPEN_RPC_METHOD)
95
+ return false;
96
+ const params = toBrowserNavigationOpenParams(request.params);
97
+ const trustedInvocationId = params !== null &&
98
+ params.definition !== undefined &&
99
+ params.flowActionSucceededSeed !== undefined &&
100
+ params.invocationId !== undefined
101
+ ? params.invocationId
102
+ : null;
103
+ const fingerprint = trustedInvocationId !== null
104
+ ? JSON.stringify({
105
+ gen: request.gen,
106
+ idemKey: request.idemKey,
107
+ params,
108
+ requestViewId: request.viewId,
109
+ })
110
+ : null;
111
+ if (trustedInvocationId !== null && fingerprint !== null) {
112
+ const existing = invocations.get(trustedInvocationId);
113
+ if (existing) {
114
+ if (existing.fingerprint !== fingerprint) {
115
+ respond(request, false);
116
+ }
117
+ else if (existing.handled === undefined) {
118
+ existing.waiters.push(request);
119
+ }
120
+ else {
121
+ respond(request, existing.handled);
122
+ }
123
+ return true;
124
+ }
125
+ }
126
+ const router = input.getRouter();
127
+ if (params === null ||
128
+ params.generation !== input.generation ||
129
+ !input.isViewActive(params.viewId) ||
130
+ router === undefined) {
131
+ respond(request, false);
132
+ return true;
133
+ }
134
+ let invocation = null;
135
+ if (trustedInvocationId !== null && fingerprint !== null) {
136
+ invocation = { fingerprint, waiters: [request] };
137
+ invocations.set(trustedInvocationId, invocation);
138
+ }
139
+ let handled = false;
140
+ try {
141
+ invokeLinkRouter(router, Object.assign({ url: params.url }, (params.target === undefined ? {} : { target: params.target })));
142
+ handled = true;
143
+ }
144
+ catch (error) {
145
+ reportError(error);
146
+ }
147
+ if (disposed)
148
+ return true;
149
+ if (invocation === null) {
150
+ respond(request, handled);
151
+ return true;
152
+ }
153
+ invocation.handled = handled;
154
+ for (const waiter of invocation.waiters.splice(0)) {
155
+ respond(waiter, handled);
156
+ }
157
+ if (handled)
158
+ reportSuccess(request, params);
159
+ trimSettledInvocations();
160
+ return true;
161
+ },
162
+ };
163
+ };
@@ -7,6 +7,7 @@ export declare function createNativeProviderClientController(input: {
7
7
  }): {
8
8
  client: import("./client.js").Client;
9
9
  directSessionController: import("./native-direct-command-session-controller.js").NativeDirectCommandSessionController;
10
+ flowRegistry: import("./native-provider-flow-registry.js").NativeProviderFlowRegistry;
10
11
  getRuntimeConfiguration: () => ConfigureOptions;
11
12
  observeOpenRequest: (payload: Parameters<OpenRequestDispatcher["dispatch"]>[0]) => ReturnType<OpenRequestDispatcher["dispatch"]>;
12
13
  dispose: () => void;
@@ -56,6 +56,7 @@ export function createNativeProviderClientController(input) {
56
56
  return {
57
57
  client,
58
58
  directSessionController: session,
59
+ flowRegistry: flows,
59
60
  getRuntimeConfiguration: () => (Object.assign({}, runtimeConfiguration)),
60
61
  observeOpenRequest,
61
62
  dispose: () => {
@@ -2,7 +2,7 @@ import type { PublicCommandPayload } from "@getuserfeedback/protocol";
2
2
  import { type OpenRequestDispatcher } from "@getuserfeedback/sdk/internal/open-request-dispatcher";
3
3
  import type { Client } from "./client.js";
4
4
  type NativeProviderClientCommand = Extract<PublicCommandPayload, {
5
- kind: "configure" | "identify" | "track" | "close" | "reset" | "updateHostContext" | "emitHostSignal";
5
+ kind: "configure" | "identify" | "track" | "close" | "reset";
6
6
  }>;
7
7
  type NativeProviderCommandOptions = NonNullable<Parameters<Client["configure"]>[1]>;
8
8
  type NativeProviderClientOptions = {
@@ -64,7 +64,5 @@ export function createNativeProviderClient({ dispatchCommand, flow, instanceId,
64
64
  close: () => dispatchCommand({ kind: "close" }),
65
65
  onOpenRequested: openRequestDispatcher.subscribe,
66
66
  reset: (options) => dispatchCommand({ kind: "reset" }, options),
67
- updateHostContext: (context, options) => dispatchCommand({ kind: "updateHostContext", context }, options),
68
- emitHostSignal: (name, data, options) => dispatchCommand(Object.assign({ kind: "emitHostSignal", name: parseRequiredString("name", name) }, (data === undefined ? {} : { data })), options),
69
67
  };
70
68
  }
@@ -1,7 +1,14 @@
1
1
  import type { FlowRun } from "./client.js";
2
2
  import type { createNativeProviderCommandController } from "./native-provider-command-controller.js";
3
- export declare function createNativeProviderFlowRegistry(commands: ReturnType<typeof createNativeProviderCommandController>): {
3
+ export type NativeFlowRegistryRecord = Readonly<{
4
+ flowId: string;
5
+ isOpening: boolean;
6
+ }>;
7
+ export type NativeProviderFlowRegistry = {
4
8
  flow: (flowId: string) => FlowRun;
9
+ getSnapshot: () => readonly NativeFlowRegistryRecord[];
10
+ subscribe: (listener: () => void) => () => void;
5
11
  invalidate: (handleId: string) => void;
6
12
  reset: () => void;
7
13
  };
14
+ export declare function createNativeProviderFlowRegistry(commands: ReturnType<typeof createNativeProviderCommandController>): NativeProviderFlowRegistry;
@@ -5,8 +5,23 @@ const isStaleHandleError = (error) => error instanceof CommandSettlementError
5
5
  ? error.code === "STALE_HANDLE"
6
6
  : error instanceof Error && error.message.includes("flowHandleId is stale");
7
7
  export function createNativeProviderFlowRegistry(commands) {
8
+ const listeners = new Set();
9
+ let snapshot = Object.freeze([]);
10
+ const notify = () => {
11
+ snapshot = Object.freeze(Array.from(controllers.values(), ({ flowId, isOpening }) => Object.freeze({ flowId, isOpening })));
12
+ for (const listener of listeners)
13
+ listener();
14
+ };
8
15
  const create = (flowId) => {
9
16
  const openRequests = createFlowOpenRequestController();
17
+ let isOpening = false;
18
+ let openRevision = 0;
19
+ const setOpening = (next) => {
20
+ if (isOpening === next)
21
+ return;
22
+ isOpening = next;
23
+ notify();
24
+ };
10
25
  const handle = createFlowCommandHandleController({
11
26
  getHandleIdFromSettlement: (settlement) => { var _a; return (_a = getFlowHandleFromSettlementDetail(settlement)) === null || _a === void 0 ? void 0 : _a.flowHandleId; },
12
27
  isStaleHandleError,
@@ -22,6 +37,11 @@ export function createNativeProviderFlowRegistry(commands) {
22
37
  return commands.reject(error);
23
38
  }
24
39
  };
40
+ const clearPendingOpen = () => {
41
+ openRevision += 1;
42
+ openRequests.clear();
43
+ setOpening(false);
44
+ };
25
45
  const run = {
26
46
  flowId,
27
47
  open: (options) => prepare(() => ({
@@ -29,7 +49,17 @@ export function createNativeProviderFlowRegistry(commands) {
29
49
  hideCloseButton: options === null || options === void 0 ? void 0 : options.hideCloseButton,
30
50
  kind: "open",
31
51
  metadata: options === null || options === void 0 ? void 0 : options.metadata,
32
- }), (command) => openRequests.run(() => allocation(command))),
52
+ }), (command) => {
53
+ openRevision += 1;
54
+ const revision = openRevision;
55
+ setOpening(true);
56
+ return openRequests
57
+ .run(() => allocation(command))
58
+ .finally(() => {
59
+ if (openRevision === revision)
60
+ setOpening(false);
61
+ });
62
+ }),
33
63
  prefetch: () => prepare(() => ({
34
64
  flowId,
35
65
  kind: "prefetch",
@@ -40,13 +70,21 @@ export function createNativeProviderFlowRegistry(commands) {
40
70
  kind: "prerender",
41
71
  })),
42
72
  close: () => {
43
- openRequests.clear();
73
+ clearPendingOpen();
44
74
  return commands
45
75
  .runOperation((dispatch) => handle.runResolvedHandleCommand((flowHandleId) => ({ flowHandleId, kind: "close" }), async (command) => void (await dispatch(command))))
46
76
  .then(() => undefined);
47
77
  },
48
78
  };
49
- return { clearPendingOpen: openRequests.clear, handle, run };
79
+ return {
80
+ clearPendingOpen,
81
+ flowId,
82
+ get isOpening() {
83
+ return isOpening;
84
+ },
85
+ handle,
86
+ run,
87
+ };
50
88
  };
51
89
  const controllers = new Map();
52
90
  return {
@@ -56,6 +94,11 @@ export function createNativeProviderFlowRegistry(commands) {
56
94
  controllers.set(flowId, current);
57
95
  return current.run;
58
96
  },
97
+ getSnapshot: () => snapshot,
98
+ subscribe: (listener) => {
99
+ listeners.add(listener);
100
+ return () => listeners.delete(listener);
101
+ },
59
102
  invalidate: (handleId) => {
60
103
  for (const controller of controllers.values())
61
104
  controller.handle.invalidateResolvedHandle(handleId);
@@ -1,8 +1,10 @@
1
1
  import { type ComponentType, type ReactElement, type ReactNode } from "react";
2
2
  import type { Client, ClientOptions } from "./client.js";
3
- export type { CapabilitiesInput, Client, ClientOptions, ConfigureOptions, EventOptions, EventProperties, FlagsInput, FlowRun, GraphOperations, GraphRelationship, GraphRelationshipRef, NativeActionRegistration, OpenFlowOptions, OpenRequestedCallback, OpenRequestedEvent, PrerenderFlowOptions, TrackOptions, } 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";
4
4
  export type { UseFlowContainerReturn } from "./flow-container.js";
5
5
  export { FlowContent, useFlowContainer } from "./flow-container.js";
6
+ export type { UseFlowOpenOptions, UseFlowOptions, UseFlowReturn, } from "./use-flow.js";
7
+ export { useFlow } from "./use-flow.js";
6
8
  /**
7
9
  * Custom WebView implementation used by the native runtime.
8
10
  *