@getuserfeedback/react-native 5.12.0 → 5.14.1
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 -8
- 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-command-controller.js +3 -1
- 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";
|
|
@@ -26,8 +26,13 @@ export type NativeActionRegistration = Extract<ActionRegistration, {
|
|
|
26
26
|
}>;
|
|
27
27
|
/** React Native provider initialization options. */
|
|
28
28
|
export type ClientOptions = Omit<InitOptions, "capabilities" | "clientMeta" | "flags" | "hostActions" | "hostCapabilities" | "runtimeEndpoints"> & {
|
|
29
|
-
/**
|
|
30
|
-
|
|
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;
|
|
31
36
|
/** Capabilities supported by the current app version. */
|
|
32
37
|
capabilities?: CapabilitiesInput | undefined;
|
|
33
38
|
/** Feature flag evaluations supplied during initialization. */
|
|
@@ -68,19 +73,35 @@ export interface Client {
|
|
|
68
73
|
*/
|
|
69
74
|
readonly instanceId: string;
|
|
70
75
|
/**
|
|
71
|
-
* 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.
|
|
72
78
|
*
|
|
73
79
|
* @see https://getuserfeedback.com/docs/security-and-privacy/jwt-auth
|
|
74
80
|
*/
|
|
75
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
|
+
*/
|
|
76
89
|
identify: {
|
|
77
90
|
(userId: string, traits?: IdentifyTraits, options?: IdentifyOptions): Promise<void>;
|
|
78
91
|
(traits: IdentifyTraits, placeholder: undefined, options?: IdentifyOptions): Promise<void>;
|
|
79
92
|
(traits: IdentifyTraits, options?: IdentifyOptions): Promise<void>;
|
|
80
93
|
};
|
|
81
|
-
/**
|
|
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
|
+
*/
|
|
82
99
|
track: (eventName: string, properties?: EventProperties, options?: EventOptions) => Promise<void>;
|
|
83
|
-
/**
|
|
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
|
+
*/
|
|
84
105
|
screen: (name: string, properties?: EventProperties, options?: EventOptions) => Promise<void>;
|
|
85
106
|
/** Records directed relationship observations through the analytics pipeline. */
|
|
86
107
|
graph: GraphOperations;
|
|
@@ -104,6 +125,6 @@ export interface Client {
|
|
|
104
125
|
* ```
|
|
105
126
|
*/
|
|
106
127
|
onOpenRequested: (callback: OpenRequestedCallback) => () => void;
|
|
107
|
-
/**
|
|
128
|
+
/** Closes all open flows, clears JWT authentication, and resets user identity. */
|
|
108
129
|
reset: (options?: CommandOptions) => Promise<void>;
|
|
109
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: () => {
|
|
@@ -19,7 +19,9 @@ export function createNativeProviderCommandController(input) {
|
|
|
19
19
|
try {
|
|
20
20
|
(_a = input.onError) === null || _a === void 0 ? void 0 : _a.call(input, error);
|
|
21
21
|
}
|
|
22
|
-
catch (_b) {
|
|
22
|
+
catch (_b) {
|
|
23
|
+
// Consumer error handlers must not replace the command failure.
|
|
24
|
+
}
|
|
23
25
|
return error;
|
|
24
26
|
};
|
|
25
27
|
const observe = (promise) => promise.catch((error) => Promise.reject(report(error)));
|
|
@@ -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
|
*
|
package/dist/native-provider.js
CHANGED
|
@@ -12,6 +12,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
12
12
|
import { parseInitOptions } from "@getuserfeedback/protocol";
|
|
13
13
|
import { createUniqueId } from "@getuserfeedback/protocol/host";
|
|
14
14
|
import { createActionRegistry, } from "@getuserfeedback/sdk/internal/action-registry";
|
|
15
|
+
import { resolveLinkRouter } from "@getuserfeedback/sdk/internal/link-router";
|
|
15
16
|
import { createContext, createElement, Fragment, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
16
17
|
import { NativeStandardFlowContainer } from "./flow-container.js";
|
|
17
18
|
import { NativeFlowContainerContext, } from "./native-flow-container-context.js";
|
|
@@ -19,6 +20,7 @@ import { createNativeProviderClientController } from "./native-provider-client-c
|
|
|
19
20
|
import { REACT_NATIVE_PINNED_CORE_URL } from "./native-runtime-endpoints.js";
|
|
20
21
|
import { NativeProviderRuntime } from "./native-runtime-root.js";
|
|
21
22
|
export { FlowContent, useFlowContainer } from "./flow-container.js";
|
|
23
|
+
export { useFlow } from "./use-flow.js";
|
|
22
24
|
const GetUserFeedbackContext = createContext(null);
|
|
23
25
|
const toClientOptionsKey = (value) => {
|
|
24
26
|
if (value === null || typeof value !== "object") {
|
|
@@ -37,6 +39,20 @@ const toClientOptionsKey = (value) => {
|
|
|
37
39
|
const compareClientOptionsKeys = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
38
40
|
const haveSameActionRegistrations = (left, right) => left.length === right.length &&
|
|
39
41
|
left.every((identity, index) => identity === right[index]);
|
|
42
|
+
const useNativeLinkRouter = (links) => {
|
|
43
|
+
const router = resolveLinkRouter(links);
|
|
44
|
+
const hasRouterRef = useRef(router !== undefined);
|
|
45
|
+
if (hasRouterRef.current !== (router !== undefined)) {
|
|
46
|
+
throw new Error("links.router presence cannot change after provider mount; keep the router configured for the provider lifetime");
|
|
47
|
+
}
|
|
48
|
+
const routerRef = useRef(router);
|
|
49
|
+
useLayoutEffect(() => {
|
|
50
|
+
routerRef.current = router;
|
|
51
|
+
}, [router]);
|
|
52
|
+
return {
|
|
53
|
+
getRouter: useCallback(() => routerRef.current, []),
|
|
54
|
+
};
|
|
55
|
+
};
|
|
40
56
|
const useNativeActionRegistry = (actions) => {
|
|
41
57
|
const nextRegistry = createActionRegistry(actions);
|
|
42
58
|
if (nextRegistry.getOpenUrlHandler() !== undefined) {
|
|
@@ -69,7 +85,7 @@ const toInitFlags = (flags) => {
|
|
|
69
85
|
return normalizedFlags.length === 0 ? undefined : normalizedFlags;
|
|
70
86
|
};
|
|
71
87
|
const parseNativeInitOptions = (_a, hostActions) => {
|
|
72
|
-
var { actions: _actions, capabilities, flags } = _a, clientOptions = __rest(_a, ["actions", "capabilities", "flags"]);
|
|
88
|
+
var { actions: _actions, capabilities, flags, links: _links } = _a, clientOptions = __rest(_a, ["actions", "capabilities", "flags", "links"]);
|
|
73
89
|
const normalizedFlags = toInitFlags(flags);
|
|
74
90
|
const initOptions = parseInitOptions(Object.assign(Object.assign(Object.assign(Object.assign({}, clientOptions), (normalizedFlags === undefined ? {} : { flags: normalizedFlags })), (hostActions !== undefined && hostActions.length > 0
|
|
75
91
|
? { hostActions }
|
|
@@ -91,7 +107,8 @@ const parseNativeInitOptions = (_a, hostActions) => {
|
|
|
91
107
|
: normalizedHostCapabilities });
|
|
92
108
|
};
|
|
93
109
|
export function GetUserFeedbackProvider({ children, clientOptions, instanceId: instanceIdProp, onError, webViewComponent, }) {
|
|
94
|
-
var _a
|
|
110
|
+
var _a;
|
|
111
|
+
var _b;
|
|
95
112
|
const [instanceId] = useState(() => {
|
|
96
113
|
const normalizedInstanceId = instanceIdProp === null || instanceIdProp === void 0 ? void 0 : instanceIdProp.trim();
|
|
97
114
|
return normalizedInstanceId && normalizedInstanceId.length > 0
|
|
@@ -102,6 +119,7 @@ export function GetUserFeedbackProvider({ children, clientOptions, instanceId: i
|
|
|
102
119
|
const [projection, setProjection] = useState(null);
|
|
103
120
|
const contentOwnerRef = useRef(null);
|
|
104
121
|
const actionRegistry = useNativeActionRegistry(clientOptions.actions);
|
|
122
|
+
const linkRouter = useNativeLinkRouter(clientOptions.links);
|
|
105
123
|
const normalizedClientOptions = parseNativeInitOptions(clientOptions, actionRegistry.definitions);
|
|
106
124
|
const clientOptionsKey = toClientOptionsKey(normalizedClientOptions);
|
|
107
125
|
const coreUrl = (_b = (_a = normalizedClientOptions.runtimeEndpoints) === null || _a === void 0 ? void 0 : _a.coreUrl) !== null && _b !== void 0 ? _b : REACT_NATIVE_PINNED_CORE_URL;
|
|
@@ -148,9 +166,16 @@ export function GetUserFeedbackProvider({ children, clientOptions, instanceId: i
|
|
|
148
166
|
const flowContainerContext = useMemo(() => ({
|
|
149
167
|
client: controller.client,
|
|
150
168
|
contentOwner,
|
|
169
|
+
flowRegistry: controller.flowRegistry,
|
|
170
|
+
projection,
|
|
171
|
+
registerContent,
|
|
172
|
+
}), [
|
|
173
|
+
contentOwner,
|
|
174
|
+
controller.client,
|
|
175
|
+
controller.flowRegistry,
|
|
151
176
|
projection,
|
|
152
177
|
registerContent,
|
|
153
|
-
|
|
178
|
+
]);
|
|
154
179
|
const controllerLifecycleTokensRef = useRef(new WeakMap());
|
|
155
180
|
useEffect(() => {
|
|
156
181
|
const lifecycleToken = {};
|
|
@@ -182,6 +207,7 @@ export function GetUserFeedbackProvider({ children, clientOptions, instanceId: i
|
|
|
182
207
|
key: clientOptionsKey,
|
|
183
208
|
getRuntimeConfiguration,
|
|
184
209
|
getHostActionHandler: actionRegistry.getHandler,
|
|
210
|
+
getLinkRouter: linkRouter.getRouter,
|
|
185
211
|
onCommandSessionChange: handleCommandSessionChange,
|
|
186
212
|
onError: reportError,
|
|
187
213
|
onHandleInvalidated: handleHandleInvalidated,
|