@getuserfeedback/react-native 5.0.8 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +7 -2
- package/dist/core-webview-bridge-adapter.js +2 -0
- package/dist/flow-container.d.ts +26 -0
- package/dist/flow-container.js +75 -0
- package/dist/native-core-command-channel.js +45 -7
- package/dist/native-flow-container-context.d.ts +14 -0
- package/dist/native-flow-container-context.js +2 -0
- package/dist/native-provider-client-controller.d.ts +1 -1
- package/dist/native-provider-client.d.ts +7 -36
- package/dist/native-provider-client.js +15 -10
- package/dist/native-provider.d.ts +2 -0
- package/dist/native-provider.js +68 -6
- package/dist/native-runtime-endpoints.d.ts +8 -0
- package/dist/native-runtime-endpoints.js +8 -0
- package/dist/native-runtime-root.d.ts +7 -13
- package/dist/native-runtime-root.js +35 -32
- package/dist/native-view-host-projection.d.ts +13 -2
- package/dist/native-view-host-projection.js +26 -17
- package/dist/native-view-presentation-surface.d.ts +3 -1
- package/dist/native-view-presentation-surface.js +31 -14
- package/dist/native-view-record-controller.js +18 -0
- package/dist/version.js +1 -1
- package/dist/view-webview-bridge-adapter.js +8 -6
- package/dist/view-webview-host-controller.d.ts +1 -0
- package/dist/view-webview-host-controller.js +21 -5
- package/dist/webview-connection-bridge-script.d.ts +2 -0
- package/dist/webview-connection-bridge-script.js +38 -12
- package/package.json +4 -4
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AppEventExternalId, AppEventJsonValue, PublicCommandPayload } from "@getuserfeedback/protocol";
|
|
2
|
-
import type { ConfigureOptions, InitOptions } from "@getuserfeedback/sdk";
|
|
2
|
+
import type { CapabilitiesInput, ConfigureOptions, GraphOperations, InitOptions } from "@getuserfeedback/sdk";
|
|
3
3
|
type IdentifyTraits = Record<string, unknown>;
|
|
4
4
|
type TrackProperties = Record<string, AppEventJsonValue>;
|
|
5
5
|
type OpenCommand = Extract<PublicCommandPayload, {
|
|
@@ -23,7 +23,10 @@ type CommandOptions = {
|
|
|
23
23
|
idempotencyKey?: string;
|
|
24
24
|
};
|
|
25
25
|
/** React Native provider initialization options. */
|
|
26
|
-
export type ClientOptions = InitOptions
|
|
26
|
+
export type ClientOptions = Omit<InitOptions, "capabilities" | "hostCapabilities"> & {
|
|
27
|
+
/** Capabilities supported by the current app version. */
|
|
28
|
+
capabilities?: CapabilitiesInput | undefined;
|
|
29
|
+
};
|
|
27
30
|
/** Operations for one on-demand flow lifecycle. */
|
|
28
31
|
export interface FlowRun {
|
|
29
32
|
readonly flowId: string;
|
|
@@ -52,6 +55,8 @@ export interface Client {
|
|
|
52
55
|
};
|
|
53
56
|
/** Records a product event for targeting and analysis. */
|
|
54
57
|
track: (eventName: string, properties?: TrackProperties, options?: TrackOptions) => Promise<void>;
|
|
58
|
+
/** Records directed relationship observations through the analytics pipeline. */
|
|
59
|
+
graph: GraphOperations;
|
|
55
60
|
/** Returns the controller for one on-demand flow lifecycle. */
|
|
56
61
|
flow: (flowId: string) => FlowRun;
|
|
57
62
|
/** Closes every open flow owned by this provider. */
|
|
@@ -10,6 +10,8 @@ const CORE_WEBVIEW_BRIDGE_CONFIG = {
|
|
|
10
10
|
connectTimeoutMessage: "Timed out connecting core to the native host",
|
|
11
11
|
connectTimeoutMs: 30000,
|
|
12
12
|
inactiveConnectionMessage: "Core WebView bridge connection is not active",
|
|
13
|
+
reactivateOnNativeEpochChange: false,
|
|
14
|
+
throwOnUnavailablePost: false,
|
|
13
15
|
};
|
|
14
16
|
export const parseCoreWebViewBridgeMessage = (value) => parseWebViewConnectionBridgeMessage(value, {
|
|
15
17
|
invalidIdentity: "Invalid core WebView bridge message identity",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ReactElement } from "react";
|
|
2
|
+
/** Aggregate state and controls for a customer-owned flow container. */
|
|
3
|
+
export type UseFlowContainerReturn = Readonly<{
|
|
4
|
+
/** True when an active flow is visible. */
|
|
5
|
+
isOpen: boolean;
|
|
6
|
+
/** True when an active flow is waiting for its measured size. */
|
|
7
|
+
isLoading: boolean;
|
|
8
|
+
/** True when the customer container should be visible. */
|
|
9
|
+
shouldRenderContainer: boolean;
|
|
10
|
+
/** Close every active flow rendered by this container. */
|
|
11
|
+
close: () => Promise<void>;
|
|
12
|
+
}>;
|
|
13
|
+
export declare function NativeStandardFlowContainer(): ReactElement | null;
|
|
14
|
+
/**
|
|
15
|
+
* Observe and control the provider's customer-owned flow container.
|
|
16
|
+
*
|
|
17
|
+
* @see https://getuserfeedback.com/docs/guides/advanced/containers
|
|
18
|
+
*/
|
|
19
|
+
export declare function useFlowContainer(): UseFlowContainerReturn;
|
|
20
|
+
/**
|
|
21
|
+
* Render the active flow at its measured size inside a custom container.
|
|
22
|
+
* Keep this component mounted when the projected WebView must retain its state.
|
|
23
|
+
*
|
|
24
|
+
* @see https://getuserfeedback.com/docs/guides/advanced/containers
|
|
25
|
+
*/
|
|
26
|
+
export declare function FlowContent(): ReactElement | null;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { createElement, useCallback, useContext, useLayoutEffect, useRef, useSyncExternalStore, } from "react";
|
|
13
|
+
import { NativeFlowContainerContext } from "./native-flow-container-context.js";
|
|
14
|
+
import { NativeViewHostProjection, } from "./native-view-host-projection.js";
|
|
15
|
+
const EMPTY_RECORDS = Object.freeze([]);
|
|
16
|
+
const subscribeNoop = () => () => { };
|
|
17
|
+
const getEmptyRecords = () => EMPTY_RECORDS;
|
|
18
|
+
const renderProjection = (projection, presentationMode) => {
|
|
19
|
+
if (!projection)
|
|
20
|
+
return null;
|
|
21
|
+
const { key } = projection, props = __rest(projection, ["key"]);
|
|
22
|
+
return createElement(NativeViewHostProjection, Object.assign(Object.assign({}, props), { key,
|
|
23
|
+
presentationMode }));
|
|
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
|
+
};
|
|
29
|
+
export function NativeStandardFlowContainer() {
|
|
30
|
+
const context = useContext(NativeFlowContainerContext);
|
|
31
|
+
return (context === null || context === void 0 ? void 0 : context.contentOwner) === null
|
|
32
|
+
? renderProjection(context.projection, "overlay")
|
|
33
|
+
: null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Observe and control the provider's customer-owned flow container.
|
|
37
|
+
*
|
|
38
|
+
* @see https://getuserfeedback.com/docs/guides/advanced/containers
|
|
39
|
+
*/
|
|
40
|
+
export function useFlowContainer() {
|
|
41
|
+
const context = useContext(NativeFlowContainerContext);
|
|
42
|
+
if (!context) {
|
|
43
|
+
throw new Error("useFlowContainer must be used inside GetUserFeedbackProvider");
|
|
44
|
+
}
|
|
45
|
+
const records = useRecords(context.projection);
|
|
46
|
+
const isOpen = records.some((record) => record.isActive && record.size !== null);
|
|
47
|
+
const isLoading = records.some((record) => record.isActive && record.size === null);
|
|
48
|
+
const close = useCallback(() => context.client.close(), [context.client]);
|
|
49
|
+
return {
|
|
50
|
+
close,
|
|
51
|
+
isLoading,
|
|
52
|
+
isOpen,
|
|
53
|
+
shouldRenderContainer: isLoading || isOpen,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Render the active flow at its measured size inside a custom container.
|
|
58
|
+
* Keep this component mounted when the projected WebView must retain its state.
|
|
59
|
+
*
|
|
60
|
+
* @see https://getuserfeedback.com/docs/guides/advanced/containers
|
|
61
|
+
*/
|
|
62
|
+
export function FlowContent() {
|
|
63
|
+
var _a;
|
|
64
|
+
const context = useContext(NativeFlowContainerContext);
|
|
65
|
+
if (!context) {
|
|
66
|
+
throw new Error("FlowContent must be used inside GetUserFeedbackProvider");
|
|
67
|
+
}
|
|
68
|
+
const ownerRef = useRef(null);
|
|
69
|
+
(_a = ownerRef.current) !== null && _a !== void 0 ? _a : (ownerRef.current = {});
|
|
70
|
+
const owner = ownerRef.current;
|
|
71
|
+
useLayoutEffect(() => context.registerContent(owner), [context.registerContent, owner]);
|
|
72
|
+
return context.contentOwner === owner
|
|
73
|
+
? renderProjection(context.projection, "content")
|
|
74
|
+
: null;
|
|
75
|
+
}
|
|
@@ -1,22 +1,60 @@
|
|
|
1
|
-
import { coreCommandEnvelopeSchema, } from "@getuserfeedback/protocol";
|
|
1
|
+
import { appEventPayloadSchema, coreCommandEnvelopeSchema, } from "@getuserfeedback/protocol";
|
|
2
2
|
import { eventHandleInvalidatedSchema, } from "@getuserfeedback/protocol/internal/core-handle-invalidation";
|
|
3
3
|
import { eventCommandSettledSchema, } from "@getuserfeedback/protocol/internal/core-host-command-settlement";
|
|
4
4
|
import { CORE_SESSION_FAILED_CODE, eventCoreSessionFailedSchema, } from "@getuserfeedback/protocol/internal/core-session-failure";
|
|
5
|
+
import { buildRpcResponse, rpcRequestSchema, } from "@getuserfeedback/protocol/internal/v3-rpc-messages";
|
|
5
6
|
const DISPOSED_MESSAGE = "Core command channel is disposed";
|
|
7
|
+
const TRAFFIC_APP_EVENT_RPC_METHOD = "traffic.appEvent";
|
|
6
8
|
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
7
9
|
export function createNativeCoreCommandChannel(input) {
|
|
8
10
|
const listeners = new Set();
|
|
9
11
|
let disposed = false;
|
|
10
12
|
const handleMessage = (value) => {
|
|
11
|
-
var _a, _b, _c, _d, _e;
|
|
13
|
+
var _a, _b, _c, _d, _e, _f;
|
|
12
14
|
if (disposed || !isRecord(value)) {
|
|
13
15
|
return;
|
|
14
16
|
}
|
|
17
|
+
if (value.t === "getuserfeedback:rpc:request") {
|
|
18
|
+
const parsed = rpcRequestSchema.safeParse(value);
|
|
19
|
+
if (!parsed.success ||
|
|
20
|
+
parsed.data.gen !== input.coreConnection.generation ||
|
|
21
|
+
parsed.data.method !== TRAFFIC_APP_EVENT_RPC_METHOD) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const params = isRecord(parsed.data.params) ? parsed.data.params : null;
|
|
25
|
+
const instanceId = params && typeof params.instanceId === "string"
|
|
26
|
+
? params.instanceId.trim()
|
|
27
|
+
: "";
|
|
28
|
+
const payload = params
|
|
29
|
+
? appEventPayloadSchema.safeParse(params.payload)
|
|
30
|
+
: { success: false };
|
|
31
|
+
const result = instanceId.length > 0 && payload.success
|
|
32
|
+
? { allowed: true, payload: payload.data }
|
|
33
|
+
: { allowed: false };
|
|
34
|
+
try {
|
|
35
|
+
input.coreConnection.transport.postMessage(buildRpcResponse({
|
|
36
|
+
gen: parsed.data.gen,
|
|
37
|
+
rpcId: parsed.data.rpcId,
|
|
38
|
+
ok: true,
|
|
39
|
+
result,
|
|
40
|
+
viewId: parsed.data.viewId,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
try {
|
|
45
|
+
(_a = input.onInvalidMessage) === null || _a === void 0 ? void 0 : _a.call(input, error instanceof Error ? error : new Error(String(error)), value);
|
|
46
|
+
}
|
|
47
|
+
catch (_g) {
|
|
48
|
+
// Best-effort analytics and diagnostics cannot fail the Core session.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
15
53
|
if (value.t === "getuserfeedback:event:error" &&
|
|
16
54
|
value.code === CORE_SESSION_FAILED_CODE) {
|
|
17
55
|
const parsed = eventCoreSessionFailedSchema.safeParse(value);
|
|
18
56
|
if (!parsed.success) {
|
|
19
|
-
(
|
|
57
|
+
(_b = input.onInvalidMessage) === null || _b === void 0 ? void 0 : _b.call(input, parsed.error instanceof Error
|
|
20
58
|
? parsed.error
|
|
21
59
|
: new Error("Invalid Core session failure message"), value);
|
|
22
60
|
return;
|
|
@@ -24,13 +62,13 @@ export function createNativeCoreCommandChannel(input) {
|
|
|
24
62
|
if (parsed.data.gen !== input.coreConnection.generation) {
|
|
25
63
|
return;
|
|
26
64
|
}
|
|
27
|
-
(
|
|
65
|
+
(_c = input.onSessionFailure) === null || _c === void 0 ? void 0 : _c.call(input, new Error(parsed.data.details.message));
|
|
28
66
|
return;
|
|
29
67
|
}
|
|
30
68
|
if (value.t === "getuserfeedback:event:commandSettled") {
|
|
31
69
|
const parsed = eventCommandSettledSchema.safeParse(value);
|
|
32
70
|
if (!parsed.success) {
|
|
33
|
-
(
|
|
71
|
+
(_d = input.onInvalidMessage) === null || _d === void 0 ? void 0 : _d.call(input, parsed.error instanceof Error
|
|
34
72
|
? parsed.error
|
|
35
73
|
: new Error("Invalid core command settlement message"), value);
|
|
36
74
|
return;
|
|
@@ -48,7 +86,7 @@ export function createNativeCoreCommandChannel(input) {
|
|
|
48
86
|
}
|
|
49
87
|
const parsed = eventHandleInvalidatedSchema.safeParse(value);
|
|
50
88
|
if (!parsed.success) {
|
|
51
|
-
(
|
|
89
|
+
(_e = input.onInvalidMessage) === null || _e === void 0 ? void 0 : _e.call(input, parsed.error instanceof Error
|
|
52
90
|
? parsed.error
|
|
53
91
|
: new Error("Invalid core handle invalidation message"), value);
|
|
54
92
|
return;
|
|
@@ -56,7 +94,7 @@ export function createNativeCoreCommandChannel(input) {
|
|
|
56
94
|
if (parsed.data.gen !== input.coreConnection.generation) {
|
|
57
95
|
return;
|
|
58
96
|
}
|
|
59
|
-
(
|
|
97
|
+
(_f = input.onHandleInvalidated) === null || _f === void 0 ? void 0 : _f.call(input, parsed.data);
|
|
60
98
|
};
|
|
61
99
|
const unsubscribeTransport = input.coreConnection.transport.subscribe(handleMessage);
|
|
62
100
|
const channel = {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Client } from "./client.js";
|
|
2
|
+
import type { NativeViewProjection } from "./native-view-host-projection.js";
|
|
3
|
+
export type NativeFlowContainerContextValue = Readonly<{
|
|
4
|
+
client: Client;
|
|
5
|
+
contentOwner: object | null;
|
|
6
|
+
projection: NativeViewProjection | null;
|
|
7
|
+
registerContent: (owner: object) => () => void;
|
|
8
|
+
}>;
|
|
9
|
+
export declare const NativeFlowContainerContext: import("react").Context<Readonly<{
|
|
10
|
+
client: Client;
|
|
11
|
+
contentOwner: object | null;
|
|
12
|
+
projection: NativeViewProjection | null;
|
|
13
|
+
registerContent: (owner: object) => () => void;
|
|
14
|
+
}> | null>;
|
|
@@ -3,7 +3,7 @@ export declare function createNativeProviderClientController(input: {
|
|
|
3
3
|
instanceId: string;
|
|
4
4
|
onError?: (error: Error) => void;
|
|
5
5
|
}): {
|
|
6
|
-
client: import("./
|
|
6
|
+
client: import("./client.js").Client;
|
|
7
7
|
directSessionController: import("./native-direct-command-session-controller.js").NativeDirectCommandSessionController;
|
|
8
8
|
dispose: () => void;
|
|
9
9
|
onHandleInvalidated: (message: EventHandleInvalidated) => void;
|
|
@@ -1,43 +1,14 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
import type { FlowRun } from "./client.js";
|
|
4
|
-
type IdentifyTraits = Record<string, unknown>;
|
|
5
|
-
type TrackProperties = Record<string, AppEventJsonValue>;
|
|
1
|
+
import type { PublicCommandPayload } from "@getuserfeedback/protocol";
|
|
2
|
+
import type { Client } from "./client.js";
|
|
6
3
|
type NativeProviderClientCommand = Extract<PublicCommandPayload, {
|
|
7
4
|
kind: "configure" | "identify" | "track" | "close" | "reset" | "updateHostContext" | "emitHostSignal";
|
|
8
5
|
}>;
|
|
9
|
-
type
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
externalIds?: AppEventExternalId[];
|
|
14
|
-
};
|
|
15
|
-
type HostContext = Extract<PublicCommandPayload, {
|
|
16
|
-
kind: "updateHostContext";
|
|
17
|
-
}>["context"];
|
|
18
|
-
interface NativeProviderCommandOptions {
|
|
19
|
-
idempotencyKey?: string;
|
|
20
|
-
}
|
|
21
|
-
export interface NativeProviderClient<TResult> {
|
|
22
|
-
readonly instanceId: string;
|
|
23
|
-
configure: (opts: ConfigureOptions, options?: NativeProviderCommandOptions) => Promise<TResult>;
|
|
24
|
-
identify: {
|
|
25
|
-
(userId: string, traits?: IdentifyTraits, options?: IdentifyOptions): Promise<TResult>;
|
|
26
|
-
(traits: IdentifyTraits, placeholder: undefined, options?: IdentifyOptions): Promise<TResult>;
|
|
27
|
-
(traits: IdentifyTraits, options?: IdentifyOptions): Promise<TResult>;
|
|
28
|
-
};
|
|
29
|
-
track: (eventName: string, properties?: TrackProperties, options?: TrackOptions) => Promise<TResult>;
|
|
30
|
-
flow: (flowId: string) => FlowRun;
|
|
31
|
-
close: () => Promise<TResult>;
|
|
32
|
-
reset: (options?: NativeProviderCommandOptions) => Promise<TResult>;
|
|
33
|
-
updateHostContext: (context: HostContext, options?: NativeProviderCommandOptions) => Promise<TResult>;
|
|
34
|
-
emitHostSignal: (name: string, data?: unknown, options?: NativeProviderCommandOptions) => Promise<TResult>;
|
|
35
|
-
}
|
|
36
|
-
type NativeProviderClientOptions<TResult> = {
|
|
37
|
-
dispatchCommand: (command: NativeProviderClientCommand, options?: NativeProviderCommandOptions) => Promise<TResult>;
|
|
38
|
-
flow: (flowId: string) => FlowRun;
|
|
6
|
+
type NativeProviderCommandOptions = NonNullable<Parameters<Client["configure"]>[1]>;
|
|
7
|
+
type NativeProviderClientOptions = {
|
|
8
|
+
dispatchCommand: (command: NativeProviderClientCommand, options?: NativeProviderCommandOptions) => Promise<void>;
|
|
9
|
+
flow: Client["flow"];
|
|
39
10
|
instanceId: string;
|
|
40
11
|
};
|
|
41
12
|
export declare const parseRequiredString: (name: string, value?: string) => string;
|
|
42
|
-
export declare function createNativeProviderClient
|
|
13
|
+
export declare function createNativeProviderClient({ dispatchCommand, flow, instanceId, }: NativeProviderClientOptions): Client;
|
|
43
14
|
export {};
|
|
@@ -34,20 +34,25 @@ const toIdentifyCommandPayload = (identifyInput, traitsOrOptions, options) => {
|
|
|
34
34
|
};
|
|
35
35
|
};
|
|
36
36
|
export function createNativeProviderClient({ dispatchCommand, flow, instanceId, }) {
|
|
37
|
+
const track = (eventName, properties, options) => {
|
|
38
|
+
assertNoSegmentExternalIdsInValueArgument({
|
|
39
|
+
argument: properties,
|
|
40
|
+
commandName: "track",
|
|
41
|
+
valueArgumentName: "properties",
|
|
42
|
+
callShape: "track(eventName, properties, { externalIds })",
|
|
43
|
+
});
|
|
44
|
+
return dispatchCommand(Object.assign(Object.assign({ kind: "track", origin: "customer", event: parseRequiredString("eventName", eventName) }, (properties === undefined ? {} : { properties })), ((options === null || options === void 0 ? void 0 : options.externalIds) === undefined
|
|
45
|
+
? {}
|
|
46
|
+
: { context: { externalIds: options.externalIds } })));
|
|
47
|
+
};
|
|
37
48
|
return {
|
|
38
49
|
instanceId,
|
|
39
50
|
configure: (opts, options) => dispatchCommand({ kind: "configure", opts }, options),
|
|
40
51
|
identify: ((identifyInput, traitsOrOptions, options) => dispatchCommand(toIdentifyCommandPayload(identifyInput, traitsOrOptions, options))),
|
|
41
|
-
track
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
valueArgumentName: "properties",
|
|
46
|
-
callShape: "track(eventName, properties, { externalIds })",
|
|
47
|
-
});
|
|
48
|
-
return dispatchCommand(Object.assign(Object.assign({ kind: "track", origin: "customer", event: parseRequiredString("eventName", eventName) }, (properties === undefined ? {} : { properties })), ((options === null || options === void 0 ? void 0 : options.externalIds) === undefined
|
|
49
|
-
? {}
|
|
50
|
-
: { context: { externalIds: options.externalIds } })));
|
|
52
|
+
track,
|
|
53
|
+
graph: {
|
|
54
|
+
connect: (relationship) => track("gx.graph.relationship.connected", relationship),
|
|
55
|
+
disconnect: (relationship) => track("gx.graph.relationship.disconnected", relationship),
|
|
51
56
|
},
|
|
52
57
|
flow,
|
|
53
58
|
close: () => dispatchCommand({ kind: "close" }),
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type ComponentType, type ReactElement, type ReactNode } from "react";
|
|
2
2
|
import type { Client, ClientOptions } from "./client.js";
|
|
3
3
|
export type { Client, ClientOptions, FlowRun } from "./client.js";
|
|
4
|
+
export type { UseFlowContainerReturn } from "./flow-container.js";
|
|
5
|
+
export { FlowContent, useFlowContainer } from "./flow-container.js";
|
|
4
6
|
/**
|
|
5
7
|
* Custom WebView implementation used by the native runtime.
|
|
6
8
|
*
|
package/dist/native-provider.js
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
1
12
|
import { parseInitOptions } from "@getuserfeedback/protocol";
|
|
2
|
-
import { createUniqueId
|
|
3
|
-
import { createContext, createElement, Fragment, useCallback, useContext, useEffect, useRef, useState, } from "react";
|
|
13
|
+
import { createUniqueId } from "@getuserfeedback/protocol/host";
|
|
14
|
+
import { createContext, createElement, Fragment, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
|
|
15
|
+
import { NativeStandardFlowContainer } from "./flow-container.js";
|
|
16
|
+
import { NativeFlowContainerContext, } from "./native-flow-container-context.js";
|
|
4
17
|
import { createNativeProviderClientController } from "./native-provider-client-controller.js";
|
|
18
|
+
import { REACT_NATIVE_PINNED_CORE_URL } from "./native-runtime-endpoints.js";
|
|
5
19
|
import { NativeProviderRuntime } from "./native-runtime-root.js";
|
|
20
|
+
export { FlowContent, useFlowContainer } from "./flow-container.js";
|
|
6
21
|
const GetUserFeedbackContext = createContext(null);
|
|
7
22
|
const toClientOptionsKey = (value) => {
|
|
8
23
|
if (value === null || typeof value !== "object") {
|
|
@@ -18,6 +33,26 @@ const toClientOptionsKey = (value) => {
|
|
|
18
33
|
.map((key) => `${JSON.stringify(key)}:${toClientOptionsKey(record[key])}`)
|
|
19
34
|
.join(",")}}`;
|
|
20
35
|
};
|
|
36
|
+
const compareClientOptionsKeys = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
37
|
+
const parseNativeInitOptions = (_a) => {
|
|
38
|
+
var { capabilities } = _a, clientOptions = __rest(_a, ["capabilities"]);
|
|
39
|
+
const initOptions = parseInitOptions(Object.assign(Object.assign({}, clientOptions), (capabilities !== undefined
|
|
40
|
+
? {
|
|
41
|
+
hostCapabilities: capabilities.map((capability) => typeof capability === "string"
|
|
42
|
+
? { key: capability }
|
|
43
|
+
: Object.assign({}, capability)),
|
|
44
|
+
}
|
|
45
|
+
: {})));
|
|
46
|
+
const hostCapabilities = initOptions.hostCapabilities;
|
|
47
|
+
const normalizedHostCapabilities = hostCapabilities === null || hostCapabilities === void 0 ? void 0 : hostCapabilities.map((capability) => ({
|
|
48
|
+
capability,
|
|
49
|
+
key: toClientOptionsKey(capability),
|
|
50
|
+
})).sort((left, right) => compareClientOptionsKeys(left.key, right.key)).filter((entry, index, entries) => { var _a; return ((_a = entries[index - 1]) === null || _a === void 0 ? void 0 : _a.key) !== entry.key; }).map(({ capability }) => capability);
|
|
51
|
+
return Object.assign(Object.assign({}, initOptions), { hostCapabilities: normalizedHostCapabilities === undefined ||
|
|
52
|
+
normalizedHostCapabilities.length === 0
|
|
53
|
+
? undefined
|
|
54
|
+
: normalizedHostCapabilities });
|
|
55
|
+
};
|
|
21
56
|
export function GetUserFeedbackProvider({ children, clientOptions, instanceId: instanceIdProp, onError, webViewComponent, }) {
|
|
22
57
|
var _a, _b;
|
|
23
58
|
const [instanceId] = useState(() => {
|
|
@@ -26,9 +61,12 @@ export function GetUserFeedbackProvider({ children, clientOptions, instanceId: i
|
|
|
26
61
|
? normalizedInstanceId
|
|
27
62
|
: createUniqueId("rn");
|
|
28
63
|
});
|
|
29
|
-
const
|
|
64
|
+
const [contentOwner, setContentOwner] = useState(null);
|
|
65
|
+
const [projection, setProjection] = useState(null);
|
|
66
|
+
const contentOwnerRef = useRef(null);
|
|
67
|
+
const normalizedClientOptions = parseNativeInitOptions(clientOptions);
|
|
30
68
|
const clientOptionsKey = toClientOptionsKey(normalizedClientOptions);
|
|
31
|
-
const coreUrl = (_b = (_a = normalizedClientOptions.runtimeEndpoints) === null || _a === void 0 ? void 0 : _a.coreUrl) !== null && _b !== void 0 ? _b :
|
|
69
|
+
const coreUrl = (_b = (_a = normalizedClientOptions.runtimeEndpoints) === null || _a === void 0 ? void 0 : _a.coreUrl) !== null && _b !== void 0 ? _b : REACT_NATIVE_PINNED_CORE_URL;
|
|
32
70
|
const onErrorRef = useRef(onError);
|
|
33
71
|
onErrorRef.current = onError;
|
|
34
72
|
const reportError = useCallback((error) => {
|
|
@@ -48,6 +86,29 @@ export function GetUserFeedbackProvider({ children, clientOptions, instanceId: i
|
|
|
48
86
|
});
|
|
49
87
|
}
|
|
50
88
|
const controller = controllerRef.current;
|
|
89
|
+
const attachViewProjection = useCallback((nextProjection) => {
|
|
90
|
+
setProjection(nextProjection);
|
|
91
|
+
return () => setProjection((current) => current === nextProjection ? null : current);
|
|
92
|
+
}, []);
|
|
93
|
+
const registerContent = useCallback((owner) => {
|
|
94
|
+
if (contentOwnerRef.current && contentOwnerRef.current !== owner) {
|
|
95
|
+
throw new Error("Only one FlowContent can be mounted in a GetUserFeedbackProvider");
|
|
96
|
+
}
|
|
97
|
+
contentOwnerRef.current = owner;
|
|
98
|
+
setContentOwner(owner);
|
|
99
|
+
return () => {
|
|
100
|
+
if (contentOwnerRef.current !== owner)
|
|
101
|
+
return;
|
|
102
|
+
contentOwnerRef.current = null;
|
|
103
|
+
setContentOwner(null);
|
|
104
|
+
};
|
|
105
|
+
}, []);
|
|
106
|
+
const flowContainerContext = useMemo(() => ({
|
|
107
|
+
client: controller.client,
|
|
108
|
+
contentOwner,
|
|
109
|
+
projection,
|
|
110
|
+
registerContent,
|
|
111
|
+
}), [contentOwner, controller.client, projection, registerContent]);
|
|
51
112
|
const lifecycleRevisionRef = useRef(0);
|
|
52
113
|
useEffect(() => {
|
|
53
114
|
lifecycleRevisionRef.current += 1;
|
|
@@ -66,7 +127,8 @@ export function GetUserFeedbackProvider({ children, clientOptions, instanceId: i
|
|
|
66
127
|
}
|
|
67
128
|
}, [controller]);
|
|
68
129
|
const handleHandleInvalidated = useCallback((message) => controller.onHandleInvalidated(message), [controller]);
|
|
69
|
-
return createElement(GetUserFeedbackContext.Provider, { value: controller.client }, createElement(Fragment, null, children, createElement(NativeProviderRuntime, {
|
|
130
|
+
return createElement(GetUserFeedbackContext.Provider, { value: controller.client }, createElement(NativeFlowContainerContext.Provider, { value: flowContainerContext }, createElement(Fragment, null, children, createElement(NativeStandardFlowContainer), createElement(NativeProviderRuntime, {
|
|
131
|
+
attachViewProjection,
|
|
70
132
|
coreUrl,
|
|
71
133
|
coreWebViewComponent: webViewComponent,
|
|
72
134
|
directSessionController: controller.directSessionController,
|
|
@@ -77,7 +139,7 @@ export function GetUserFeedbackProvider({ children, clientOptions, instanceId: i
|
|
|
77
139
|
onError: reportError,
|
|
78
140
|
onHandleInvalidated: handleHandleInvalidated,
|
|
79
141
|
viewWebViewComponent: webViewComponent,
|
|
80
|
-
})));
|
|
142
|
+
}))));
|
|
81
143
|
}
|
|
82
144
|
export function useGetUserFeedback() {
|
|
83
145
|
const client = useContext(GetUserFeedbackContext);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Native is an independently deployed direct host, so this stays pinned
|
|
3
|
+
* to a retained Core artifact matching its projection contract. Advance it
|
|
4
|
+
* only after the matching artifact exists on the CDN; see
|
|
5
|
+
* docs/specs/2026-08-02-flow-interaction-model.md (Projection protocol and
|
|
6
|
+
* rollout).
|
|
7
|
+
*/
|
|
8
|
+
export declare const REACT_NATIVE_PINNED_CORE_URL: "https://cdn.getuserfeedback.com/widget/core/v3/core.html";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Native is an independently deployed direct host, so this stays pinned
|
|
3
|
+
* to a retained Core artifact matching its projection contract. Advance it
|
|
4
|
+
* only after the matching artifact exists on the CDN; see
|
|
5
|
+
* docs/specs/2026-08-02-flow-interaction-model.md (Projection protocol and
|
|
6
|
+
* rollout).
|
|
7
|
+
*/
|
|
8
|
+
export const REACT_NATIVE_PINNED_CORE_URL = "https://cdn.getuserfeedback.com/widget/core/v3/core.html";
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import type { CoreCommandEnvelope } from "@getuserfeedback/protocol";
|
|
2
2
|
import type { EventHandleInvalidated } from "@getuserfeedback/protocol/internal/core-handle-invalidation";
|
|
3
|
-
import type {
|
|
3
|
+
import type { InitOptions } from "@getuserfeedback/sdk";
|
|
4
4
|
import { type ReactElement } from "react";
|
|
5
5
|
import { type CoreWebViewHostProps } from "./core-webview-host.js";
|
|
6
6
|
import { type NativeCoreCommandSession } from "./native-core-command-session.js";
|
|
7
7
|
import { type NativeDirectCommandSession } from "./native-direct-command-session.js";
|
|
8
8
|
import type { NativeDirectCommandSessionController } from "./native-direct-command-session-controller.js";
|
|
9
|
-
import { type NativeViewHostProjectionProps } from "./native-view-host-projection.js";
|
|
9
|
+
import { type NativeViewHostProjectionProps, type NativeViewProjection } from "./native-view-host-projection.js";
|
|
10
10
|
export type NativeRuntimeRootProps = {
|
|
11
11
|
coreUrl: string;
|
|
12
12
|
coreWebViewComponent?: CoreWebViewHostProps["webViewComponent"];
|
|
13
|
-
|
|
13
|
+
startupCommand?: NativeRuntimeInitCommand;
|
|
14
14
|
onCommandSessionChange?: (session: NativeCoreCommandSession | null) => void;
|
|
15
15
|
onError?: (error: Error) => void;
|
|
16
16
|
onHandleInvalidated?: (message: EventHandleInvalidated) => void;
|
|
17
17
|
onInvalidMessage?: (error: Error, value: unknown) => void;
|
|
18
18
|
onStartupTerminalError?: (error: Error) => void;
|
|
19
19
|
startupCommandSettlementTimeoutMs?: number;
|
|
20
|
+
attachViewProjection?: (projection: NativeViewProjection) => () => void;
|
|
20
21
|
viewNativeViewComponent?: NativeViewHostProjectionProps["nativeViewComponent"];
|
|
21
22
|
viewWebViewComponent?: NativeViewHostProjectionProps["webViewComponent"];
|
|
22
23
|
};
|
|
@@ -25,20 +26,13 @@ type NativeRuntimeInitCommand = Omit<CoreCommandEnvelope, "command"> & {
|
|
|
25
26
|
kind: "init";
|
|
26
27
|
}>;
|
|
27
28
|
};
|
|
28
|
-
type
|
|
29
|
-
command: Extract<CoreCommandEnvelope["command"], {
|
|
30
|
-
kind: "configure";
|
|
31
|
-
}>;
|
|
32
|
-
};
|
|
33
|
-
type NativeRuntimeStartupCommands = readonly [NativeRuntimeInitCommand] | readonly [NativeRuntimeInitCommand, NativeRuntimeConfigureCommand];
|
|
34
|
-
type NativeProviderRuntimeProps = Omit<NativeRuntimeRootProps, "onCommandSessionChange" | "onStartupTerminalError" | "startupCommands"> & {
|
|
29
|
+
type NativeProviderRuntimeProps = Omit<NativeRuntimeRootProps, "onCommandSessionChange" | "onStartupTerminalError" | "startupCommand"> & {
|
|
35
30
|
/** Stable, exclusive controller retained and disposed by the component that owns this runtime's mounted lifetime. */
|
|
36
31
|
directSessionController: NativeDirectCommandSessionController;
|
|
37
|
-
initialConfigureOptions?: ConfigureOptions;
|
|
38
32
|
initOptions: InitOptions;
|
|
39
33
|
instanceId: string;
|
|
40
34
|
onCommandSessionChange?: (session: NativeDirectCommandSession | null) => void;
|
|
41
35
|
};
|
|
42
|
-
export declare function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, onCommandSessionChange, onError, onHandleInvalidated, onInvalidMessage, onStartupTerminalError, startupCommandSettlementTimeoutMs,
|
|
43
|
-
export declare function NativeProviderRuntime({ directSessionController, initOptions,
|
|
36
|
+
export declare function NativeRuntimeRoot({ attachViewProjection, coreUrl, coreWebViewComponent, onCommandSessionChange, onError, onHandleInvalidated, onInvalidMessage, onStartupTerminalError, startupCommandSettlementTimeoutMs, startupCommand, viewNativeViewComponent, viewWebViewComponent, }: NativeRuntimeRootProps): ReactElement;
|
|
37
|
+
export declare function NativeProviderRuntime({ directSessionController, initOptions, instanceId, onCommandSessionChange, onError, ...runtimeProps }: NativeProviderRuntimeProps): ReactElement;
|
|
44
38
|
export {};
|
|
@@ -19,14 +19,7 @@ import { NativeViewHostProjection, } from "./native-view-host-projection.js";
|
|
|
19
19
|
import { createNativeViewRecordController, } from "./native-view-record-controller.js";
|
|
20
20
|
import { createProviderCommandEnvelope, REACT_NATIVE_CLIENT_META, } from "./provider-command-helpers.js";
|
|
21
21
|
const DEFAULT_STARTUP_COMMAND_SETTLEMENT_TIMEOUT_MS = 30000;
|
|
22
|
-
|
|
23
|
-
for (const command of input.commands) {
|
|
24
|
-
await input.commandSession.dispatch(command, {
|
|
25
|
-
settlementTimeoutMs: input.timeoutMs,
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, onCommandSessionChange, onError, onHandleInvalidated, onInvalidMessage, onStartupTerminalError, startupCommandSettlementTimeoutMs = DEFAULT_STARTUP_COMMAND_SETTLEMENT_TIMEOUT_MS, startupCommands, viewNativeViewComponent, viewWebViewComponent, }) {
|
|
22
|
+
export function NativeRuntimeRoot({ attachViewProjection, coreUrl, coreWebViewComponent, onCommandSessionChange, onError, onHandleInvalidated, onInvalidMessage, onStartupTerminalError, startupCommandSettlementTimeoutMs = DEFAULT_STARTUP_COMMAND_SETTLEMENT_TIMEOUT_MS, startupCommand, viewNativeViewComponent, viewWebViewComponent, }) {
|
|
30
23
|
const [coreHostRevision, setCoreHostRevision] = useState(0);
|
|
31
24
|
const [session, setSession] = useState(null);
|
|
32
25
|
const sessionRef = useRef(null);
|
|
@@ -42,8 +35,8 @@ export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, onCommandSess
|
|
|
42
35
|
onInvalidMessageRef.current = onInvalidMessage;
|
|
43
36
|
const onStartupTerminalErrorRef = useRef(onStartupTerminalError);
|
|
44
37
|
onStartupTerminalErrorRef.current = onStartupTerminalError;
|
|
45
|
-
const
|
|
46
|
-
|
|
38
|
+
const startupCommandRef = useRef(startupCommand);
|
|
39
|
+
startupCommandRef.current = startupCommand;
|
|
47
40
|
const reportError = useCallback((error) => {
|
|
48
41
|
var _a;
|
|
49
42
|
try {
|
|
@@ -126,12 +119,15 @@ export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, onCommandSess
|
|
|
126
119
|
setSession(nextSession);
|
|
127
120
|
notifyCommandSessionObserver(nextSession.commandSessionObserver, commandSessionOwner.session);
|
|
128
121
|
};
|
|
129
|
-
const
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
122
|
+
const startupCommand = startupCommandRef.current;
|
|
123
|
+
if (startupCommand) {
|
|
124
|
+
// TODO(architecture): Reconcile direct-runtime startup command settlement
|
|
125
|
+
// with view projection. Android smoke can project the opened view while the
|
|
126
|
+
// public open request still times out waiting for Core's settlement.
|
|
127
|
+
// [principle=one-authoritative-command-lifecycle] [scope=slice] [priority=high] [impact=high] [risk=high] [epic=runtime-neutral-view-host-orchestration] [epic_order=2]
|
|
128
|
+
void commandSessionOwner.session
|
|
129
|
+
.dispatch(startupCommand, {
|
|
130
|
+
settlementTimeoutMs: startupCommandSettlementTimeoutMs,
|
|
135
131
|
})
|
|
136
132
|
.then(commitSession)
|
|
137
133
|
.catch((error) => {
|
|
@@ -206,6 +202,23 @@ export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, onCommandSess
|
|
|
206
202
|
}
|
|
207
203
|
}
|
|
208
204
|
}, [notifyCommandSessionObserver]);
|
|
205
|
+
useLayoutEffect(() => {
|
|
206
|
+
if (!session || !attachViewProjection)
|
|
207
|
+
return;
|
|
208
|
+
return attachViewProjection({
|
|
209
|
+
controller: session.controller,
|
|
210
|
+
key: session.key,
|
|
211
|
+
nativeViewComponent: viewNativeViewComponent,
|
|
212
|
+
onInvalidMessage,
|
|
213
|
+
webViewComponent: viewWebViewComponent,
|
|
214
|
+
});
|
|
215
|
+
}, [
|
|
216
|
+
attachViewProjection,
|
|
217
|
+
onInvalidMessage,
|
|
218
|
+
session,
|
|
219
|
+
viewNativeViewComponent,
|
|
220
|
+
viewWebViewComponent,
|
|
221
|
+
]);
|
|
209
222
|
return createElement(Fragment, null, createElement(CoreWebViewHost, {
|
|
210
223
|
coreUrl,
|
|
211
224
|
key: `${coreUrl}:${coreHostRevision}`,
|
|
@@ -214,7 +227,7 @@ export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, onCommandSess
|
|
|
214
227
|
onDisconnected: handleDisconnected,
|
|
215
228
|
onInvalidMessage,
|
|
216
229
|
webViewComponent: coreWebViewComponent,
|
|
217
|
-
}), session === null
|
|
230
|
+
}), session === null || attachViewProjection
|
|
218
231
|
? null
|
|
219
232
|
: createElement(NativeViewHostProjection, {
|
|
220
233
|
controller: session.controller,
|
|
@@ -225,7 +238,7 @@ export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, onCommandSess
|
|
|
225
238
|
}));
|
|
226
239
|
}
|
|
227
240
|
export function NativeProviderRuntime(_a) {
|
|
228
|
-
var { directSessionController, initOptions,
|
|
241
|
+
var { directSessionController, initOptions, instanceId, onCommandSessionChange, onError } = _a, runtimeProps = __rest(_a, ["directSessionController", "initOptions", "instanceId", "onCommandSessionChange", "onError"]);
|
|
229
242
|
const directSessionRef = useRef(null);
|
|
230
243
|
useLayoutEffect(() => {
|
|
231
244
|
directSessionController.beginAttachmentAttempt();
|
|
@@ -254,22 +267,12 @@ export function NativeProviderRuntime(_a) {
|
|
|
254
267
|
}
|
|
255
268
|
onCommandSessionChange === null || onCommandSessionChange === void 0 ? void 0 : onCommandSessionChange(directSessionController.session);
|
|
256
269
|
}, [directSessionController, onCommandSessionChange]);
|
|
257
|
-
const
|
|
258
|
-
|
|
270
|
+
const startupCommand = useMemo(() => {
|
|
271
|
+
return createProviderCommandEnvelope({
|
|
259
272
|
clientMeta: REACT_NATIVE_CLIENT_META,
|
|
260
273
|
command: { kind: "init", opts: initOptions },
|
|
261
274
|
instanceId,
|
|
262
275
|
});
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
return [
|
|
267
|
-
initCommand,
|
|
268
|
-
createProviderCommandEnvelope({
|
|
269
|
-
command: { kind: "configure", opts: initialConfigureOptions },
|
|
270
|
-
instanceId,
|
|
271
|
-
}),
|
|
272
|
-
];
|
|
273
|
-
}, [initialConfigureOptions, initOptions, instanceId]);
|
|
274
|
-
return createElement(NativeRuntimeRoot, Object.assign(Object.assign({}, runtimeProps), { onCommandSessionChange: handleCommandSessionChange, onError, onStartupTerminalError: handleStartupTerminalError, startupCommands }));
|
|
276
|
+
}, [initOptions, instanceId]);
|
|
277
|
+
return createElement(NativeRuntimeRoot, Object.assign(Object.assign({}, runtimeProps), { onCommandSessionChange: handleCommandSessionChange, onError, onStartupTerminalError: handleStartupTerminalError, startupCommand }));
|
|
275
278
|
}
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import { type ComponentType, type ReactElement } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type { NativeViewPresentationMode } from "./native-view-presentation-surface.js";
|
|
3
|
+
import type { NativeViewRecord, NativeViewRecordController } from "./native-view-record-controller.js";
|
|
3
4
|
export type NativeViewHostProjectionProps = {
|
|
4
5
|
controller: NativeViewRecordController;
|
|
5
6
|
onInvalidMessage?: (error: Error, value: unknown) => void;
|
|
6
7
|
nativeViewComponent?: ComponentType<Record<string, unknown>>;
|
|
8
|
+
presentationMode?: NativeViewPresentationMode;
|
|
7
9
|
webViewComponent?: ComponentType<Record<string, unknown>>;
|
|
8
10
|
};
|
|
9
|
-
export
|
|
11
|
+
export type NativeViewProjection = Readonly<Omit<NativeViewHostProjectionProps, "presentationMode"> & {
|
|
12
|
+
key: number;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function NativeProjectedViewContent({ controller, onInvalidMessage, record, webViewComponent, }: {
|
|
15
|
+
controller: NativeViewRecordController;
|
|
16
|
+
onInvalidMessage?: (error: Error, value: unknown) => void;
|
|
17
|
+
record: NativeViewRecord;
|
|
18
|
+
webViewComponent?: ComponentType<Record<string, unknown>>;
|
|
19
|
+
}): ReactElement;
|
|
20
|
+
export declare function NativeViewHostProjection({ controller, nativeViewComponent, onInvalidMessage, presentationMode, webViewComponent, }: NativeViewHostProjectionProps): ReactElement;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { createElement, Fragment, useCallback, useEffect, useLayoutEffect, useRef, useSyncExternalStore, } from "react";
|
|
2
2
|
import { NativeViewPresentationSurface } from "./native-view-presentation-surface.js";
|
|
3
3
|
import { ViewWebViewHost } from "./view-webview-host.js";
|
|
4
|
-
function
|
|
5
|
-
var _a;
|
|
4
|
+
export function NativeProjectedViewContent({ controller, onInvalidMessage, record, webViewComponent, }) {
|
|
6
5
|
const detachConnectionRef = useRef(null);
|
|
7
6
|
const detachConnection = useCallback(() => {
|
|
8
7
|
const detach = detachConnectionRef.current;
|
|
@@ -18,6 +17,26 @@ function NativeViewHostProjectionEntry({ controller, nativeViewComponent, onInva
|
|
|
18
17
|
detachConnectionRef.current = controllerDetach;
|
|
19
18
|
}
|
|
20
19
|
}, [controller, record.allocationId]);
|
|
20
|
+
useEffect(() => detachConnection, [detachConnection]);
|
|
21
|
+
return createElement(ViewWebViewHost, {
|
|
22
|
+
generation: record.generation,
|
|
23
|
+
onActivationRetryExhausted: (error) => {
|
|
24
|
+
controller.failViewAllocation({
|
|
25
|
+
allocationId: record.allocationId,
|
|
26
|
+
error,
|
|
27
|
+
viewId: record.viewId,
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
onConnected: handleConnected,
|
|
31
|
+
onDisconnected: detachConnection,
|
|
32
|
+
onInvalidMessage,
|
|
33
|
+
srcdoc: record.srcdoc,
|
|
34
|
+
viewId: record.viewId,
|
|
35
|
+
webViewComponent,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function NativeViewHostProjectionEntry({ controller, nativeViewComponent, onInvalidMessage, presentationMode, record, webViewComponent, }) {
|
|
39
|
+
var _a;
|
|
21
40
|
const layoutTransactionId = (_a = record.size) === null || _a === void 0 ? void 0 : _a.layoutTransactionId;
|
|
22
41
|
useLayoutEffect(() => {
|
|
23
42
|
if (!layoutTransactionId) {
|
|
@@ -48,30 +67,20 @@ function NativeViewHostProjectionEntry({ controller, nativeViewComponent, onInva
|
|
|
48
67
|
viewId: record.viewId,
|
|
49
68
|
});
|
|
50
69
|
});
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
generation: record.generation,
|
|
54
|
-
onActivationRetryExhausted: (error) => {
|
|
55
|
-
controller.failViewAllocation({
|
|
56
|
-
allocationId: record.allocationId,
|
|
57
|
-
error,
|
|
58
|
-
viewId: record.viewId,
|
|
59
|
-
});
|
|
60
|
-
},
|
|
61
|
-
onConnected: handleConnected,
|
|
62
|
-
onDisconnected: detachConnection,
|
|
70
|
+
return createElement(NativeViewPresentationSurface, { nativeViewComponent, presentationMode, record }, createElement(NativeProjectedViewContent, {
|
|
71
|
+
controller,
|
|
63
72
|
onInvalidMessage,
|
|
64
|
-
|
|
65
|
-
viewId: record.viewId,
|
|
73
|
+
record,
|
|
66
74
|
webViewComponent,
|
|
67
75
|
}));
|
|
68
76
|
}
|
|
69
|
-
export function NativeViewHostProjection({ controller, nativeViewComponent, onInvalidMessage, webViewComponent, }) {
|
|
77
|
+
export function NativeViewHostProjection({ controller, nativeViewComponent, onInvalidMessage, presentationMode, webViewComponent, }) {
|
|
70
78
|
const records = useSyncExternalStore(controller.subscribe, controller.getSnapshot, controller.getSnapshot);
|
|
71
79
|
return createElement(Fragment, null, records.map((record) => createElement(NativeViewHostProjectionEntry, {
|
|
72
80
|
controller,
|
|
73
81
|
nativeViewComponent,
|
|
74
82
|
onInvalidMessage,
|
|
83
|
+
presentationMode,
|
|
75
84
|
record,
|
|
76
85
|
webViewComponent,
|
|
77
86
|
key: record.allocationId,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type ComponentType, type ReactElement, type ReactNode } from "react";
|
|
2
2
|
import type { NativeViewRecord } from "./native-view-record-controller.js";
|
|
3
|
-
export
|
|
3
|
+
export type NativeViewPresentationMode = "content" | "overlay";
|
|
4
|
+
export declare function NativeViewPresentationSurface({ children, presentationMode, nativeViewComponent, record, }: {
|
|
4
5
|
children?: ReactNode;
|
|
6
|
+
presentationMode?: NativeViewPresentationMode;
|
|
5
7
|
nativeViewComponent?: ComponentType<Record<string, unknown>>;
|
|
6
8
|
record: NativeViewRecord;
|
|
7
9
|
}): ReactElement;
|
|
@@ -18,6 +18,11 @@ const HIDDEN_HOST_SURFACE_STYLE = {
|
|
|
18
18
|
const PRESENTED_HOST_SURFACE_STYLE = {
|
|
19
19
|
zIndex: 2147483647,
|
|
20
20
|
};
|
|
21
|
+
const CONTENT_HOST_SURFACE_STYLE = {
|
|
22
|
+
alignItems: "center",
|
|
23
|
+
maxHeight: "100%",
|
|
24
|
+
maxWidth: "100%",
|
|
25
|
+
};
|
|
21
26
|
const TOP_PLACEMENT_STYLE = {
|
|
22
27
|
justifyContent: "flex-start",
|
|
23
28
|
};
|
|
@@ -50,8 +55,30 @@ const resolveNativeView = () => {
|
|
|
50
55
|
}
|
|
51
56
|
return reactNativeModule.View;
|
|
52
57
|
};
|
|
53
|
-
export function NativeViewPresentationSurface({ children, nativeViewComponent, record, }) {
|
|
54
|
-
var _a, _b, _c
|
|
58
|
+
export function NativeViewPresentationSurface({ children, presentationMode = "overlay", nativeViewComponent, record, }) {
|
|
59
|
+
var _a, _b, _c;
|
|
60
|
+
const NativeView = nativeViewComponent !== null && nativeViewComponent !== void 0 ? nativeViewComponent : resolveNativeView();
|
|
61
|
+
const isPresented = record.isActive && record.size !== null;
|
|
62
|
+
const hostStyle = presentationMode === "content" && isPresented
|
|
63
|
+
? Object.assign(Object.assign({}, CONTENT_HOST_SURFACE_STYLE), { height: (_a = record.size) === null || _a === void 0 ? void 0 : _a.height, width: (_b = record.size) === null || _b === void 0 ? void 0 : _b.width }) : Object.assign(Object.assign(Object.assign({}, HOST_SURFACE_STYLE), (((_c = record.size) === null || _c === void 0 ? void 0 : _c.placement) === "docked-top"
|
|
64
|
+
? TOP_PLACEMENT_STYLE
|
|
65
|
+
: BOTTOM_PLACEMENT_STYLE)), (isPresented
|
|
66
|
+
? PRESENTED_HOST_SURFACE_STYLE
|
|
67
|
+
: HIDDEN_HOST_SURFACE_STYLE));
|
|
68
|
+
return createElement(NativeView, {
|
|
69
|
+
accessibilityElementsHidden: !isPresented,
|
|
70
|
+
importantForAccessibility: isPresented ? "auto" : "no-hide-descendants",
|
|
71
|
+
pointerEvents: isPresented ? "box-none" : "none",
|
|
72
|
+
style: hostStyle,
|
|
73
|
+
testID: `gx-native-view-presentation-${record.viewId}`,
|
|
74
|
+
}, createElement(NativeViewPresentationFrame, {
|
|
75
|
+
children,
|
|
76
|
+
nativeViewComponent,
|
|
77
|
+
record,
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
function NativeViewPresentationFrame({ children, nativeViewComponent, record, }) {
|
|
81
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
55
82
|
const NativeView = nativeViewComponent !== null && nativeViewComponent !== void 0 ? nativeViewComponent : resolveNativeView();
|
|
56
83
|
const isPresented = record.isActive && record.size !== null;
|
|
57
84
|
const hasMeasuredFrame = isPresented || ((_a = record.size) === null || _a === void 0 ? void 0 : _a.layoutTransactionId) !== undefined;
|
|
@@ -74,19 +101,9 @@ export function NativeViewPresentationSurface({ children, nativeViewComponent, r
|
|
|
74
101
|
borderTopRightRadius: cornerRadii.topRight,
|
|
75
102
|
};
|
|
76
103
|
return createElement(NativeView, {
|
|
77
|
-
accessibilityElementsHidden: !isPresented,
|
|
78
|
-
importantForAccessibility: isPresented ? "auto" : "no-hide-descendants",
|
|
79
|
-
pointerEvents: isPresented ? "box-none" : "none",
|
|
80
|
-
style: Object.assign(Object.assign(Object.assign({}, HOST_SURFACE_STYLE), (((_g = record.size) === null || _g === void 0 ? void 0 : _g.placement) === "docked-top"
|
|
81
|
-
? TOP_PLACEMENT_STYLE
|
|
82
|
-
: BOTTOM_PLACEMENT_STYLE)), (isPresented
|
|
83
|
-
? PRESENTED_HOST_SURFACE_STYLE
|
|
84
|
-
: HIDDEN_HOST_SURFACE_STYLE)),
|
|
85
|
-
testID: `gx-native-view-presentation-${record.viewId}`,
|
|
86
|
-
}, createElement(NativeView, {
|
|
87
104
|
pointerEvents: isPresented ? "auto" : "none",
|
|
88
105
|
style: hasMeasuredFrame
|
|
89
|
-
? Object.assign(Object.assign(Object.assign(Object.assign({}, MEASURED_VIEW_FRAME_STYLE), cornerRadiusStyle), frameShadowStyle), { height: (
|
|
106
|
+
? Object.assign(Object.assign(Object.assign(Object.assign({}, MEASURED_VIEW_FRAME_STYLE), cornerRadiusStyle), frameShadowStyle), { height: (_g = record.size) === null || _g === void 0 ? void 0 : _g.height, width: (_h = record.size) === null || _h === void 0 ? void 0 : _h.width }) : PREPARING_VIEW_FRAME_STYLE,
|
|
90
107
|
testID: `gx-native-view-frame-${record.viewId}`,
|
|
91
108
|
}, createElement(NativeView, {
|
|
92
109
|
style: Object.assign(Object.assign({}, VIEW_FRAME_CONTENT_STYLE), cornerRadiusStyle),
|
|
@@ -95,5 +112,5 @@ export function NativeViewPresentationSurface({ children, nativeViewComponent, r
|
|
|
95
112
|
pointerEvents: "none",
|
|
96
113
|
style: Object.assign(Object.assign(Object.assign(Object.assign({}, VIEW_FRAME_BORDER_OVERLAY_STYLE), cornerRadiusStyle), borderStyle), frameOverlayShadowStyle),
|
|
97
114
|
testID: `gx-native-view-frame-border-${record.viewId}`,
|
|
98
|
-
}))
|
|
115
|
+
}));
|
|
99
116
|
}
|
|
@@ -236,6 +236,7 @@ export function createNativeViewRecordController(input) {
|
|
|
236
236
|
layoutReportRevision: 0,
|
|
237
237
|
pendingCoreToViewMessages: [],
|
|
238
238
|
preparation,
|
|
239
|
+
retainedSize: null,
|
|
239
240
|
size: null,
|
|
240
241
|
srcdoc: action.srcdoc,
|
|
241
242
|
committedLayoutReportOccurrence: null,
|
|
@@ -541,6 +542,7 @@ export function createNativeViewRecordController(input) {
|
|
|
541
542
|
}
|
|
542
543
|
};
|
|
543
544
|
const handleMessage = (value) => {
|
|
545
|
+
var _a;
|
|
544
546
|
if (coreConnectionFailed ||
|
|
545
547
|
detached ||
|
|
546
548
|
disposed ||
|
|
@@ -554,6 +556,14 @@ export function createNativeViewRecordController(input) {
|
|
|
554
556
|
connected.data.viewId === viewId) {
|
|
555
557
|
const wasViewConnected = entry.isViewConnected;
|
|
556
558
|
entry.isViewConnected = true;
|
|
559
|
+
if (entry.size === null &&
|
|
560
|
+
((_a = entry.retainedSize) === null || _a === void 0 ? void 0 : _a.connectionId) === connection.connectionId) {
|
|
561
|
+
entry.size = entry.retainedSize.size;
|
|
562
|
+
const readySize = connectionReadiness.recordSize(entry.size);
|
|
563
|
+
if (readySize) {
|
|
564
|
+
deliverReadiness(readySize);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
557
567
|
drainCoreToViewMessages(entry);
|
|
558
568
|
if (!wasViewConnected &&
|
|
559
569
|
!coreConnectionFailed &&
|
|
@@ -600,6 +610,10 @@ export function createNativeViewRecordController(input) {
|
|
|
600
610
|
}
|
|
601
611
|
const size = toViewSizeReport(parsed.data);
|
|
602
612
|
entry.size = size;
|
|
613
|
+
entry.retainedSize = {
|
|
614
|
+
connectionId: connection.connectionId,
|
|
615
|
+
size,
|
|
616
|
+
};
|
|
603
617
|
entry.layoutReportRevision += 1;
|
|
604
618
|
const readySize = connectionReadiness.recordSize(size);
|
|
605
619
|
if (readySize) {
|
|
@@ -636,6 +650,10 @@ export function createNativeViewRecordController(input) {
|
|
|
636
650
|
entry.viewConnection = connection;
|
|
637
651
|
entry.isViewConnected = false;
|
|
638
652
|
entry.committedLayoutReportOccurrence = null;
|
|
653
|
+
const retainedSize = entry.retainedSize;
|
|
654
|
+
if ((retainedSize === null || retainedSize === void 0 ? void 0 : retainedSize.connectionId) !== connection.connectionId) {
|
|
655
|
+
entry.retainedSize = null;
|
|
656
|
+
}
|
|
639
657
|
previousDetach === null || previousDetach === void 0 ? void 0 : previousDetach();
|
|
640
658
|
if (entry.size !== null) {
|
|
641
659
|
entry.size = null;
|
package/dist/version.js
CHANGED
|
@@ -3,4 +3,4 @@ const reactNativeSdkVersion = typeof __GX_REACT_NATIVE_SDK_VERSION__ === "string
|
|
|
3
3
|
: "";
|
|
4
4
|
// Build scripts patch this fallback to the package version for published artifacts.
|
|
5
5
|
// Source-linked workspace usage keeps the local fallback.
|
|
6
|
-
export const REACT_NATIVE_SDK_VERSION = reactNativeSdkVersion.length > 0 ? reactNativeSdkVersion : "5.0
|
|
6
|
+
export const REACT_NATIVE_SDK_VERSION = reactNativeSdkVersion.length > 0 ? reactNativeSdkVersion : "5.1.0";
|
|
@@ -10,6 +10,8 @@ const VIEW_WEBVIEW_BRIDGE_CONFIG = {
|
|
|
10
10
|
connectTimeoutMessage: "Timed out connecting view to the native host",
|
|
11
11
|
connectTimeoutMs: 30000,
|
|
12
12
|
inactiveConnectionMessage: "View WebView bridge connection is not active",
|
|
13
|
+
reactivateOnNativeEpochChange: true,
|
|
14
|
+
throwOnUnavailablePost: true,
|
|
13
15
|
};
|
|
14
16
|
export const parseViewWebViewBridgeMessage = (value) => parseWebViewConnectionBridgeMessage(value, {
|
|
15
17
|
invalidIdentity: "Invalid view WebView bridge message identity",
|
|
@@ -40,16 +42,16 @@ const buildViewHostBootstrapScript = () => {
|
|
|
40
42
|
var connection = bridge.createConnection({
|
|
41
43
|
connectionIdentity: { viewId: config.viewId },
|
|
42
44
|
generation: config.generation,
|
|
43
|
-
|
|
45
|
+
prepareActivation: function (activeTransport) {
|
|
44
46
|
transport = activeTransport;
|
|
45
47
|
if (typeof options.onMessage === "function") {
|
|
46
48
|
activeTransport.subscribe(options.onMessage);
|
|
47
49
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
50
|
+
},
|
|
51
|
+
onActivated: function (_activeTransport, reactivate) {
|
|
52
|
+
if (reactivate) {
|
|
53
|
+
window.dispatchEvent(new Event("resize"));
|
|
54
|
+
}
|
|
53
55
|
},
|
|
54
56
|
onError: options.onError
|
|
55
57
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ViewWebViewBridgeMessage } from "./view-webview-bridge-adapter.js";
|
|
2
2
|
import { type WebViewHostController } from "./webview-host-controller.js";
|
|
3
3
|
export type ViewWebViewHostConnection = {
|
|
4
|
+
connectionId: string;
|
|
4
5
|
generation: number;
|
|
5
6
|
transport: {
|
|
6
7
|
postMessage: (message: unknown) => void;
|
|
@@ -11,18 +11,34 @@ export const createViewWebViewHostController = (input) => {
|
|
|
11
11
|
buildActivationScript: buildViewWebViewBridgeScript,
|
|
12
12
|
buildHostMessageScript: buildViewWebViewHostMessageScript,
|
|
13
13
|
onConnected: (connection) => {
|
|
14
|
+
const listeners = new Set();
|
|
14
15
|
input.onConnected({
|
|
16
|
+
connectionId: connection.connectionId,
|
|
15
17
|
generation: connection.generation,
|
|
16
18
|
transport: {
|
|
17
19
|
postMessage: connection.postMessage,
|
|
18
|
-
subscribe: (listener) =>
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
subscribe: (listener) => {
|
|
21
|
+
listeners.add(listener);
|
|
22
|
+
const unsubscribe = connection.subscribe((message) => {
|
|
23
|
+
if (message.kind === "message") {
|
|
24
|
+
listener(message.message);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return () => {
|
|
28
|
+
listeners.delete(listener);
|
|
29
|
+
unsubscribe();
|
|
30
|
+
};
|
|
31
|
+
},
|
|
23
32
|
},
|
|
24
33
|
viewId: input.viewId,
|
|
25
34
|
});
|
|
35
|
+
for (const listener of listeners) {
|
|
36
|
+
listener({
|
|
37
|
+
gen: input.generation,
|
|
38
|
+
t: "getuserfeedback:action:viewConnect",
|
|
39
|
+
viewId: input.viewId,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
26
42
|
},
|
|
27
43
|
onDisconnected: input.onDisconnected,
|
|
28
44
|
onActivationRetryExhausted: input.onActivationRetryExhausted,
|
|
@@ -4,6 +4,8 @@ export type WebViewConnectionBridgeScriptConfig = {
|
|
|
4
4
|
connectTimeoutMessage: string;
|
|
5
5
|
connectTimeoutMs: number;
|
|
6
6
|
inactiveConnectionMessage: string;
|
|
7
|
+
reactivateOnNativeEpochChange: boolean;
|
|
8
|
+
throwOnUnavailablePost: boolean;
|
|
7
9
|
};
|
|
8
10
|
export declare const buildWebViewConnectionBridgeScript: (input: ({
|
|
9
11
|
phase: "documentStart";
|
|
@@ -8,6 +8,8 @@ export const buildWebViewConnectionBridgeScript = (input) => {
|
|
|
8
8
|
const bridgeProtocolVersion = serializeForJavaScript(input.config.bridgeProtocolVersion);
|
|
9
9
|
const connectTimeoutMessage = serializeForJavaScript(input.config.connectTimeoutMessage);
|
|
10
10
|
const inactiveConnectionMessage = serializeForJavaScript(input.config.inactiveConnectionMessage);
|
|
11
|
+
const reactivateOnNativeEpochChange = String(input.config.reactivateOnNativeEpochChange);
|
|
12
|
+
const throwOnUnavailablePost = String(input.config.throwOnUnavailablePost);
|
|
11
13
|
return `
|
|
12
14
|
(function () {
|
|
13
15
|
var bridgeId = ${bridgeId};
|
|
@@ -15,6 +17,8 @@ export const buildWebViewConnectionBridgeScript = (input) => {
|
|
|
15
17
|
var bridgeProtocolVersion = ${bridgeProtocolVersion};
|
|
16
18
|
var connectTimeoutMessage = ${connectTimeoutMessage};
|
|
17
19
|
var inactiveConnectionMessage = ${inactiveConnectionMessage};
|
|
20
|
+
var reactivateOnNativeEpochChange = ${reactivateOnNativeEpochChange};
|
|
21
|
+
var throwOnUnavailablePost = ${throwOnUnavailablePost};
|
|
18
22
|
var existingBridge = window[bridgeGlobalKey];
|
|
19
23
|
|
|
20
24
|
if (
|
|
@@ -22,11 +26,11 @@ export const buildWebViewConnectionBridgeScript = (input) => {
|
|
|
22
26
|
existingBridge.protocolVersion === bridgeProtocolVersion &&
|
|
23
27
|
existingBridge.bridgeId === bridgeId
|
|
24
28
|
) {
|
|
25
|
-
var
|
|
29
|
+
var activationResult = false;
|
|
26
30
|
if (${String(activateNative)}) {
|
|
27
|
-
|
|
31
|
+
activationResult = existingBridge.activateNative(${String(activationEpoch)});
|
|
28
32
|
}
|
|
29
|
-
if (
|
|
33
|
+
if (activationResult === false) {
|
|
30
34
|
existingBridge.notifyConnected();
|
|
31
35
|
}
|
|
32
36
|
return;
|
|
@@ -41,6 +45,7 @@ export const buildWebViewConnectionBridgeScript = (input) => {
|
|
|
41
45
|
var isClosed = false;
|
|
42
46
|
var isNativeActivated = ${String(activateNative)};
|
|
43
47
|
var nativeEpoch = ${String(activationEpoch)};
|
|
48
|
+
var activatedNativeEpoch = null;
|
|
44
49
|
|
|
45
50
|
function postToNative(message) {
|
|
46
51
|
var nativeBridge = window.ReactNativeWebView;
|
|
@@ -90,6 +95,7 @@ export const buildWebViewConnectionBridgeScript = (input) => {
|
|
|
90
95
|
connectPromise: null,
|
|
91
96
|
connectTimeout: null,
|
|
92
97
|
onActivated: options.onActivated,
|
|
98
|
+
prepareActivation: options.prepareActivation,
|
|
93
99
|
onError: options.onError,
|
|
94
100
|
rejectConnect: null,
|
|
95
101
|
resolveConnect: null,
|
|
@@ -103,7 +109,12 @@ export const buildWebViewConnectionBridgeScript = (input) => {
|
|
|
103
109
|
}
|
|
104
110
|
return;
|
|
105
111
|
}
|
|
106
|
-
|
|
112
|
+
if (
|
|
113
|
+
!postToNative(toConnectionMessage("message", connection, message)) &&
|
|
114
|
+
throwOnUnavailablePost
|
|
115
|
+
) {
|
|
116
|
+
throw new Error(inactiveConnectionMessage);
|
|
117
|
+
}
|
|
107
118
|
},
|
|
108
119
|
subscribe: function (listener) {
|
|
109
120
|
if (activeConnection !== connection) {
|
|
@@ -154,7 +165,7 @@ export const buildWebViewConnectionBridgeScript = (input) => {
|
|
|
154
165
|
}, ${connectTimeoutMs});
|
|
155
166
|
});
|
|
156
167
|
connection.connectPromise = connectPromise;
|
|
157
|
-
bridge.activateConnection();
|
|
168
|
+
bridge.activateConnection(false);
|
|
158
169
|
return connectPromise;
|
|
159
170
|
},
|
|
160
171
|
disconnect: function () {
|
|
@@ -190,26 +201,34 @@ export const buildWebViewConnectionBridgeScript = (input) => {
|
|
|
190
201
|
bridgeId: bridgeId,
|
|
191
202
|
protocolVersion: bridgeProtocolVersion,
|
|
192
203
|
createConnection: createConnection,
|
|
193
|
-
activateConnection: function () {
|
|
204
|
+
activateConnection: function (reactivate) {
|
|
194
205
|
var connection = activeConnection;
|
|
195
206
|
if (
|
|
196
207
|
!isNativeActivated ||
|
|
197
208
|
!connection ||
|
|
198
|
-
connection.isConnected
|
|
209
|
+
(connection.isConnected && !reactivate)
|
|
199
210
|
) {
|
|
200
211
|
return false;
|
|
201
212
|
}
|
|
213
|
+
if (typeof connection.prepareActivation === "function") {
|
|
214
|
+
try {
|
|
215
|
+
connection.prepareActivation(connection.transport, reactivate);
|
|
216
|
+
} catch {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
202
220
|
try {
|
|
203
221
|
if (!postToNative(toConnectionMessage("connected", connection))) {
|
|
204
222
|
return false;
|
|
205
223
|
}
|
|
206
224
|
} catch (error) {
|
|
207
|
-
reportConnectionError(connection, error);
|
|
208
225
|
return false;
|
|
209
226
|
}
|
|
210
227
|
connection.isConnected = true;
|
|
211
228
|
try {
|
|
212
|
-
connection.onActivated
|
|
229
|
+
if (typeof connection.onActivated === "function") {
|
|
230
|
+
connection.onActivated(connection.transport, reactivate);
|
|
231
|
+
}
|
|
213
232
|
} catch (error) {
|
|
214
233
|
connection.isConnected = false;
|
|
215
234
|
connection.isDisconnected = true;
|
|
@@ -233,6 +252,7 @@ export const buildWebViewConnectionBridgeScript = (input) => {
|
|
|
233
252
|
connection.connectPromise = null;
|
|
234
253
|
return false;
|
|
235
254
|
}
|
|
255
|
+
activatedNativeEpoch = nativeEpoch;
|
|
236
256
|
if (connection.resolveConnect) {
|
|
237
257
|
if (connection.connectTimeout) {
|
|
238
258
|
clearTimeout(connection.connectTimeout);
|
|
@@ -246,14 +266,20 @@ export const buildWebViewConnectionBridgeScript = (input) => {
|
|
|
246
266
|
},
|
|
247
267
|
activateNative: function (epoch) {
|
|
248
268
|
if (typeof epoch !== "number" || !Number.isSafeInteger(epoch) || epoch < 0) {
|
|
249
|
-
return
|
|
269
|
+
return null;
|
|
250
270
|
}
|
|
251
271
|
if (nativeEpoch !== null && epoch < nativeEpoch) {
|
|
252
|
-
return
|
|
272
|
+
return null;
|
|
253
273
|
}
|
|
274
|
+
var shouldReactivate =
|
|
275
|
+
reactivateOnNativeEpochChange &&
|
|
276
|
+
activeConnection &&
|
|
277
|
+
activeConnection.isConnected &&
|
|
278
|
+
activatedNativeEpoch !== epoch;
|
|
254
279
|
isNativeActivated = true;
|
|
255
280
|
nativeEpoch = epoch;
|
|
256
|
-
|
|
281
|
+
var didActivate = bridge.activateConnection(shouldReactivate);
|
|
282
|
+
return shouldReactivate && !didActivate ? null : didActivate;
|
|
257
283
|
},
|
|
258
284
|
notifyConnected: function () {
|
|
259
285
|
if (activeConnection && activeConnection.isConnected) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getuserfeedback/react-native",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "getuserfeedback React Native SDK",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"getuserfeedback",
|
|
@@ -38,14 +38,14 @@
|
|
|
38
38
|
"publish:dry-run": "node ../../scripts/publish-package.cjs . --verify-packed-runtime scripts/verify-packed-runtime.ts --expect-dependency @getuserfeedback/protocol:../protocol/package.json --expect-dependency @getuserfeedback/sdk:../sdk/package.json -- --dry-run",
|
|
39
39
|
"publish:npm": "node ../../scripts/publish-package.cjs . --verify-packed-runtime scripts/verify-packed-runtime.ts --expect-dependency @getuserfeedback/protocol:../protocol/package.json --expect-dependency @getuserfeedback/sdk:../sdk/package.json",
|
|
40
40
|
"build": "bun scripts/build.ts",
|
|
41
|
-
"typecheck": "tsc -p tsconfig.json --noEmit --
|
|
41
|
+
"typecheck": "tsc -p tsconfig.json --noEmit --composite false --incremental false && tsc -p tsconfig-scripts.json --noEmit --composite false --incremental false",
|
|
42
42
|
"test": "bun test --dots",
|
|
43
43
|
"test:changed": "bun test --changed=${TEST_CHANGED_BASE:-HEAD} --pass-with-no-tests --dots",
|
|
44
44
|
"lint": "biome check ."
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@getuserfeedback/protocol": "^3.31.
|
|
48
|
-
"@getuserfeedback/sdk": "^0.
|
|
47
|
+
"@getuserfeedback/protocol": "^3.31.2",
|
|
48
|
+
"@getuserfeedback/sdk": "^0.13.0",
|
|
49
49
|
"robot3": "^1.2.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|