@getuserfeedback/react-native 3.0.59 → 3.0.66

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 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, REACT_NATIVE_WEBVIEW_TRANSPORT_CAPABILITIES, } from "@getuserfeedback/protocol/host";
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
- var _a;
269
- const requestId = createCommandRequestId();
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 command = pendingCommand.result.envelope.command;
325
- const nextEnvelope = Object.assign(Object.assign({}, pendingCommand.result.envelope), { command: toReactNativeOneShotCommand(command), requestId: createCommandRequestId() });
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 NativeCoreCommandChannel } from "./native-core-command-channel.js";
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
- onCommandChannelChange?: (channel: NativeCoreCommandChannel | null) => void;
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
- export declare function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewProps, instanceRegistrationEnvelope, onCommandChannelChange, onError, onInvalidMessage, viewWebViewComponent, viewWebViewProps, }: NativeRuntimeRootProps): ReactElement;
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
- import { createElement, Fragment, useCallback, useEffect, useRef, useState, } from "react";
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, } from "./native-core-command-channel.js";
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
- // TODO(architecture): Compose provider-owned startup state with the private native runtime without changing the default loader-backed path.
7
- // [from=protocol-owned-native-command-bootstrap-routing] [to=provider-owned-native-registration-composition] [scope=slice] [principle=preserve-provider-runtime] [priority=high] [impact=high] [risk=high] [epic=runtime-neutral-view-host-orchestration] [epic_order=80]
8
- export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewProps, instanceRegistrationEnvelope, onCommandChannelChange, onError, onInvalidMessage, viewWebViewComponent, viewWebViewProps, }) {
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 onCommandChannelChangeRef = useRef(onCommandChannelChange);
17
- onCommandChannelChangeRef.current = onCommandChannelChange;
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 notifyCommandChannelObserver = useCallback((observer, channel) => {
59
+ const notifyCommandSessionObserver = useCallback((observer, commandSession) => {
32
60
  try {
33
- observer === null || observer === void 0 ? void 0 : observer(channel);
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
- notifyCommandChannelObserver(previousSession.observer, null);
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
- observer: onCommandChannelChangeRef.current,
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
- try {
65
- commandChannelOwner.channel.post(registrationEnvelope);
66
- }
67
- catch (error) {
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 (!registrationRetryAttemptedRef.current) {
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
- return;
76
- }
131
+ });
132
+ return;
77
133
  }
78
- registrationRetryAttemptedRef.current = false;
79
- nextSessionKeyRef.current = nextSession.key;
80
- sessionRef.current = nextSession;
81
- setSession(nextSession);
82
- notifyCommandChannelObserver(nextSession.observer, commandChannelOwner.channel);
83
- }, [notifyCommandChannelObserver, reportError]);
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
- notifyCommandChannelObserver(currentSession.observer, null);
94
- }, [notifyCommandChannelObserver]);
150
+ if (currentSession.ready) {
151
+ notifyCommandSessionObserver(currentSession.commandSessionObserver, null);
152
+ }
153
+ }, [notifyCommandSessionObserver]);
95
154
  useEffect(() => {
96
155
  const currentSession = sessionRef.current;
97
- if (!currentSession || currentSession.observer === onCommandChannelChange) {
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
- const previousObserver = currentSession.observer;
101
- currentSession.observer = onCommandChannelChange;
102
- notifyCommandChannelObserver(previousObserver, null);
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
- notifyCommandChannelObserver(currentSession.observer, null);
175
+ if (currentSession.ready) {
176
+ notifyCommandSessionObserver(currentSession.commandSessionObserver, null);
177
+ }
112
178
  }
113
- }, [notifyCommandChannelObserver]);
179
+ }, [notifyCommandSessionObserver]);
114
180
  return createElement(Fragment, null, createElement(CoreWebViewHost, {
115
181
  coreUrl,
116
182
  key: `${coreUrl}:${coreHostRevision}`,
@@ -129,3 +195,17 @@ export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewPr
129
195
  webViewProps: viewWebViewProps,
130
196
  }));
131
197
  }
198
+ export function NativeProviderRuntime(_a) {
199
+ var { initOptions, instanceId, onCommandSessionChange } = _a, runtimeProps = __rest(_a, ["initOptions", "instanceId", "onCommandSessionChange"]);
200
+ const handleCommandSessionChange = useCallback((session) => {
201
+ onCommandSessionChange === null || onCommandSessionChange === void 0 ? void 0 : onCommandSessionChange(session === null
202
+ ? null
203
+ : createNativeDirectCommandSession({ coreSession: session }));
204
+ }, [onCommandSessionChange]);
205
+ const instanceRegistrationEnvelope = useMemo(() => createProviderCommandEnvelope({
206
+ clientMeta: REACT_NATIVE_CLIENT_META,
207
+ command: { kind: "init", opts: initOptions },
208
+ instanceId,
209
+ }), [initOptions, instanceId]);
210
+ return createElement(NativeRuntimeRoot, Object.assign(Object.assign({}, runtimeProps), { instanceRegistrationEnvelope, onCommandSessionChange: handleCommandSessionChange }));
211
+ }
@@ -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.59";
6
+ export const REACT_NATIVE_SDK_VERSION = reactNativeSdkVersion.length > 0 ? reactNativeSdkVersion : "3.0.66";
@@ -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.59",
3
+ "version": "3.0.66",
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.16.1",
48
- "@getuserfeedback/sdk": "^0.10.21",
47
+ "@getuserfeedback/protocol": "^3.18.0",
48
+ "@getuserfeedback/sdk": "^0.12.0",
49
49
  "robot3": "^1.2.0"
50
50
  },
51
51
  "peerDependencies": {