@getuserfeedback/react-native 3.0.58 → 3.0.65
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/index.js +15 -25
- package/dist/native-core-command-session.d.ts +14 -0
- package/dist/native-core-command-session.js +61 -0
- package/dist/native-direct-command-session.d.ts +27 -0
- package/dist/native-direct-command-session.js +15 -0
- package/dist/native-runtime-root.d.ts +12 -3
- package/dist/native-runtime-root.js +116 -34
- package/dist/provider-command-helpers.d.ts +19 -0
- package/dist/provider-command-helpers.js +22 -1
- package/dist/version.js +1 -1
- package/dist/view-orchestration-runtime.js +55 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -9,15 +9,14 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
9
9
|
}
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
|
-
import { assertNoSegmentExternalIdsInValueArgument, CommandSettlementError, CommandSettlementTimeoutError, createFlowCommandHandleController, getFlowHandleFromSettlementDetail, getWidgetLayoutPlanNowMs,
|
|
12
|
+
import { assertNoSegmentExternalIdsInValueArgument, CommandSettlementError, CommandSettlementTimeoutError, createFlowCommandHandleController, getFlowHandleFromSettlementDetail, getWidgetLayoutPlanNowMs, } from "@getuserfeedback/protocol/host";
|
|
13
13
|
import { parseWebViewTransportNativeMessage } from "@getuserfeedback/protocol/webview-transport";
|
|
14
14
|
import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
|
|
15
15
|
import { useResolvedHostViewport } from "./host-viewport.js";
|
|
16
|
-
import { toCommandSettlementError, toReactNativeOneShotCommand, } from "./provider-command-helpers.js";
|
|
16
|
+
import { createProviderCommandEnvelope, REACT_NATIVE_LOADER_CLIENT_META, toCommandSettlementError, toReactNativeOneShotCommand, } from "./provider-command-helpers.js";
|
|
17
17
|
import { useProviderCommandTransport } from "./provider-command-transport.js";
|
|
18
18
|
import { createProviderWidgetHostLifecycleController, HIDDEN_PROVIDER_WIDGET_HOST_VISIBILITY, } from "./provider-widget-host-lifecycle.js";
|
|
19
19
|
import { ProviderWidgetHostOverlay, toProviderWidgetHostStyle, toProviderWidgetSheetHostLayoutPlan, } from "./provider-widget-host-overlay.js";
|
|
20
|
-
import { REACT_NATIVE_SDK_VERSION } from "./version.js";
|
|
21
20
|
import { WidgetHost, } from "./widget-host.js";
|
|
22
21
|
export { REACT_NATIVE_SDK_VERSION } from "./version.js";
|
|
23
22
|
export { buildNativeMessageInjectionScript, buildWebViewBridgeScript, } from "./webview-bridge-script.js";
|
|
@@ -122,15 +121,6 @@ const createReactNativeFlowRunController = (flowId, { enqueueFlowCommand, enqueu
|
|
|
122
121
|
resetIdleHandle,
|
|
123
122
|
};
|
|
124
123
|
};
|
|
125
|
-
const CLIENT_META = {
|
|
126
|
-
clientName: "@getuserfeedback/react-native",
|
|
127
|
-
clientVersion: REACT_NATIVE_SDK_VERSION,
|
|
128
|
-
loader: "sdk",
|
|
129
|
-
protocolCapabilities: [
|
|
130
|
-
REACT_NATIVE_WEBVIEW_TRANSPORT_CAPABILITIES.LAYOUT_TRANSITION_POLICY,
|
|
131
|
-
],
|
|
132
|
-
transport: "loader",
|
|
133
|
-
};
|
|
134
124
|
const createUniqueId = (prefix) => {
|
|
135
125
|
if (typeof globalThis.crypto !== "undefined" &&
|
|
136
126
|
typeof globalThis.crypto.randomUUID === "function") {
|
|
@@ -138,7 +128,6 @@ const createUniqueId = (prefix) => {
|
|
|
138
128
|
}
|
|
139
129
|
return `${prefix}_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
140
130
|
};
|
|
141
|
-
const createCommandRequestId = () => createUniqueId("req");
|
|
142
131
|
const createTransportMessageId = () => createUniqueId("native");
|
|
143
132
|
const normalizeOptionalString = (value) => {
|
|
144
133
|
if (value === undefined) {
|
|
@@ -265,19 +254,15 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
|
|
|
265
254
|
}, 0);
|
|
266
255
|
}, [onCommandError]);
|
|
267
256
|
const enqueueTransportCommand = useCallback((command, options, { isStartup = false, preserveFlowHandle = false, prioritizeStartup = false, } = {}) => {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const idempotencyKey = (_a = normalizeOptionalString(options === null || options === void 0 ? void 0 : options.idempotencyKey)) !== null && _a !== void 0 ? _a : requestId;
|
|
271
|
-
const envelope = {
|
|
272
|
-
version: "1",
|
|
273
|
-
instanceId,
|
|
274
|
-
requestId,
|
|
275
|
-
idempotencyKey,
|
|
276
|
-
clientMeta: CLIENT_META,
|
|
257
|
+
const envelope = createProviderCommandEnvelope({
|
|
258
|
+
clientMeta: REACT_NATIVE_LOADER_CLIENT_META,
|
|
277
259
|
command: preserveFlowHandle
|
|
278
260
|
? command
|
|
279
261
|
: toReactNativeOneShotCommand(command),
|
|
280
|
-
|
|
262
|
+
idempotencyKey: options === null || options === void 0 ? void 0 : options.idempotencyKey,
|
|
263
|
+
instanceId,
|
|
264
|
+
});
|
|
265
|
+
const { requestId } = envelope;
|
|
281
266
|
const message = {
|
|
282
267
|
kind: "enqueueCommand",
|
|
283
268
|
id: createTransportMessageId(),
|
|
@@ -321,8 +306,13 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
|
|
|
321
306
|
clearTimeout(pendingCommand.timeout);
|
|
322
307
|
pendingCommand.timeout = null;
|
|
323
308
|
}
|
|
324
|
-
const
|
|
325
|
-
const nextEnvelope =
|
|
309
|
+
const previousEnvelope = pendingCommand.result.envelope;
|
|
310
|
+
const nextEnvelope = createProviderCommandEnvelope({
|
|
311
|
+
clientMeta: previousEnvelope.clientMeta,
|
|
312
|
+
command: toReactNativeOneShotCommand(previousEnvelope.command),
|
|
313
|
+
idempotencyKey: previousEnvelope.idempotencyKey,
|
|
314
|
+
instanceId: previousEnvelope.instanceId,
|
|
315
|
+
});
|
|
326
316
|
const nextMessage = parseWebViewTransportNativeMessage({
|
|
327
317
|
kind: "enqueueCommand",
|
|
328
318
|
id: createTransportMessageId(),
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type CoreCommandEnvelope } from "@getuserfeedback/protocol";
|
|
2
|
+
import { type CommandSettledDetail } from "@getuserfeedback/protocol/host";
|
|
3
|
+
import type { NativeCoreCommandChannel } from "./native-core-command-channel.js";
|
|
4
|
+
export type NativeCoreCommandSession = {
|
|
5
|
+
dispatch: (envelope: CoreCommandEnvelope) => Promise<CommandSettledDetail>;
|
|
6
|
+
};
|
|
7
|
+
type NativeCoreCommandSessionOwner = {
|
|
8
|
+
dispose: () => void;
|
|
9
|
+
session: NativeCoreCommandSession;
|
|
10
|
+
};
|
|
11
|
+
export declare function createNativeCoreCommandSession(input: {
|
|
12
|
+
channel: NativeCoreCommandChannel;
|
|
13
|
+
}): NativeCoreCommandSessionOwner;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { coreCommandEnvelopeSchema, } from "@getuserfeedback/protocol";
|
|
2
|
+
import { toCommandSettlementError, } from "@getuserfeedback/protocol/host";
|
|
3
|
+
import { createPendingCommandTracker } from "./view-orchestration-runtime.js";
|
|
4
|
+
class NativeCoreCommandSessionClosedError extends Error {
|
|
5
|
+
constructor(requestId) {
|
|
6
|
+
super("Native core command session closed before settlement");
|
|
7
|
+
this.name = "NativeCoreCommandSessionClosedError";
|
|
8
|
+
this.requestId = requestId;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function toCommandSettledDetail(settlement) {
|
|
12
|
+
var _a, _b;
|
|
13
|
+
if (settlement.ok) {
|
|
14
|
+
return Object.assign({ instanceId: (_a = settlement.instanceId) !== null && _a !== void 0 ? _a : null, kind: settlement.kind, ok: true, requestId: settlement.requestId }, (settlement.result === undefined ? {} : { result: settlement.result }));
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
error: settlement.error,
|
|
18
|
+
instanceId: (_b = settlement.instanceId) !== null && _b !== void 0 ? _b : null,
|
|
19
|
+
kind: settlement.kind,
|
|
20
|
+
ok: false,
|
|
21
|
+
requestId: settlement.requestId,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function createNativeCoreCommandSession(input) {
|
|
25
|
+
const pendingCommands = createPendingCommandTracker();
|
|
26
|
+
let disposed = false;
|
|
27
|
+
const unsubscribe = input.channel.subscribe((settlement) => {
|
|
28
|
+
const detail = toCommandSettledDetail(settlement);
|
|
29
|
+
if (detail.ok) {
|
|
30
|
+
pendingCommands.resolve(detail.requestId, detail);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
pendingCommands.reject(detail.requestId, toCommandSettlementError(detail));
|
|
34
|
+
});
|
|
35
|
+
return {
|
|
36
|
+
session: {
|
|
37
|
+
dispatch: async (envelope) => {
|
|
38
|
+
if (disposed) {
|
|
39
|
+
return Promise.reject(new Error("Native core command session is closed"));
|
|
40
|
+
}
|
|
41
|
+
const canonicalEnvelope = coreCommandEnvelopeSchema.parse(envelope);
|
|
42
|
+
const settlement = pendingCommands.waitFor(canonicalEnvelope.requestId);
|
|
43
|
+
try {
|
|
44
|
+
input.channel.post(canonicalEnvelope);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
pendingCommands.reject(canonicalEnvelope.requestId, error instanceof Error ? error : new Error(String(error)));
|
|
48
|
+
}
|
|
49
|
+
return settlement;
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
dispose: () => {
|
|
53
|
+
if (disposed) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
disposed = true;
|
|
57
|
+
unsubscribe();
|
|
58
|
+
pendingCommands.reset((requestId) => new NativeCoreCommandSessionClosedError(requestId));
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CommandEnvelopeWithInstanceId, CommandSettledDetail } from "@getuserfeedback/protocol/host";
|
|
2
|
+
import { type CoreTranslatablePublicCommandPayload } from "@getuserfeedback/protocol/internal/public-core-command";
|
|
3
|
+
import type { NativeCoreCommandSession } from "./native-core-command-session.js";
|
|
4
|
+
type FlowLifecycleCommandPayload = Extract<CoreTranslatablePublicCommandPayload, {
|
|
5
|
+
kind: "open" | "prefetch" | "prerender" | "close";
|
|
6
|
+
}>;
|
|
7
|
+
/** Public commands that can be sent directly to the settled native core session. */
|
|
8
|
+
type NativeDirectCommandPayload = Exclude<CoreTranslatablePublicCommandPayload, FlowLifecycleCommandPayload | {
|
|
9
|
+
kind: "init";
|
|
10
|
+
}>;
|
|
11
|
+
/** Public command envelope accepted by the direct native provider session. */
|
|
12
|
+
export type NativeDirectCommandEnvelope = Omit<CommandEnvelopeWithInstanceId, "command"> & {
|
|
13
|
+
command: NativeDirectCommandPayload;
|
|
14
|
+
};
|
|
15
|
+
export type NativeDirectCommandSession = {
|
|
16
|
+
dispatch: (envelope: NativeDirectCommandEnvelope) => Promise<CommandSettledDetail>;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Adapt direct public commands to the already-settled native core session.
|
|
20
|
+
*
|
|
21
|
+
* Flow lifecycle and container commands stay outside this adapter until their
|
|
22
|
+
* shared orchestration contract is ready.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createNativeDirectCommandSession(input: {
|
|
25
|
+
coreSession: NativeCoreCommandSession;
|
|
26
|
+
}): NativeDirectCommandSession;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { toCoreCommandPayload, } from "@getuserfeedback/protocol/internal/public-core-command";
|
|
2
|
+
/**
|
|
3
|
+
* Adapt direct public commands to the already-settled native core session.
|
|
4
|
+
*
|
|
5
|
+
* Flow lifecycle and container commands stay outside this adapter until their
|
|
6
|
+
* shared orchestration contract is ready.
|
|
7
|
+
*/
|
|
8
|
+
export function createNativeDirectCommandSession(input) {
|
|
9
|
+
return {
|
|
10
|
+
dispatch: (envelope) => {
|
|
11
|
+
const coreEnvelope = Object.assign(Object.assign({}, envelope), { command: toCoreCommandPayload(envelope.command) });
|
|
12
|
+
return input.coreSession.dispatch(coreEnvelope);
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import type { CoreCommandEnvelope } from "@getuserfeedback/protocol";
|
|
2
|
+
import type { InitOptions } from "@getuserfeedback/sdk";
|
|
2
3
|
import { type ReactElement } from "react";
|
|
3
4
|
import { type CoreWebViewHostProps } from "./core-webview-host.js";
|
|
4
|
-
import { type
|
|
5
|
+
import { type NativeCoreCommandSession } from "./native-core-command-session.js";
|
|
6
|
+
import { type NativeDirectCommandSession } from "./native-direct-command-session.js";
|
|
5
7
|
import { type NativeViewHostProjectionProps } from "./native-view-host-projection.js";
|
|
6
8
|
export type NativeRuntimeRootProps = {
|
|
7
9
|
coreUrl: string;
|
|
8
10
|
coreWebViewComponent?: CoreWebViewHostProps["webViewComponent"];
|
|
9
11
|
coreWebViewProps?: CoreWebViewHostProps["webViewProps"];
|
|
10
12
|
instanceRegistrationEnvelope?: NativeRuntimeInstanceRegistrationEnvelope;
|
|
11
|
-
|
|
13
|
+
onCommandSessionChange?: (session: NativeCoreCommandSession | null) => void;
|
|
12
14
|
onError?: (error: Error) => void;
|
|
13
15
|
onInvalidMessage?: (error: Error, value: unknown) => void;
|
|
16
|
+
registrationSettlementTimeoutMs?: number;
|
|
14
17
|
viewWebViewComponent?: NativeViewHostProjectionProps["webViewComponent"];
|
|
15
18
|
viewWebViewProps?: NativeViewHostProjectionProps["webViewProps"];
|
|
16
19
|
};
|
|
@@ -19,5 +22,11 @@ type NativeRuntimeInstanceRegistrationEnvelope = Omit<CoreCommandEnvelope, "comm
|
|
|
19
22
|
kind: "init";
|
|
20
23
|
}>;
|
|
21
24
|
};
|
|
22
|
-
|
|
25
|
+
type NativeProviderRuntimeProps = Omit<NativeRuntimeRootProps, "instanceRegistrationEnvelope" | "onCommandSessionChange"> & {
|
|
26
|
+
initOptions: InitOptions;
|
|
27
|
+
instanceId: string;
|
|
28
|
+
onCommandSessionChange?: (session: NativeDirectCommandSession | null) => void;
|
|
29
|
+
};
|
|
30
|
+
export declare function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewProps, instanceRegistrationEnvelope, onCommandSessionChange, onError, onInvalidMessage, registrationSettlementTimeoutMs, viewWebViewComponent, viewWebViewProps, }: NativeRuntimeRootProps): ReactElement;
|
|
31
|
+
export declare function NativeProviderRuntime({ initOptions, instanceId, onCommandSessionChange, ...runtimeProps }: NativeProviderRuntimeProps): ReactElement;
|
|
23
32
|
export {};
|
|
@@ -1,11 +1,39 @@
|
|
|
1
|
-
|
|
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 { CommandSettlementError, CommandSettlementTimeoutError, } from "@getuserfeedback/protocol/host";
|
|
13
|
+
import { createElement, Fragment, useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
2
14
|
import { CoreWebViewHost, } from "./core-webview-host.js";
|
|
3
|
-
import { createNativeCoreCommandChannel
|
|
15
|
+
import { createNativeCoreCommandChannel } from "./native-core-command-channel.js";
|
|
16
|
+
import { createNativeCoreCommandSession, } from "./native-core-command-session.js";
|
|
17
|
+
import { createNativeDirectCommandSession, } from "./native-direct-command-session.js";
|
|
4
18
|
import { NativeViewHostProjection, } from "./native-view-host-projection.js";
|
|
5
19
|
import { createNativeViewRecordController, } from "./native-view-record-controller.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
20
|
+
import { createProviderCommandEnvelope, REACT_NATIVE_CLIENT_META, } from "./provider-command-helpers.js";
|
|
21
|
+
const DEFAULT_REGISTRATION_SETTLEMENT_TIMEOUT_MS = 30000;
|
|
22
|
+
function dispatchRegistration(input) {
|
|
23
|
+
let timeoutId;
|
|
24
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
25
|
+
timeoutId = setTimeout(() => reject(new CommandSettlementTimeoutError(input.envelope.requestId)), Math.max(0, input.timeoutMs));
|
|
26
|
+
});
|
|
27
|
+
return Promise.race([
|
|
28
|
+
input.commandSession.dispatch(input.envelope).then(() => undefined),
|
|
29
|
+
timeout,
|
|
30
|
+
]).finally(() => {
|
|
31
|
+
if (timeoutId !== undefined) {
|
|
32
|
+
clearTimeout(timeoutId);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewProps, instanceRegistrationEnvelope, onCommandSessionChange, onError, onInvalidMessage, registrationSettlementTimeoutMs = DEFAULT_REGISTRATION_SETTLEMENT_TIMEOUT_MS, viewWebViewComponent, viewWebViewProps, }) {
|
|
9
37
|
const [coreHostRevision, setCoreHostRevision] = useState(0);
|
|
10
38
|
const [session, setSession] = useState(null);
|
|
11
39
|
const sessionRef = useRef(null);
|
|
@@ -13,8 +41,8 @@ export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewPr
|
|
|
13
41
|
const registrationRetryAttemptedRef = useRef(false);
|
|
14
42
|
const onErrorRef = useRef(onError);
|
|
15
43
|
onErrorRef.current = onError;
|
|
16
|
-
const
|
|
17
|
-
|
|
44
|
+
const onCommandSessionChangeRef = useRef(onCommandSessionChange);
|
|
45
|
+
onCommandSessionChangeRef.current = onCommandSessionChange;
|
|
18
46
|
const onInvalidMessageRef = useRef(onInvalidMessage);
|
|
19
47
|
onInvalidMessageRef.current = onInvalidMessage;
|
|
20
48
|
const instanceRegistrationEnvelopeRef = useRef(instanceRegistrationEnvelope);
|
|
@@ -28,9 +56,9 @@ export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewPr
|
|
|
28
56
|
// Error observers cannot change native runtime settlement.
|
|
29
57
|
}
|
|
30
58
|
}, []);
|
|
31
|
-
const
|
|
59
|
+
const notifyCommandSessionObserver = useCallback((observer, commandSession) => {
|
|
32
60
|
try {
|
|
33
|
-
observer === null || observer === void 0 ? void 0 : observer(
|
|
61
|
+
observer === null || observer === void 0 ? void 0 : observer(commandSession);
|
|
34
62
|
}
|
|
35
63
|
catch (error) {
|
|
36
64
|
reportError(error);
|
|
@@ -40,10 +68,13 @@ export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewPr
|
|
|
40
68
|
const previousSession = sessionRef.current;
|
|
41
69
|
if (previousSession) {
|
|
42
70
|
sessionRef.current = null;
|
|
71
|
+
previousSession.commandSessionOwner.dispose();
|
|
43
72
|
previousSession.commandChannelOwner.dispose();
|
|
44
73
|
previousSession.controller.dispose();
|
|
45
74
|
setSession(null);
|
|
46
|
-
|
|
75
|
+
if (previousSession.ready) {
|
|
76
|
+
notifyCommandSessionObserver(previousSession.commandSessionObserver, null);
|
|
77
|
+
}
|
|
47
78
|
}
|
|
48
79
|
const controller = createNativeViewRecordController({
|
|
49
80
|
coreConnection,
|
|
@@ -53,64 +84,99 @@ export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewPr
|
|
|
53
84
|
coreConnection,
|
|
54
85
|
onInvalidMessage: (error, value) => { var _a; return (_a = onInvalidMessageRef.current) === null || _a === void 0 ? void 0 : _a.call(onInvalidMessageRef, error, value); },
|
|
55
86
|
});
|
|
87
|
+
const commandSessionOwner = createNativeCoreCommandSession({
|
|
88
|
+
channel: commandChannelOwner.channel,
|
|
89
|
+
});
|
|
56
90
|
const nextSession = {
|
|
57
91
|
commandChannelOwner,
|
|
92
|
+
commandSessionOwner,
|
|
58
93
|
controller,
|
|
94
|
+
commandSessionObserver: onCommandSessionChangeRef.current,
|
|
59
95
|
key: nextSessionKeyRef.current + 1,
|
|
60
|
-
|
|
96
|
+
ready: false,
|
|
97
|
+
};
|
|
98
|
+
sessionRef.current = nextSession;
|
|
99
|
+
const commitSession = () => {
|
|
100
|
+
if (sessionRef.current !== nextSession) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
registrationRetryAttemptedRef.current = false;
|
|
104
|
+
nextSession.ready = true;
|
|
105
|
+
nextSessionKeyRef.current = nextSession.key;
|
|
106
|
+
setSession(nextSession);
|
|
107
|
+
notifyCommandSessionObserver(nextSession.commandSessionObserver, commandSessionOwner.session);
|
|
61
108
|
};
|
|
62
109
|
const registrationEnvelope = instanceRegistrationEnvelopeRef.current;
|
|
63
110
|
if (registrationEnvelope) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
111
|
+
void dispatchRegistration({
|
|
112
|
+
commandSession: commandSessionOwner.session,
|
|
113
|
+
envelope: registrationEnvelope,
|
|
114
|
+
timeoutMs: registrationSettlementTimeoutMs,
|
|
115
|
+
})
|
|
116
|
+
.then(commitSession)
|
|
117
|
+
.catch((error) => {
|
|
118
|
+
if (sessionRef.current !== nextSession) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
sessionRef.current = null;
|
|
122
|
+
commandSessionOwner.dispose();
|
|
68
123
|
commandChannelOwner.dispose();
|
|
69
124
|
controller.dispose();
|
|
70
|
-
if (!
|
|
125
|
+
if (!(error instanceof CommandSettlementError) &&
|
|
126
|
+
!registrationRetryAttemptedRef.current) {
|
|
71
127
|
registrationRetryAttemptedRef.current = true;
|
|
72
128
|
setCoreHostRevision((revision) => revision + 1);
|
|
73
129
|
}
|
|
74
130
|
reportError(error);
|
|
75
|
-
|
|
76
|
-
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
77
133
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
134
|
+
commitSession();
|
|
135
|
+
}, [
|
|
136
|
+
notifyCommandSessionObserver,
|
|
137
|
+
registrationSettlementTimeoutMs,
|
|
138
|
+
reportError,
|
|
139
|
+
]);
|
|
84
140
|
const handleDisconnected = useCallback(() => {
|
|
85
141
|
const currentSession = sessionRef.current;
|
|
86
142
|
if (!currentSession) {
|
|
87
143
|
return;
|
|
88
144
|
}
|
|
89
145
|
sessionRef.current = null;
|
|
146
|
+
currentSession.commandSessionOwner.dispose();
|
|
90
147
|
currentSession.commandChannelOwner.dispose();
|
|
91
148
|
currentSession.controller.dispose();
|
|
92
149
|
setSession(null);
|
|
93
|
-
|
|
94
|
-
|
|
150
|
+
if (currentSession.ready) {
|
|
151
|
+
notifyCommandSessionObserver(currentSession.commandSessionObserver, null);
|
|
152
|
+
}
|
|
153
|
+
}, [notifyCommandSessionObserver]);
|
|
95
154
|
useEffect(() => {
|
|
96
155
|
const currentSession = sessionRef.current;
|
|
97
|
-
if (!currentSession ||
|
|
156
|
+
if (!currentSession ||
|
|
157
|
+
currentSession.commandSessionObserver === onCommandSessionChange) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const previousObserver = currentSession.commandSessionObserver;
|
|
161
|
+
currentSession.commandSessionObserver = onCommandSessionChange;
|
|
162
|
+
if (!currentSession.ready) {
|
|
98
163
|
return;
|
|
99
164
|
}
|
|
100
|
-
|
|
101
|
-
currentSession.
|
|
102
|
-
|
|
103
|
-
notifyCommandChannelObserver(onCommandChannelChange, currentSession.commandChannelOwner.channel);
|
|
104
|
-
}, [notifyCommandChannelObserver, onCommandChannelChange]);
|
|
165
|
+
notifyCommandSessionObserver(previousObserver, null);
|
|
166
|
+
notifyCommandSessionObserver(onCommandSessionChange, currentSession.commandSessionOwner.session);
|
|
167
|
+
}, [notifyCommandSessionObserver, onCommandSessionChange]);
|
|
105
168
|
useEffect(() => () => {
|
|
106
169
|
const currentSession = sessionRef.current;
|
|
107
170
|
sessionRef.current = null;
|
|
108
171
|
if (currentSession) {
|
|
172
|
+
currentSession.commandSessionOwner.dispose();
|
|
109
173
|
currentSession.commandChannelOwner.dispose();
|
|
110
174
|
currentSession.controller.dispose();
|
|
111
|
-
|
|
175
|
+
if (currentSession.ready) {
|
|
176
|
+
notifyCommandSessionObserver(currentSession.commandSessionObserver, null);
|
|
177
|
+
}
|
|
112
178
|
}
|
|
113
|
-
}, [
|
|
179
|
+
}, [notifyCommandSessionObserver]);
|
|
114
180
|
return createElement(Fragment, null, createElement(CoreWebViewHost, {
|
|
115
181
|
coreUrl,
|
|
116
182
|
key: `${coreUrl}:${coreHostRevision}`,
|
|
@@ -129,3 +195,19 @@ export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewPr
|
|
|
129
195
|
webViewProps: viewWebViewProps,
|
|
130
196
|
}));
|
|
131
197
|
}
|
|
198
|
+
// TODO(architecture): Extract the host-neutral flow-command seam from loader before attaching flow lifecycle commands here; keep direct commands on the settled core session.
|
|
199
|
+
// [from=provider-owned-native-direct-command-routing] [to=shared-flow-command-orchestration] [scope=slice] [principle=core-owned-flow-lifecycle] [priority=high] [impact=high] [risk=high] [epic=runtime-neutral-view-host-orchestration] [epic_order=120]
|
|
200
|
+
export function NativeProviderRuntime(_a) {
|
|
201
|
+
var { initOptions, instanceId, onCommandSessionChange } = _a, runtimeProps = __rest(_a, ["initOptions", "instanceId", "onCommandSessionChange"]);
|
|
202
|
+
const handleCommandSessionChange = useCallback((session) => {
|
|
203
|
+
onCommandSessionChange === null || onCommandSessionChange === void 0 ? void 0 : onCommandSessionChange(session === null
|
|
204
|
+
? null
|
|
205
|
+
: createNativeDirectCommandSession({ coreSession: session }));
|
|
206
|
+
}, [onCommandSessionChange]);
|
|
207
|
+
const instanceRegistrationEnvelope = useMemo(() => createProviderCommandEnvelope({
|
|
208
|
+
clientMeta: REACT_NATIVE_CLIENT_META,
|
|
209
|
+
command: { kind: "init", opts: initOptions },
|
|
210
|
+
instanceId,
|
|
211
|
+
}), [initOptions, instanceId]);
|
|
212
|
+
return createElement(NativeRuntimeRoot, Object.assign(Object.assign({}, runtimeProps), { instanceRegistrationEnvelope, onCommandSessionChange: handleCommandSessionChange }));
|
|
213
|
+
}
|
|
@@ -1,6 +1,25 @@
|
|
|
1
|
+
import type { ClientMeta } from "@getuserfeedback/protocol";
|
|
1
2
|
import { CommandSettlementError } from "@getuserfeedback/protocol/host";
|
|
2
3
|
import type { WebViewCommandEnvelope, WebViewTransportHostEvent } from "@getuserfeedback/protocol/webview-transport";
|
|
3
4
|
type ReactNativeWidgetCommand = WebViewCommandEnvelope["command"];
|
|
5
|
+
type ReactNativeInitCommand = Extract<ReactNativeWidgetCommand, {
|
|
6
|
+
kind: "init";
|
|
7
|
+
}>;
|
|
8
|
+
export declare const REACT_NATIVE_CLIENT_META: ClientMeta;
|
|
9
|
+
export declare const REACT_NATIVE_LOADER_CLIENT_META: ClientMeta;
|
|
10
|
+
type CreateProviderCommandEnvelopeInput = {
|
|
11
|
+
clientMeta?: ClientMeta;
|
|
12
|
+
command: ReactNativeWidgetCommand;
|
|
13
|
+
idempotencyKey?: string;
|
|
14
|
+
instanceId: string;
|
|
15
|
+
requestId?: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function createProviderCommandEnvelope(input: CreateProviderCommandEnvelopeInput & {
|
|
18
|
+
command: ReactNativeInitCommand;
|
|
19
|
+
}): Omit<WebViewCommandEnvelope, "command"> & {
|
|
20
|
+
command: ReactNativeInitCommand;
|
|
21
|
+
};
|
|
22
|
+
export declare function createProviderCommandEnvelope(input: CreateProviderCommandEnvelopeInput): WebViewCommandEnvelope;
|
|
4
23
|
export type CommandSettledEvent = Extract<WebViewTransportHostEvent, {
|
|
5
24
|
name: "instance:command:settled";
|
|
6
25
|
}>;
|
|
@@ -9,7 +9,28 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
9
9
|
}
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
|
-
import { CommandSettlementError } from "@getuserfeedback/protocol/host";
|
|
12
|
+
import { CommandSettlementError, createCommandEnvelopeWithInstanceId, REACT_NATIVE_WEBVIEW_TRANSPORT_CAPABILITIES, } from "@getuserfeedback/protocol/host";
|
|
13
|
+
import { REACT_NATIVE_SDK_VERSION } from "./version.js";
|
|
14
|
+
export const REACT_NATIVE_CLIENT_META = {
|
|
15
|
+
clientName: "@getuserfeedback/react-native",
|
|
16
|
+
clientVersion: REACT_NATIVE_SDK_VERSION,
|
|
17
|
+
loader: "sdk",
|
|
18
|
+
protocolCapabilities: [
|
|
19
|
+
REACT_NATIVE_WEBVIEW_TRANSPORT_CAPABILITIES.LAYOUT_TRANSITION_POLICY,
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
export const REACT_NATIVE_LOADER_CLIENT_META = Object.assign(Object.assign({}, REACT_NATIVE_CLIENT_META), { transport: "loader" });
|
|
23
|
+
export function createProviderCommandEnvelope({ clientMeta, command, idempotencyKey, instanceId, requestId, }) {
|
|
24
|
+
const resolvedCommand = command.kind === "init" && clientMeta !== undefined
|
|
25
|
+
? Object.assign(Object.assign({}, command), { opts: Object.assign(Object.assign({}, command.opts), { clientMeta }) }) : command;
|
|
26
|
+
return createCommandEnvelopeWithInstanceId({
|
|
27
|
+
clientMeta,
|
|
28
|
+
command: resolvedCommand,
|
|
29
|
+
idempotencyKey,
|
|
30
|
+
instanceId,
|
|
31
|
+
requestId,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
13
34
|
function isReactNativeFlowCommand(command) {
|
|
14
35
|
return (command.kind === "open" ||
|
|
15
36
|
command.kind === "prefetch" ||
|
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 : "3.0.
|
|
6
|
+
export const REACT_NATIVE_SDK_VERSION = reactNativeSdkVersion.length > 0 ? reactNativeSdkVersion : "3.0.65";
|
|
@@ -32,6 +32,57 @@ function createActiveViewSnapshotListController() {
|
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
+
// ../view-orchestration/src/pending-command-tracker.ts
|
|
36
|
+
var createDeferred = () => {
|
|
37
|
+
let resolve;
|
|
38
|
+
let reject;
|
|
39
|
+
const promise = new Promise((nextResolve, nextReject) => {
|
|
40
|
+
resolve = nextResolve;
|
|
41
|
+
reject = nextReject;
|
|
42
|
+
});
|
|
43
|
+
return { promise, resolve, reject };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
class PendingCommandTracker {
|
|
47
|
+
pendingByRequestId = new Map;
|
|
48
|
+
waitFor(requestId) {
|
|
49
|
+
if (this.pendingByRequestId.has(requestId)) {
|
|
50
|
+
throw new Error(`Duplicate pending command requestId: "${requestId}"`);
|
|
51
|
+
}
|
|
52
|
+
const deferred = createDeferred();
|
|
53
|
+
this.pendingByRequestId.set(requestId, deferred);
|
|
54
|
+
return deferred.promise;
|
|
55
|
+
}
|
|
56
|
+
resolve(requestId, value) {
|
|
57
|
+
const deferred = this.pendingByRequestId.get(requestId);
|
|
58
|
+
if (!deferred) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
this.pendingByRequestId.delete(requestId);
|
|
62
|
+
deferred.resolve(value);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
reject(requestId, error) {
|
|
66
|
+
const deferred = this.pendingByRequestId.get(requestId);
|
|
67
|
+
if (!deferred) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
this.pendingByRequestId.delete(requestId);
|
|
71
|
+
deferred.reject(error);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
reset(createError) {
|
|
75
|
+
for (const requestId of this.pendingByRequestId.keys()) {
|
|
76
|
+
this.reject(requestId, createError(requestId));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
has(requestId) {
|
|
80
|
+
return this.pendingByRequestId.has(requestId);
|
|
81
|
+
}
|
|
82
|
+
size() {
|
|
83
|
+
return this.pendingByRequestId.size;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
35
86
|
// ../view-orchestration/src/view-activation-machine.ts
|
|
36
87
|
import { createMachine, interpret, state, transition } from "robot3";
|
|
37
88
|
var VIEW_ACTIVATION_STATES = {
|
|
@@ -212,6 +263,9 @@ function createViewPreparationRegistry() {
|
|
|
212
263
|
};
|
|
213
264
|
}
|
|
214
265
|
// src/view-orchestration-runtime.ts
|
|
266
|
+
function createPendingCommandTracker() {
|
|
267
|
+
return new PendingCommandTracker;
|
|
268
|
+
}
|
|
215
269
|
function createActiveViewSnapshotListController2() {
|
|
216
270
|
return createActiveViewSnapshotListController();
|
|
217
271
|
}
|
|
@@ -228,5 +282,6 @@ export {
|
|
|
228
282
|
createViewPreparationRegistry2 as createViewPreparationRegistry,
|
|
229
283
|
createViewPreparationReadinessRegistry2 as createViewPreparationReadinessRegistry,
|
|
230
284
|
createViewActivationController2 as createViewActivationController,
|
|
285
|
+
createPendingCommandTracker,
|
|
231
286
|
createActiveViewSnapshotListController2 as createActiveViewSnapshotListController
|
|
232
287
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getuserfeedback/react-native",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.65",
|
|
4
4
|
"description": "getuserfeedback React Native SDK",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"getuserfeedback",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"lint": "biome check ."
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@getuserfeedback/protocol": "^3.
|
|
48
|
-
"@getuserfeedback/sdk": "^0.
|
|
47
|
+
"@getuserfeedback/protocol": "^3.17.2",
|
|
48
|
+
"@getuserfeedback/sdk": "^0.11.2",
|
|
49
49
|
"robot3": "^1.2.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|