@getuserfeedback/react-native 4.0.7 → 4.0.9

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.
@@ -2,6 +2,7 @@ import type { CoreHostBootstrapTransport } from "@getuserfeedback/protocol/inter
2
2
  import { type CoreWebViewBridgeMessage } from "./core-webview-bridge-adapter.js";
3
3
  import { type WebViewHostController } from "./webview-host-controller.js";
4
4
  export type CoreWebViewHostConnection = {
5
+ disconnect: () => void;
5
6
  generation: number;
6
7
  transport: CoreHostBootstrapTransport;
7
8
  };
@@ -2,6 +2,7 @@ import { buildCoreWebViewBridgeScript, buildCoreWebViewHostMessageScript, } from
2
2
  import { createWebViewHostController, } from "./webview-host-controller.js";
3
3
  export const createCoreWebViewHostController = (input) => createWebViewHostController(Object.assign(Object.assign({}, input), { connectionLabel: "Core WebView host", buildActivationScript: buildCoreWebViewBridgeScript, buildHostMessageScript: buildCoreWebViewHostMessageScript, onConnected: (connection) => {
4
4
  input.onConnected({
5
+ disconnect: connection.disconnect,
5
6
  generation: connection.generation,
6
7
  transport: {
7
8
  postMessage: connection.postMessage,
@@ -1,11 +1,8 @@
1
- import type { CoreHostBootstrapTransport } from "@getuserfeedback/protocol/internal/core-host-bootstrap";
2
1
  import { type ComponentType, type ReactElement } from "react";
2
+ import { type CoreWebViewHostConnection } from "./core-webview-host-controller.js";
3
3
  export type CoreWebViewHostProps = {
4
4
  coreUrl: string;
5
- onConnected: (connection: {
6
- generation: number;
7
- transport: CoreHostBootstrapTransport;
8
- }) => void;
5
+ onConnected: (connection: CoreWebViewHostConnection) => void;
9
6
  onDisconnected?: () => void;
10
7
  onInvalidMessage?: (error: Error, value: unknown) => void;
11
8
  webViewComponent?: ComponentType<Record<string, unknown>>;
@@ -1,4 +1,5 @@
1
1
  import { type CoreCommandEnvelope } from "@getuserfeedback/protocol";
2
+ import { type EventHandleInvalidated } from "@getuserfeedback/protocol/internal/core-handle-invalidation";
2
3
  import { type EventCommandSettled } from "@getuserfeedback/protocol/internal/core-host-command-settlement";
3
4
  import type { CoreWebViewHostConnection } from "./core-webview-host-controller.js";
4
5
  export type NativeCoreCommandChannel = {
@@ -11,6 +12,7 @@ type OwnedNativeCoreCommandChannel = {
11
12
  };
12
13
  export declare function createNativeCoreCommandChannel(input: {
13
14
  coreConnection: CoreWebViewHostConnection;
15
+ onHandleInvalidated?: (message: EventHandleInvalidated) => void;
14
16
  onInvalidMessage?: (error: Error, value: unknown) => void;
15
17
  }): OwnedNativeCoreCommandChannel;
16
18
  export {};
@@ -1,4 +1,5 @@
1
1
  import { coreCommandEnvelopeSchema, } from "@getuserfeedback/protocol";
2
+ import { eventHandleInvalidatedSchema, } from "@getuserfeedback/protocol/internal/core-handle-invalidation";
2
3
  import { eventCommandSettledSchema, } from "@getuserfeedback/protocol/internal/core-host-command-settlement";
3
4
  const DISPOSED_MESSAGE = "Core command channel is disposed";
4
5
  const isRecord = (value) => typeof value === "object" && value !== null;
@@ -6,7 +7,7 @@ export function createNativeCoreCommandChannel(input) {
6
7
  const listeners = new Set();
7
8
  let disposed = false;
8
9
  const handleMessage = (value) => {
9
- var _a;
10
+ var _a, _b, _c;
10
11
  if (disposed || !isRecord(value)) {
11
12
  return;
12
13
  }
@@ -26,6 +27,20 @@ export function createNativeCoreCommandChannel(input) {
26
27
  }
27
28
  return;
28
29
  }
30
+ if (value.t !== "getuserfeedback:event:handleInvalidated") {
31
+ return;
32
+ }
33
+ const parsed = eventHandleInvalidatedSchema.safeParse(value);
34
+ if (!parsed.success) {
35
+ (_b = input.onInvalidMessage) === null || _b === void 0 ? void 0 : _b.call(input, parsed.error instanceof Error
36
+ ? parsed.error
37
+ : new Error("Invalid core handle invalidation message"), value);
38
+ return;
39
+ }
40
+ if (parsed.data.gen !== input.coreConnection.generation) {
41
+ return;
42
+ }
43
+ (_c = input.onHandleInvalidated) === null || _c === void 0 ? void 0 : _c.call(input, parsed.data);
29
44
  };
30
45
  const unsubscribeTransport = input.coreConnection.transport.subscribe(handleMessage);
31
46
  const channel = {
@@ -1,4 +1,5 @@
1
1
  import type { CoreCommandEnvelope } from "@getuserfeedback/protocol";
2
+ import type { EventHandleInvalidated } from "@getuserfeedback/protocol/internal/core-handle-invalidation";
2
3
  import type { ConfigureOptions, InitOptions } from "@getuserfeedback/sdk";
3
4
  import { type ReactElement } from "react";
4
5
  import { type CoreWebViewHostProps } from "./core-webview-host.js";
@@ -13,6 +14,7 @@ export type NativeRuntimeRootProps = {
13
14
  startupCommands?: NativeRuntimeStartupCommands;
14
15
  onCommandSessionChange?: (session: NativeCoreCommandSession | null) => void;
15
16
  onError?: (error: Error) => void;
17
+ onHandleInvalidated?: (message: EventHandleInvalidated) => void;
16
18
  onInvalidMessage?: (error: Error, value: unknown) => void;
17
19
  onStartupTerminalError?: (error: Error) => void;
18
20
  startupCommandSettlementTimeoutMs?: number;
@@ -39,6 +41,6 @@ type NativeProviderRuntimeProps = Omit<NativeRuntimeRootProps, "onCommandSession
39
41
  instanceId: string;
40
42
  onCommandSessionChange?: (session: NativeDirectCommandSession | null) => void;
41
43
  };
42
- export declare function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewProps, onCommandSessionChange, onError, onInvalidMessage, onStartupTerminalError, startupCommandSettlementTimeoutMs, startupCommands, viewNativeViewComponent, viewWebViewComponent, viewWebViewProps, }: NativeRuntimeRootProps): ReactElement;
44
+ export declare function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewProps, onCommandSessionChange, onError, onHandleInvalidated, onInvalidMessage, onStartupTerminalError, startupCommandSettlementTimeoutMs, startupCommands, viewNativeViewComponent, viewWebViewComponent, viewWebViewProps, }: NativeRuntimeRootProps): ReactElement;
43
45
  export declare function NativeProviderRuntime({ directSessionController, initOptions, initialConfigureOptions, instanceId, onCommandSessionChange, onError, ...runtimeProps }: NativeProviderRuntimeProps): ReactElement;
44
46
  export {};
@@ -26,7 +26,7 @@ async function dispatchStartupCommands(input) {
26
26
  });
27
27
  }
28
28
  }
29
- export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewProps, onCommandSessionChange, onError, onInvalidMessage, onStartupTerminalError, startupCommandSettlementTimeoutMs = DEFAULT_STARTUP_COMMAND_SETTLEMENT_TIMEOUT_MS, startupCommands, viewNativeViewComponent, viewWebViewComponent, viewWebViewProps, }) {
29
+ export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewProps, onCommandSessionChange, onError, onHandleInvalidated, onInvalidMessage, onStartupTerminalError, startupCommandSettlementTimeoutMs = DEFAULT_STARTUP_COMMAND_SETTLEMENT_TIMEOUT_MS, startupCommands, viewNativeViewComponent, viewWebViewComponent, viewWebViewProps, }) {
30
30
  const [coreHostRevision, setCoreHostRevision] = useState(0);
31
31
  const [session, setSession] = useState(null);
32
32
  const sessionRef = useRef(null);
@@ -36,6 +36,8 @@ export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewPr
36
36
  onErrorRef.current = onError;
37
37
  const onCommandSessionChangeRef = useRef(onCommandSessionChange);
38
38
  onCommandSessionChangeRef.current = onCommandSessionChange;
39
+ const onHandleInvalidatedRef = useRef(onHandleInvalidated);
40
+ onHandleInvalidatedRef.current = onHandleInvalidated;
39
41
  const onInvalidMessageRef = useRef(onInvalidMessage);
40
42
  onInvalidMessageRef.current = onInvalidMessage;
41
43
  const onStartupTerminalErrorRef = useRef(onStartupTerminalError);
@@ -77,6 +79,15 @@ export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewPr
77
79
  });
