@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 +27 -8
- package/dist/client.d.ts +29 -22
- package/dist/core-webview-host.js +1 -1
- package/dist/flow-container.js +1 -4
- package/dist/native-core-command-channel.d.ts +7 -0
- package/dist/native-core-command-channel.js +17 -0
- package/dist/native-flow-container-context.d.ts +3 -0
- package/dist/native-link-router-rpc.d.ts +14 -0
- package/dist/native-link-router-rpc.js +163 -0
- package/dist/native-provider-client-controller.d.ts +1 -0
- package/dist/native-provider-client-controller.js +1 -0
- package/dist/native-provider-client.d.ts +1 -1
- package/dist/native-provider-client.js +0 -2
- package/dist/native-provider-flow-registry.d.ts +8 -1
- package/dist/native-provider-flow-registry.js +46 -3
- package/dist/native-provider.d.ts +3 -1
- package/dist/native-provider.js +29 -3
- package/dist/native-runtime-root.d.ts +8 -2
- package/dist/native-runtime-root.js +32 -7
- package/dist/native-view-host-projection.js +8 -2
- package/dist/native-view-layout-transition.d.ts +25 -0
- package/dist/native-view-layout-transition.js +244 -0
- package/dist/native-view-presentation-surface.d.ts +3 -1
- package/dist/native-view-presentation-surface.js +48 -16
- package/dist/native-view-record-controller.d.ts +7 -1
- package/dist/native-view-record-controller.js +50 -6
- package/dist/use-flow.d.ts +32 -0
- package/dist/use-flow.js +50 -0
- package/dist/version.js +1 -1
- package/dist/view-webview-host.js +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,17 +1,35 @@
|
|
|
1
1
|
# @getuserfeedback/react-native
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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
|
|
29
|
-
the targeting rules you set in the dashboard.
|
|
46
|
+
That is enough to receive flows published for automatic Widget delivery.
|
|
30
47
|
|
|
31
|
-
|
|
32
|
-
[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
|
-
/**
|
|
33
|
-
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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(
|
|
50
|
+
return createElement(WebViewDocumentHost, {
|
|
51
51
|
buildDocumentStartScript,
|
|
52
52
|
controller,
|
|
53
53
|
parseMessage: parseCoreWebViewBridgeMessage,
|
package/dist/flow-container.js
CHANGED
|
@@ -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"
|
|
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
|
|
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) =>
|
|
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
|
-
|
|
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 {
|
|
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
|
*
|