78
80
  const commandChannelOwner = createNativeCoreCommandChannel({
79
81
  coreConnection,
82
+ onHandleInvalidated: (message) => {
83
+ var _a;
84
+ try {
85
+ (_a = onHandleInvalidatedRef.current) === null || _a === void 0 ? void 0 : _a.call(onHandleInvalidatedRef, message);
86
+ }
87
+ catch (error) {
88
+ reportError(error);
89
+ }
90
+ },
80
91
  onInvalidMessage: (error, value) => { var _a; return (_a = onInvalidMessageRef.current) === null || _a === void 0 ? void 0 : _a.call(onInvalidMessageRef, error, value); },
81
92
  });
82
93
  const commandSessionOwner = createNativeCoreCommandSession({
@@ -25,6 +25,5 @@ export type NativeViewRecordController = {
25
25
  export declare function createNativeViewRecordController(input: {
26
26
  coreConnection: CoreWebViewHostConnection;
27
27
  onError: (error: Error) => void;
28
- readinessRetryDelayMs?: number;
29
28
  }): NativeViewRecordController;
30
29
  export {};
@@ -3,16 +3,14 @@ import { buildEventViewSize, eventViewSizeSchema, } from "@getuserfeedback/proto
3
3
  import { buildEventViewPrerendered } from "@getuserfeedback/protocol/internal/view-host-lifecycle-events";
4
4
  import { createViewPreparationReadinessRegistry, createViewPreparationRegistry, } from "./view-orchestration-runtime.js";
5
5
  const createNativeViewReadinessRegistry = () => createViewPreparationReadinessRegistry();
6
- const DEFAULT_READINESS_RETRY_DELAY_MS = 50;
7
6
  export function createNativeViewRecordController(input) {
8
- var _a;
9
7
  const entries = new Map();
10
8
  const listeners = new Set();
11
9
  const preparations = createViewPreparationRegistry();
10
+ let coreConnectionFailed = false;
12
11
  let disposed = false;
13
12
  let nextAllocationId = 0;
14
13
  let snapshot = Object.freeze([]);
15
- const readinessRetryDelayMs = (_a = input.readinessRetryDelayMs) !== null && _a !== void 0 ? _a : DEFAULT_READINESS_RETRY_DELAY_MS;
16
14
  const reportError = (error, fallbackMessage) => {
17
15
  try {
18
16
  input.onError(error instanceof Error ? error : new Error(fallbackMessage));
@@ -54,9 +52,9 @@ export function createNativeViewRecordController(input) {
54
52
  generation: action.gen,
55
53
  instanceId: action.instanceId,
56
54
  isActive: false,
55
+ isPreparationCommitted: false,
57
56
  openRequest: toOpenRequestMetadata(action.openRequest),
58
57
  preparation,
59
- preparationState: "claimed",
60
58
  size: null,
61
59
  srcdoc: action.srcdoc,
62
60
  viewReadinessDetach: null,
@@ -91,7 +89,7 @@ export function createNativeViewRecordController(input) {
91
89
  }
92
90
  };
93
91
  const handleCoreMessage = (value) => {
94
- if (disposed) {
92
+ if (coreConnectionFailed || disposed) {
95
93
  return;
96
94
  }
97
95
  const parsed = coreViewHostActionSchema.safeParse(value);
@@ -111,37 +109,26 @@ export function createNativeViewRecordController(input) {
111
109
  break;
112
110
  }
113
111
  };
114
- const completeConnectionReadiness = (delivery, entry, size) => {
115
- if (!size || disposed || entries.get(entry.viewId) !== entry) {
116
- return false;
112
+ const completeConnectionReadiness = (entry, size) => {
113
+ if (disposed || entries.get(entry.viewId) !== entry) {
114
+ return;
117
115
  }
118
- if (entry.preparationState === "claimed") {
116
+ if (!entry.isPreparationCommitted) {
119
117
  if (!entry.preparation.commit()) {
120
- return false;
118
+ return;
121
119
  }
122
- entry.preparationState = "committed";
123
- }
124
- if (delivery.stage === "pending-size") {
125
- input.coreConnection.transport.postMessage(buildEventViewSize(Object.assign({ gen: entry.generation, viewId: entry.viewId }, size)));
126
- delivery.stage = "pending-prerendered";
127
- }
128
- if (delivery.stage === "pending-prerendered") {
129
- input.coreConnection.transport.postMessage(buildEventViewPrerendered({
130
- gen: entry.generation,
131
- viewId: entry.viewId,
132
- }));
133
- delivery.stage = "delivered";
120
+ entry.isPreparationCommitted = true;
134
121
  }
135
- if (entry.preparationState !== "prepared") {
136
- entry.preparationState = "prepared";
137
- }
138
- notify();
139
- return true;
122
+ input.coreConnection.transport.postMessage(buildEventViewSize(Object.assign({ gen: entry.generation, viewId: entry.viewId }, size)));
123
+ input.coreConnection.transport.postMessage(buildEventViewPrerendered({
124
+ gen: entry.generation,
125
+ viewId: entry.viewId,
126
+ }));
140
127
  };
141
128
  const stopCoreTransport = input.coreConnection.transport.subscribe(handleCoreMessage);
142
129
  return {
143
130
  attachViewReadinessConnection({ allocationId, connection }) {
144
- if (disposed) {
131
+ if (coreConnectionFailed || disposed) {
145
132
  return null;
146
133
  }
147
134
  const { viewId } = connection;
@@ -153,12 +140,6 @@ export function createNativeViewRecordController(input) {
153
140
  }
154
141
  const previousDetach = entry.viewReadinessDetach;
155
142
  const connectionReadiness = createNativeViewReadinessRegistry().begin(viewId);
156
- const readinessDelivery = {
157
- stage: "awaiting-readiness",
158
- };
159
- let latestReadySize = null;
160
- let retryState = "available";
161
- let retryTimer = null;
162
143
  let detached = false;
163
144
  let subscribed = false;
164
145
  let unsubscribe = () => { };
@@ -168,10 +149,6 @@ export function createNativeViewRecordController(input) {
168
149
  return;
169
150
  }
170
151
  detached = true;
171
- if (retryTimer !== null) {
172
- clearTimeout(retryTimer);
173
- retryTimer = null;
174
- }
175
152
  unsubscribe();
176
153
  connectionReadiness.release();
177
154
  if (entry.viewReadinessDetach === detach) {
@@ -184,49 +161,33 @@ export function createNativeViewRecordController(input) {
184
161
  }
185
162
  }
186
163
  };
187
- const attemptReadinessDelivery = (isRetry = false) => {
188
- if (!latestReadySize ||
189
- readinessDelivery.stage === "awaiting-readiness" ||
190
- readinessDelivery.stage === "delivered" ||
164
+ const deliverReadiness = (size) => {
165
+ if (coreConnectionFailed ||
191
166
  detached ||
192
167
  disposed ||
193
168
  entries.get(viewId) !== entry ||
194
169
  entry.viewReadinessDetach !== detach) {
195
- return "idle";
196
- }
197
- if ((isRetry && retryState !== "scheduled") ||
198
- (!isRetry && retryState !== "available")) {
199
- return "failed";
200
- }
201
- if (isRetry) {
202
- retryState = "exhausted";
170
+ return;
203
171
  }
204
172
  try {
205
- if (completeConnectionReadiness(readinessDelivery, entry, latestReadySize)) {
206
- if (retryTimer !== null) {
207
- clearTimeout(retryTimer);
208
- retryTimer = null;
209
- }
210
- return "delivered";
211
- }
212
- return "idle";
173
+ completeConnectionReadiness(entry, size);
213
174
  }
214
175
  catch (error) {
215
- if (!isRetry) {
216
- retryState = "scheduled";
217
- retryTimer = setTimeout(() => {
218
- retryTimer = null;
219
- attemptReadinessDelivery(true);
220
- }, readinessRetryDelayMs);
176
+ coreConnectionFailed = true;
177
+ try {
178
+ input.coreConnection.disconnect();
179
+ }
180
+ catch (_a) {
181
+ // Disconnect observers cannot change readiness settlement.
221
182
  }
222
- else {
183
+ finally {
223
184
  reportError(error, "Native view readiness delivery failed");
224
185
  }
225
- return "failed";
226
186
  }
227
187
  };
228
188
  const handleMessage = (value) => {
229
- if (detached ||
189
+ if (coreConnectionFailed ||
190
+ detached ||
230
191
  disposed ||
231
192
  entries.get(viewId) !== entry ||
232
193
  entry.viewReadinessDetach !== detach) {
@@ -242,14 +203,11 @@ export function createNativeViewRecordController(input) {
242
203
  entry.size = size;
243
204
  const readySize = connectionReadiness.recordSize(size);
244
205
  if (readySize) {
245
- latestReadySize = readySize;
246
- readinessDelivery.stage = "pending-size";
247
- }
248
- else if (readinessDelivery.stage === "pending-size" ||
249
- readinessDelivery.stage === "pending-prerendered") {
250
- latestReadySize = size;
206
+ deliverReadiness(readySize);
251
207
  }
252
- if (attemptReadinessDelivery() !== "delivered") {
208
+ if (!coreConnectionFailed &&
209
+ !disposed &&
210
+ entries.get(viewId) === entry) {
253
211
  notify();
254
212
  }
255
213
  };
@@ -289,10 +247,8 @@ export function createNativeViewRecordController(input) {
289
247
  }
290
248
  const readySize = connectionReadiness.markHostPrepared();
291
249
  if (readySize) {
292
- latestReadySize = readySize;
293
- readinessDelivery.stage = "pending-size";
250
+ deliverReadiness(readySize);
294
251
  }
295
- attemptReadinessDelivery();
296
252
  return detached ? null : detach;
297
253
  },
298
254
  dispose() {
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 : "4.0.7";
6
+ export const REACT_NATIVE_SDK_VERSION = reactNativeSdkVersion.length > 0 ? reactNativeSdkVersion : "4.0.9";
@@ -10,6 +10,7 @@ export type WebViewBridgeMessageIdentity = {
10
10
  };
11
11
  export type WebViewHostConnection<TMessage extends WebViewBridgeMessageIdentity> = {
12
12
  connectionId: string;
13
+ disconnect: () => void;
13
14
  epoch: number;
14
15
  generation: number;
15
16
  postMessage: (message: unknown) => void;
@@ -142,6 +142,11 @@ export const createWebViewHostController = (input) => {
142
142
  const listeners = new Set();
143
143
  const connection = {
144
144
  connectionId: message.connectionId,
145
+ disconnect: () => {
146
+ if (activeConnection === connection) {
147
+ disconnect();
148
+ }
149
+ },
145
150
  epoch: message.epoch,
146
151
  generation: message.generation,
147
152
  isAnnounced: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getuserfeedback/react-native",
3
- "version": "4.0.7",
3
+ "version": "4.0.9",
4
4
  "description": "getuserfeedback React Native SDK",
5
5
  "keywords": [
6
6
  "getuserfeedback",