@getuserfeedback/react-native 1.3.12 → 1.3.14

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
@@ -14,9 +14,9 @@ import { parseWebViewTransportNativeMessage } from "@getuserfeedback/protocol/we
14
14
  import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
15
15
  import { useResolvedHostViewport } from "./host-viewport.js";
16
16
  import { toCommandFlowId, toCommandSettlementError, toRetryWithoutFlowHandle, } from "./provider-command-helpers.js";
17
+ import { useProviderCommandTransport } from "./provider-command-transport.js";
17
18
  import { clearProviderFlowHandleCache, createProviderFlowHandleCache, deleteCachedProviderFlowHandleId, deleteCachedProviderFlowId, getCachedProviderFlowHandleId, snapshotProviderFlowCommand, writeSettledProviderFlowHandleId, } from "./provider-flow-handle-cache.js";
18
- import { enqueueProviderNativeMessage, removeProviderNativeMessageId, removeProviderNativeMessageIds, replaceProviderNativeMessages, } from "./provider-message-queue.js";
19
- import { cancelProviderStartupCommands, clearProviderPendingCommands, createProviderPendingCommandRegistry, getProviderPendingCommand, getProviderPendingCommandEntries, getProviderPendingCommands, hasDeliveredProviderPendingCommands, isProviderPendingMessageStartup, registerProviderPendingCommand, reissueProviderPendingCommand, takeProviderPendingCommand, } from "./provider-pending-commands.js";
19
+ import { createProviderWidgetHostLifecycleController, HIDDEN_PROVIDER_WIDGET_HOST_VISIBILITY, } from "./provider-widget-host-lifecycle.js";
20
20
  import { ProviderWidgetHostOverlay, toProviderWidgetHostStyle, toProviderWidgetSheetHostViewport, } from "./provider-widget-host-overlay.js";
21
21
  import { REACT_NATIVE_SDK_VERSION } from "./version.js";
22
22
  import { WidgetHost, } from "./widget-host.js";
@@ -33,12 +33,6 @@ const CLIENT_META = {
33
33
  loader: "sdk",
34
34
  transport: "loader",
35
35
  };
36
- const HIDDEN_PROVIDER_WIDGET_HOST_VISIBILITY = {
37
- hostSourceVersion: null,
38
- isVisible: false,
39
- readySequence: null,
40
- sourceKey: null,
41
- };
42
36
  const createUniqueId = (prefix) => {
43
37
  if (typeof globalThis.crypto !== "undefined" &&
44
38
  typeof globalThis.crypto.randomUUID === "function") {
@@ -118,15 +112,10 @@ const toIdentifyCommandPayload = (identifyInput, traitsOrOptions, options) => {
118
112
  };
119
113
  };
120
114
  export function GetUserFeedbackProvider({ autoInit = true, children, commandTimeoutMs = DEFAULT_COMMAND_TIMEOUT_MS, configureOptions, hostViewport, instanceId: instanceIdProp, initOptions, loaderUrl, onCommandError, onInvalidWebMessage, onWebMessage, source, webViewComponent, webViewProps, }) {
121
- var _a, _b;
122
115
  const { View: NativeView } = useMemo(resolveReactNativeComponents, []);
123
116
  const [instanceId] = useState(() => { var _a; return (_a = normalizeOptionalString(instanceIdProp)) !== null && _a !== void 0 ? _a : createUniqueId("rn"); });
124
- const [nativeMessages, setNativeMessages] = useState([]);
125
117
  const [widgetHostVisibility, setWidgetHostVisibility] = useState(HIDDEN_PROVIDER_WIDGET_HOST_VISIBILITY);
126
118
  const resolvedHostViewport = useResolvedHostViewport(hostViewport);
127
- const nativeMessagesRef = useRef([]);
128
- const pendingCommandsRef = useRef(createProviderPendingCommandRegistry());
129
- const hostEpochRef = useRef(0);
130
119
  const flowHandleCacheRef = useRef(createProviderFlowHandleCache());
131
120
  const initOptionsRef = useRef(initOptions);
132
121
  const configureOptionsRef = useRef(configureOptions);
@@ -135,28 +124,23 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
135
124
  const hostSourceKey = useMemo(() => toJsonKey({ loaderUrl: loaderUrl !== null && loaderUrl !== void 0 ? loaderUrl : null, source: source !== null && source !== void 0 ? source : null }), [loaderUrl, source]);
136
125
  const startupOptionsKey = useMemo(() => toJsonKey({ configureOptionsKey, initOptionsKey }), [configureOptionsKey, initOptionsKey]);
137
126
  const startupIdempotencyKey = useMemo(() => toStableHash({ configureOptionsKey, initOptionsKey }), [configureOptionsKey, initOptionsKey]);
138
- const startupQueuedKeyRef = useRef(null);
139
- const lastReadyRef = useRef(null);
140
- const visibilitySourceKeyRef = useRef(hostSourceKey);
141
- const hostSourceKeyRef = useRef(hostSourceKey);
142
- const hostSourceVersionRef = useRef(0);
143
- if (hostSourceKeyRef.current !== hostSourceKey) {
144
- hostSourceKeyRef.current = hostSourceKey;
145
- hostSourceVersionRef.current += 1;
146
- for (const pendingCommand of getProviderPendingCommands(pendingCommandsRef.current)) {
147
- if (!pendingCommand.timeout) {
148
- continue;
149
- }
150
- clearTimeout(pendingCommand.timeout);
151
- pendingCommand.timeout = null;
152
- }
153
- lastReadyRef.current = null;
154
- startupQueuedKeyRef.current = null;
127
+ const widgetHostLifecycleRef = useRef(createProviderWidgetHostLifecycleController(hostSourceKey));
128
+ const widgetHostLifecycle = widgetHostLifecycleRef.current;
129
+ const { clearFlowHandleFromVisibility, getCurrentHostEpoch, isWidgetHostVisible: resolveIsWidgetHostVisible, markNotReady, markReady, syncHostSourceKey, toFlowStateVisibility, toReadyVisibility, } = widgetHostLifecycle;
130
+ const createCommandTimeoutError = useCallback((requestId) => new CommandSettlementTimeoutError(requestId), []);
131
+ const transport = useProviderCommandTransport({
132
+ commandTimeoutMs,
133
+ createTimeoutError: createCommandTimeoutError,
134
+ getCurrentHostEpoch,
135
+ });
136
+ const { nativeMessages, nativeMessagesRef, clearStartupQueuedKey, createPendingCommandPromise, getPendingCommand, getPendingCommandEntries, hasDeliveredPendingCommands, isStartupQueuedKey, pausePendingCommandTimeouts, registerNativeMessage, reissuePendingCommand, removeNativeMessageId, replaceNativeMessages, resetStartupQueuedKeyIfChanged, setStartupQueuedKey, startPendingCommandTimeouts, takePendingCommand, cancelStartupCommands, dispose: disposeCommandTransport, } = transport;
137
+ if (syncHostSourceKey(hostSourceKey)) {
138
+ pausePendingCommandTimeouts();
139
+ clearStartupQueuedKey();
155
140
  clearProviderFlowHandleCache(flowHandleCacheRef.current);
156
141
  }
157
142
  initOptionsRef.current = initOptions;
158
143
  configureOptionsRef.current = configureOptions;
159
- nativeMessagesRef.current = nativeMessages;
160
144
  const reportCommandError = useCallback((error) => {
161
145
  if (error.message === PROVIDER_UNMOUNTED_ERROR_MESSAGE ||
162
146
  error.message === STARTUP_COMMAND_REPLACED_ERROR_MESSAGE) {
@@ -170,45 +154,6 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
170
154
  throw error;
171
155
  }, 0);
172
156
  }, [onCommandError]);
173
- const rejectPendingCommand = useCallback((requestId, pendingCommand, error) => {
174
- takeProviderPendingCommand(pendingCommandsRef.current, requestId);
175
- if (pendingCommand.timeout) {
176
- clearTimeout(pendingCommand.timeout);
177
- pendingCommand.timeout = null;
178
- }
179
- if (pendingCommand.isStartup) {
180
- startupQueuedKeyRef.current = null;
181
- }
182
- setNativeMessages((messages) => removeProviderNativeMessageId(messages, pendingCommand.messageId));
183
- pendingCommand.reject(error);
184
- }, []);
185
- const startPendingCommandTimeout = useCallback((requestId, pendingCommand) => {
186
- var _a, _b;
187
- pendingCommand.hostEpoch = (_b = (_a = lastReadyRef.current) === null || _a === void 0 ? void 0 : _a.hostEpoch) !== null && _b !== void 0 ? _b : null;
188
- if (commandTimeoutMs <= 0) {
189
- return;
190
- }
191
- if (pendingCommand.timeout) {
192
- clearTimeout(pendingCommand.timeout);
193
- }
194
- pendingCommand.timeout = setTimeout(() => {
195
- rejectPendingCommand(requestId, pendingCommand, new CommandSettlementTimeoutError(requestId));
196
- }, commandTimeoutMs);
197
- }, [commandTimeoutMs, rejectPendingCommand]);
198
- const startPendingCommandTimeouts = useCallback(() => {
199
- for (const [requestId, pendingCommand] of getProviderPendingCommandEntries(pendingCommandsRef.current)) {
200
- startPendingCommandTimeout(requestId, pendingCommand);
201
- }
202
- }, [startPendingCommandTimeout]);
203
- const pausePendingCommandTimeouts = useCallback(() => {
204
- for (const pendingCommand of getProviderPendingCommands(pendingCommandsRef.current)) {
205
- if (!pendingCommand.timeout) {
206
- continue;
207
- }
208
- clearTimeout(pendingCommand.timeout);
209
- pendingCommand.timeout = null;
210
- }
211
- }, []);
212
157
  const enqueueTransportCommand = useCallback((command, options, { isStartup = false, prioritizeStartup = false, } = {}) => {
213
158
  var _a;
214
159
  const requestId = createCommandRequestId();
@@ -232,7 +177,7 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
232
177
  }
233
178
  catch (error) {
234
179
  if (isStartup) {
235
- startupQueuedKeyRef.current = null;
180
+ clearStartupQueuedKey();
236
181
  }
237
182
  return Promise.reject(error instanceof Error ? error : new Error(String(error)));
238
183
  }
@@ -241,35 +186,26 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
241
186
  message: validatedMessage,
242
187
  };
243
188
  const flowCommandSnapshot = snapshotProviderFlowCommand(flowHandleCacheRef.current, validatedMessage.envelope.command);
244
- const promise = new Promise((resolve, reject) => {
245
- const pendingCommand = {
246
- flowCommandSequence: flowCommandSnapshot.flowCommandSequence,
247
- flowHandleCacheEpoch: flowCommandSnapshot.flowHandleCacheEpoch,
248
- hostEpoch: null,
249
- isStartup,
250
- messageId: validatedMessage.id,
251
- reject,
252
- resolve,
253
- result,
254
- timeout: null,
255
- };
256
- registerProviderPendingCommand(pendingCommandsRef.current, requestId, pendingCommand);
257
- if (lastReadyRef.current !== null) {
258
- startPendingCommandTimeout(requestId, pendingCommand);
259
- }
260
- });
261
- setNativeMessages((messages) => enqueueProviderNativeMessage(messages, {
189
+ const promise = createPendingCommandPromise({
190
+ flowCommandSequence: flowCommandSnapshot.flowCommandSequence,
191
+ flowHandleCacheEpoch: flowCommandSnapshot.flowHandleCacheEpoch,
262
192
  isStartup,
263
- isStartupMessage: (entry) => isProviderPendingMessageStartup(pendingCommandsRef.current, entry.envelope.requestId),
264
193
  message: validatedMessage,
265
- prioritizeStartup,
266
- }));
194
+ requestId,
195
+ result,
196
+ });
197
+ registerNativeMessage(validatedMessage, { isStartup, prioritizeStartup });
267
198
  return promise;
268
- }, [instanceId, startPendingCommandTimeout]);
199
+ }, [
200
+ clearStartupQueuedKey,
201
+ createPendingCommandPromise,
202
+ instanceId,
203
+ registerNativeMessage,
204
+ ]);
269
205
  const reissueDeliveredPendingActionCommands = useCallback(() => {
270
206
  var _a;
271
207
  const replacements = new Map();
272
- for (const [requestId, pendingCommand] of getProviderPendingCommandEntries(pendingCommandsRef.current)) {
208
+ for (const [requestId, pendingCommand] of getPendingCommandEntries()) {
273
209
  if (pendingCommand.isStartup || pendingCommand.hostEpoch === null) {
274
210
  continue;
275
211
  }
@@ -284,9 +220,9 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
284
220
  id: createTransportMessageId(),
285
221
  envelope: nextEnvelope,
286
222
  });
287
- const reissuedCommand = reissueProviderPendingCommand(pendingCommandsRef.current, {
223
+ const reissuedCommand = reissuePendingCommand({
288
224
  flowHandleCacheEpoch: flowHandleCacheRef.current.epoch,
289
- nextMessageId: nextMessage.id,
225
+ nextMessage,
290
226
  nextRequestId: nextEnvelope.requestId,
291
227
  nextResult: {
292
228
  envelope: nextMessage.envelope,
@@ -302,34 +238,21 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
302
238
  if (replacements.size === 0) {
303
239
  return false;
304
240
  }
305
- setNativeMessages((messages) => replaceProviderNativeMessages(messages, replacements));
241
+ replaceNativeMessages(replacements);
306
242
  return true;
307
- }, []);
308
- const cancelPendingStartupCommands = useCallback(() => {
309
- const canceled = cancelProviderStartupCommands(pendingCommandsRef.current);
310
- for (const pendingCommand of canceled.commands) {
311
- if (pendingCommand.timeout) {
312
- clearTimeout(pendingCommand.timeout);
313
- }
314
- pendingCommand.reject(new Error(STARTUP_COMMAND_REPLACED_ERROR_MESSAGE));
315
- }
316
- if (canceled.messageIds.size === 0) {
317
- return;
318
- }
319
- setNativeMessages((messages) => removeProviderNativeMessageIds(messages, canceled.messageIds));
320
- }, []);
243
+ }, [getPendingCommandEntries, reissuePendingCommand, replaceNativeMessages]);
321
244
  const enqueueStartupCommands = useCallback(({ force = false } = {}) => {
322
245
  if (!autoInit) {
323
246
  return;
324
247
  }
325
- if (!force && startupQueuedKeyRef.current === startupOptionsKey) {
248
+ if (!force && isStartupQueuedKey(startupOptionsKey)) {
326
249
  return;
327
250
  }
328
- const isReplacingStartup = force || startupQueuedKeyRef.current !== startupOptionsKey;
251
+ const isReplacingStartup = force || !isStartupQueuedKey(startupOptionsKey);
329
252
  if (isReplacingStartup) {
330
- cancelPendingStartupCommands();
253
+ cancelStartupCommands(new Error(STARTUP_COMMAND_REPLACED_ERROR_MESSAGE));
331
254
  }
332
- startupQueuedKeyRef.current = startupOptionsKey;
255
+ setStartupQueuedKey(startupOptionsKey);
333
256
  enqueueTransportCommand({ kind: "init", opts: initOptionsRef.current }, { idempotencyKey: `${instanceId}:init:${startupIdempotencyKey}` }, { isStartup: true, prioritizeStartup: isReplacingStartup }).catch(reportCommandError);
334
257
  if (configureOptionsRef.current === undefined) {
335
258
  return;
@@ -337,10 +260,12 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
337
260
  enqueueTransportCommand({ kind: "configure", opts: configureOptionsRef.current }, { idempotencyKey: `${instanceId}:configure:${startupIdempotencyKey}` }, { isStartup: true, prioritizeStartup: isReplacingStartup }).catch(reportCommandError);
338
261
  }, [
339
262
  autoInit,
340
- cancelPendingStartupCommands,
263
+ cancelStartupCommands,
341
264
  enqueueTransportCommand,
342
265
  instanceId,
266
+ isStartupQueuedKey,
343
267
  reportCommandError,
268
+ setStartupQueuedKey,
344
269
  startupIdempotencyKey,
345
270
  startupOptionsKey,
346
271
  ]);
@@ -349,36 +274,30 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
349
274
  return enqueueTransportCommand(command, options);
350
275
  }, [enqueueStartupCommands, enqueueTransportCommand]);
351
276
  const handleWidgetReady = useCallback((event) => {
352
- const previous = lastReadyRef.current;
353
- const alreadySeen = (previous === null || previous === void 0 ? void 0 : previous.sourceKey) === event.sourceKey &&
354
- previous.readySequence === event.readySequence;
355
- if (alreadySeen) {
277
+ const readyResult = markReady(event, {
278
+ hasDeliveredPendingCommands: hasDeliveredPendingCommands(),
279
+ });
280
+ if (readyResult.alreadySeen) {
356
281
  return false;
357
282
  }
358
- const hasDeliveredPendingCommands = hasDeliveredProviderPendingCommands(pendingCommandsRef.current);
359
283
  let didReissueDeliveredCommands = false;
360
- if (previous !== null || hasDeliveredPendingCommands) {
361
- hostEpochRef.current += 1;
362
- startupQueuedKeyRef.current = null;
284
+ if (readyResult.didResetHostEpoch) {
285
+ clearStartupQueuedKey();
363
286
  clearProviderFlowHandleCache(flowHandleCacheRef.current);
364
287
  didReissueDeliveredCommands = reissueDeliveredPendingActionCommands();
365
288
  }
366
- lastReadyRef.current = Object.assign(Object.assign({}, event), { hostEpoch: hostEpochRef.current });
367
- setWidgetHostVisibility((visibility) => {
368
- if (!visibility.isVisible ||
369
- visibility.sourceKey !== event.sourceKey ||
370
- visibility.hostSourceVersion !== hostSourceVersionRef.current) {
371
- return visibility;
372
- }
373
- return Object.assign(Object.assign({}, visibility), { readySequence: event.readySequence });
374
- });
375
- enqueueStartupCommands({ force: previous !== null });
289
+ setWidgetHostVisibility((visibility) => toReadyVisibility(visibility, event));
290
+ enqueueStartupCommands({ force: readyResult.previousReady !== null });
376
291
  startPendingCommandTimeouts();
377
292
  return didReissueDeliveredCommands;
378
293
  }, [
379
294
  enqueueStartupCommands,
295
+ clearStartupQueuedKey,
296
+ hasDeliveredPendingCommands,
297
+ markReady,
380
298
  reissueDeliveredPendingActionCommands,
381
299
  startPendingCommandTimeouts,
300
+ toReadyVisibility,
382
301
  ]);
383
302
  const handleWidgetReadyStateChange = useCallback((event) => {
384
303
  if (event.isReady) {
@@ -390,17 +309,18 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
390
309
  deferNativeMessageInjection: didReissueDeliveredCommands,
391
310
  };
392
311
  }
393
- if (lastReadyRef.current !== null) {
394
- hostEpochRef.current += 1;
395
- startupQueuedKeyRef.current = null;
312
+ const notReadyResult = markNotReady();
313
+ if (notReadyResult.wasReady) {
314
+ clearStartupQueuedKey();
396
315
  clearProviderFlowHandleCache(flowHandleCacheRef.current);
397
316
  reissueDeliveredPendingActionCommands();
398
317
  }
399
- lastReadyRef.current = null;
400
318
  setWidgetHostVisibility(HIDDEN_PROVIDER_WIDGET_HOST_VISIBILITY);
401
319
  pausePendingCommandTimeouts();
402
320
  }, [
321
+ clearStartupQueuedKey,
403
322
  handleWidgetReady,
323
+ markNotReady,
404
324
  pausePendingCommandTimeouts,
405
325
  reissueDeliveredPendingActionCommands,
406
326
  ]);
@@ -409,29 +329,16 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
409
329
  }, []);
410
330
  const handleFlowHandleInvalidated = useCallback((flowHandleId) => {
411
331
  deleteCachedFlowHandleId(flowHandleId);
412
- setWidgetHostVisibility((visibility) => visibility.flowHandleId === flowHandleId
413
- ? Object.assign(Object.assign({}, visibility), { flowHandleId: undefined }) : visibility);
414
- }, [deleteCachedFlowHandleId]);
332
+ setWidgetHostVisibility((visibility) => clearFlowHandleFromVisibility(visibility, flowHandleId));
333
+ }, [clearFlowHandleFromVisibility, deleteCachedFlowHandleId]);
415
334
  const handleInstanceFlowStateChanged = useCallback((detail) => {
416
- if (detail.instanceId !== instanceId) {
417
- return;
418
- }
419
- const readyState = lastReadyRef.current;
420
- if (!readyState) {
335
+ const visibility = toFlowStateVisibility(detail, instanceId);
336
+ if (!visibility) {
421
337
  return;
422
338
  }
423
- const isVisible = detail.isOpen || detail.isLoading;
424
- setWidgetHostVisibility({
425
- flowHeight: detail.height,
426
- flowHandleId: detail.flowHandleId,
427
- hostSourceVersion: hostSourceVersionRef.current,
428
- isVisible,
429
- readySequence: readyState.readySequence,
430
- sourceKey: readyState.sourceKey,
431
- });
432
- }, [instanceId]);
339
+ setWidgetHostVisibility(visibility);
340
+ }, [instanceId, toFlowStateVisibility]);
433
341
  const handleWebMessage = useCallback((message) => {
434
- var _a;
435
342
  if (message.kind === "hostEvent" &&
436
343
  message.event.name === "instance:handle:invalidated" &&
437
344
  message.event.detail.handleKind === "flow") {
@@ -444,22 +351,22 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
444
351
  if (message.kind === "hostEvent" &&
445
352
  message.event.name === "instance:command:settled") {
446
353
  const requestId = message.event.detail.requestId;
447
- const pendingCommand = getProviderPendingCommand(pendingCommandsRef.current, requestId);
354
+ const pendingCommand = getPendingCommand(requestId);
448
355
  if (pendingCommand) {
449
356
  if (pendingCommand.hostEpoch !== null &&
450
- pendingCommand.hostEpoch !== ((_a = lastReadyRef.current) === null || _a === void 0 ? void 0 : _a.hostEpoch)) {
357
+ pendingCommand.hostEpoch !== getCurrentHostEpoch()) {
451
358
  onWebMessage === null || onWebMessage === void 0 ? void 0 : onWebMessage(message);
452
359
  return;
453
360
  }
454
- takeProviderPendingCommand(pendingCommandsRef.current, requestId);
455
- setNativeMessages((messages) => removeProviderNativeMessageId(messages, pendingCommand.messageId));
361
+ takePendingCommand(requestId);
362
+ removeNativeMessageId(pendingCommand.messageId);
456
363
  if (pendingCommand.timeout) {
457
364
  clearTimeout(pendingCommand.timeout);
458
365
  }
459
366
  const error = toCommandSettlementError(message.event);
460
367
  if (error) {
461
368
  if (pendingCommand.isStartup) {
462
- startupQueuedKeyRef.current = null;
369
+ clearStartupQueuedKey();
463
370
  }
464
371
  const flowId = toCommandFlowId(pendingCommand.result.envelope.command);
465
372
  const isStaleFlowHandleError = error.code === "STALE_HANDLE" ||
@@ -496,30 +403,23 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
496
403
  onWebMessage === null || onWebMessage === void 0 ? void 0 : onWebMessage(message);
497
404
  }, [
498
405
  enqueueTransportCommand,
406
+ clearStartupQueuedKey,
407
+ getCurrentHostEpoch,
408
+ getPendingCommand,
499
409
  handleInstanceFlowStateChanged,
500
410
  handleFlowHandleInvalidated,
501
411
  onWebMessage,
412
+ removeNativeMessageId,
413
+ takePendingCommand,
502
414
  ]);
503
415
  useEffect(() => () => {
504
- for (const pendingCommand of getProviderPendingCommands(pendingCommandsRef.current)) {
505
- if (pendingCommand.timeout) {
506
- clearTimeout(pendingCommand.timeout);
507
- }
508
- pendingCommand.reject(new Error(PROVIDER_UNMOUNTED_ERROR_MESSAGE));
509
- }
510
- clearProviderPendingCommands(pendingCommandsRef.current);
511
- }, []);
512
- useEffect(() => {
513
- if (visibilitySourceKeyRef.current === hostSourceKey) {
514
- return;
515
- }
516
- visibilitySourceKeyRef.current = hostSourceKey;
517
- setWidgetHostVisibility(HIDDEN_PROVIDER_WIDGET_HOST_VISIBILITY);
518
- }, [hostSourceKey]);
416
+ disposeCommandTransport(new Error(PROVIDER_UNMOUNTED_ERROR_MESSAGE));
417
+ }, [disposeCommandTransport]);
418
+ const getNativeMessages = useCallback(() => nativeMessagesRef.current, [nativeMessagesRef]);
519
419
  const contextValue = useMemo(() => ({
520
420
  instanceId,
521
421
  get nativeMessages() {
522
- return nativeMessagesRef.current;
422
+ return getNativeMessages();
523
423
  },
524
424
  enqueueCommand,
525
425
  configure: (opts, options) => enqueueCommand({ kind: "configure", opts }, options),
@@ -570,22 +470,20 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
570
470
  updateHostContext: (context, options) => enqueueCommand({ kind: "updateHostContext", context }, options),
571
471
  emitHostSignal: (name, data, options) => enqueueCommand(Object.assign({ kind: "emitHostSignal", name: resolveRequiredString("name", name) }, (data === undefined ? {} : { data })), options),
572
472
  handleWebMessage,
573
- }), [enqueueCommand, handleWebMessage, instanceId]);
473
+ }), [enqueueCommand, getNativeMessages, handleWebMessage, instanceId]);
574
474
  useEffect(() => {
575
- if (startupQueuedKeyRef.current !== startupOptionsKey) {
576
- startupQueuedKeyRef.current = null;
577
- }
475
+ resetStartupQueuedKeyIfChanged(startupOptionsKey);
578
476
  enqueueStartupCommands();
579
- }, [enqueueStartupCommands, startupOptionsKey]);
477
+ }, [
478
+ enqueueStartupCommands,
479
+ resetStartupQueuedKeyIfChanged,
480
+ startupOptionsKey,
481
+ ]);
580
482
  const providerHostViewport = hostViewport !== null && hostViewport !== void 0 ? hostViewport : resolvedHostViewport;
581
483
  const sheetHostViewport = toProviderWidgetSheetHostViewport(providerHostViewport, {
582
484
  height: widgetHostVisibility.flowHeight,
583
485
  });
584
- const isWidgetHostVisible = widgetHostVisibility.isVisible &&
585
- sheetHostViewport !== undefined &&
586
- widgetHostVisibility.hostSourceVersion === hostSourceVersionRef.current &&
587
- widgetHostVisibility.sourceKey === ((_a = lastReadyRef.current) === null || _a === void 0 ? void 0 : _a.sourceKey) &&
588
- widgetHostVisibility.readySequence === ((_b = lastReadyRef.current) === null || _b === void 0 ? void 0 : _b.readySequence);
486
+ const isWidgetHostVisible = resolveIsWidgetHostVisible(widgetHostVisibility, sheetHostViewport !== undefined);
589
487
  const widgetHostViewport = providerHostViewport;
590
488
  const composedWebViewStyle = toProviderWidgetHostStyle({
591
489
  isVisible: isWidgetHostVisible,
@@ -0,0 +1,54 @@
1
+ import type { WebViewTransportNativeMessage } from "@getuserfeedback/protocol/webview-transport";
2
+ import { type ProviderPendingCommand } from "./provider-pending-commands.js";
3
+ export type ProviderCommandTransportOptions = {
4
+ commandTimeoutMs: number;
5
+ createTimeoutError: (requestId: string) => Error;
6
+ getCurrentHostEpoch: () => number | null;
7
+ };
8
+ export type ProviderPendingCommandRegistration<Result> = {
9
+ flowCommandSequence: number | null;
10
+ flowHandleCacheEpoch: number;
11
+ isStartup: boolean;
12
+ message: WebViewTransportNativeMessage;
13
+ requestId: string;
14
+ result: Result;
15
+ };
16
+ export type ProviderPendingCommandReissue<Result> = {
17
+ flowHandleCacheEpoch: number;
18
+ nextMessage: WebViewTransportNativeMessage;
19
+ nextRequestId: string;
20
+ nextResult: Result;
21
+ requestId: string;
22
+ };
23
+ export type ProviderCommandTransport<Result> = {
24
+ clearStartupQueuedKey: () => void;
25
+ createPendingCommandPromise: (input: ProviderPendingCommandRegistration<Result>) => Promise<Result>;
26
+ dispose: (error: Error) => void;
27
+ getPendingCommand: (requestId: string) => ProviderPendingCommand<Result> | undefined;
28
+ getPendingCommandEntries: () => Array<[
29
+ string,
30
+ ProviderPendingCommand<Result>
31
+ ]>;
32
+ hasDeliveredPendingCommands: () => boolean;
33
+ isStartupQueuedKey: (key: string) => boolean;
34
+ nativeMessages: WebViewTransportNativeMessage[];
35
+ nativeMessagesRef: {
36
+ current: WebViewTransportNativeMessage[];
37
+ };
38
+ pausePendingCommandTimeouts: () => void;
39
+ registerNativeMessage: (message: WebViewTransportNativeMessage, options: {
40
+ isStartup: boolean;
41
+ prioritizeStartup: boolean;
42
+ }) => void;
43
+ reissuePendingCommand: (input: ProviderPendingCommandReissue<Result>) => {
44
+ previousMessageId: string;
45
+ } | null;
46
+ removeNativeMessageId: (messageId: string) => void;
47
+ replaceNativeMessages: (replacements: ReadonlyMap<string, WebViewTransportNativeMessage>) => void;
48
+ resetStartupQueuedKeyIfChanged: (key: string) => void;
49
+ setStartupQueuedKey: (key: string) => void;
50
+ startPendingCommandTimeouts: () => void;
51
+ takePendingCommand: (requestId: string) => ProviderPendingCommand<Result> | undefined;
52
+ cancelStartupCommands: (error: Error) => void;
53
+ };
54
+ export declare function useProviderCommandTransport<Result>({ commandTimeoutMs, createTimeoutError, getCurrentHostEpoch, }: ProviderCommandTransportOptions): ProviderCommandTransport<Result>;
@@ -0,0 +1,153 @@
1
+ import { useCallback, useRef, useState } from "react";
2
+ import { enqueueProviderNativeMessage, removeProviderNativeMessageId, removeProviderNativeMessageIds, replaceProviderNativeMessages, } from "./provider-message-queue.js";
3
+ import { cancelProviderStartupCommands, clearProviderPendingCommands, createProviderPendingCommandRegistry, getProviderPendingCommand, getProviderPendingCommandEntries, getProviderPendingCommands, hasDeliveredProviderPendingCommands, isProviderPendingMessageStartup, registerProviderPendingCommand, reissueProviderPendingCommand, takeProviderPendingCommand, } from "./provider-pending-commands.js";
4
+ export function useProviderCommandTransport({ commandTimeoutMs, createTimeoutError, getCurrentHostEpoch, }) {
5
+ const [nativeMessages, setNativeMessages] = useState([]);
6
+ const nativeMessagesRef = useRef([]);
7
+ const pendingCommandsRef = useRef(createProviderPendingCommandRegistry());
8
+ const startupQueuedKeyRef = useRef(null);
9
+ nativeMessagesRef.current = nativeMessages;
10
+ const removeNativeMessageId = useCallback((messageId) => {
11
+ setNativeMessages((messages) => removeProviderNativeMessageId(messages, messageId));
12
+ }, []);
13
+ const removeNativeMessageIds = useCallback((messageIds) => {
14
+ setNativeMessages((messages) => removeProviderNativeMessageIds(messages, messageIds));
15
+ }, []);
16
+ const rejectPendingCommand = useCallback((requestId, pendingCommand, error) => {
17
+ takeProviderPendingCommand(pendingCommandsRef.current, requestId);
18
+ if (pendingCommand.timeout) {
19
+ clearTimeout(pendingCommand.timeout);
20
+ pendingCommand.timeout = null;
21
+ }
22
+ if (pendingCommand.isStartup) {
23
+ startupQueuedKeyRef.current = null;
24
+ }
25
+ removeNativeMessageId(pendingCommand.messageId);
26
+ pendingCommand.reject(error);
27
+ }, [removeNativeMessageId]);
28
+ const startPendingCommandTimeout = useCallback((requestId, pendingCommand) => {
29
+ pendingCommand.hostEpoch = getCurrentHostEpoch();
30
+ if (commandTimeoutMs <= 0) {
31
+ return;
32
+ }
33
+ if (pendingCommand.timeout) {
34
+ clearTimeout(pendingCommand.timeout);
35
+ }
36
+ pendingCommand.timeout = setTimeout(() => {
37
+ rejectPendingCommand(requestId, pendingCommand, createTimeoutError(requestId));
38
+ }, commandTimeoutMs);
39
+ }, [
40
+ commandTimeoutMs,
41
+ createTimeoutError,
42
+ getCurrentHostEpoch,
43
+ rejectPendingCommand,
44
+ ]);
45
+ const startPendingCommandTimeouts = useCallback(() => {
46
+ for (const [requestId, pendingCommand] of getProviderPendingCommandEntries(pendingCommandsRef.current)) {
47
+ startPendingCommandTimeout(requestId, pendingCommand);
48
+ }
49
+ }, [startPendingCommandTimeout]);
50
+ const pausePendingCommandTimeouts = useCallback(() => {
51
+ for (const pendingCommand of getProviderPendingCommands(pendingCommandsRef.current)) {
52
+ if (!pendingCommand.timeout) {
53
+ continue;
54
+ }
55
+ clearTimeout(pendingCommand.timeout);
56
+ pendingCommand.timeout = null;
57
+ }
58
+ }, []);
59
+ const createPendingCommandPromise = useCallback(({ flowCommandSequence, flowHandleCacheEpoch, isStartup, message, requestId, result, }) => new Promise((resolve, reject) => {
60
+ const pendingCommand = {
61
+ flowCommandSequence,
62
+ flowHandleCacheEpoch,
63
+ hostEpoch: null,
64
+ isStartup,
65
+ messageId: message.id,
66
+ reject,
67
+ resolve,
68
+ result,
69
+ timeout: null,
70
+ };
71
+ registerProviderPendingCommand(pendingCommandsRef.current, requestId, pendingCommand);
72
+ if (getCurrentHostEpoch() !== null) {
73
+ startPendingCommandTimeout(requestId, pendingCommand);
74
+ }
75
+ }), [getCurrentHostEpoch, startPendingCommandTimeout]);
76
+ const registerNativeMessage = useCallback((message, options) => {
77
+ setNativeMessages((messages) => enqueueProviderNativeMessage(messages, Object.assign(Object.assign({}, options), { isStartupMessage: (entry) => isProviderPendingMessageStartup(pendingCommandsRef.current, entry.envelope.requestId), message })));
78
+ }, []);
79
+ const replaceNativeMessages = useCallback((replacements) => {
80
+ setNativeMessages((messages) => replaceProviderNativeMessages(messages, replacements));
81
+ }, []);
82
+ const reissuePendingCommand = useCallback(({ flowHandleCacheEpoch, nextMessage, nextRequestId, nextResult, requestId, }) => {
83
+ const reissuedCommand = reissueProviderPendingCommand(pendingCommandsRef.current, {
84
+ flowHandleCacheEpoch,
85
+ nextMessageId: nextMessage.id,
86
+ nextRequestId,
87
+ nextResult,
88
+ requestId,
89
+ });
90
+ return reissuedCommand
91
+ ? { previousMessageId: reissuedCommand.previousMessageId }
92
+ : null;
93
+ }, []);
94
+ const cancelStartupCommands = useCallback((error) => {
95
+ const canceled = cancelProviderStartupCommands(pendingCommandsRef.current);
96
+ for (const pendingCommand of canceled.commands) {
97
+ if (pendingCommand.timeout) {
98
+ clearTimeout(pendingCommand.timeout);
99
+ }
100
+ pendingCommand.reject(error);
101
+ }
102
+ if (canceled.messageIds.size > 0) {
103
+ removeNativeMessageIds(canceled.messageIds);
104
+ }
105
+ }, [removeNativeMessageIds]);
106
+ const dispose = useCallback((error) => {
107
+ for (const pendingCommand of getProviderPendingCommands(pendingCommandsRef.current)) {
108
+ if (pendingCommand.timeout) {
109
+ clearTimeout(pendingCommand.timeout);
110
+ }
111
+ pendingCommand.reject(error);
112
+ }
113
+ clearProviderPendingCommands(pendingCommandsRef.current);
114
+ startupQueuedKeyRef.current = null;
115
+ }, []);
116
+ const clearStartupQueuedKey = useCallback(() => {
117
+ startupQueuedKeyRef.current = null;
118
+ }, []);
119
+ const getPendingCommand = useCallback((requestId) => getProviderPendingCommand(pendingCommandsRef.current, requestId), []);
120
+ const getPendingCommandEntries = useCallback(() => getProviderPendingCommandEntries(pendingCommandsRef.current), []);
121
+ const hasDeliveredPendingCommands = useCallback(() => hasDeliveredProviderPendingCommands(pendingCommandsRef.current), []);
122
+ const isStartupQueuedKey = useCallback((key) => startupQueuedKeyRef.current === key, []);
123
+ const resetStartupQueuedKeyIfChanged = useCallback((key) => {
124
+ if (startupQueuedKeyRef.current !== key) {
125
+ startupQueuedKeyRef.current = null;
126
+ }
127
+ }, []);
128
+ const setStartupQueuedKey = useCallback((key) => {
129
+ startupQueuedKeyRef.current = key;
130
+ }, []);
131
+ const takePendingCommand = useCallback((requestId) => takeProviderPendingCommand(pendingCommandsRef.current, requestId), []);
132
+ return {
133
+ cancelStartupCommands,
134
+ clearStartupQueuedKey,
135
+ createPendingCommandPromise,
136
+ dispose,
137
+ getPendingCommand,
138
+ getPendingCommandEntries,
139
+ hasDeliveredPendingCommands,
140
+ isStartupQueuedKey,
141
+ nativeMessages,
142
+ nativeMessagesRef,
143
+ pausePendingCommandTimeouts,
144
+ registerNativeMessage,
145
+ reissuePendingCommand,
146
+ removeNativeMessageId,
147
+ replaceNativeMessages,
148
+ resetStartupQueuedKeyIfChanged,
149
+ setStartupQueuedKey,
150
+ startPendingCommandTimeouts,
151
+ takePendingCommand,
152
+ };
153
+ }
@@ -0,0 +1,46 @@
1
+ export type ProviderWidgetHostReadyState = {
2
+ hostEpoch: number;
3
+ readySequence: number;
4
+ sourceKey: string;
5
+ };
6
+ export type ProviderWidgetHostVisibility = {
7
+ flowHeight?: number;
8
+ flowHandleId?: string;
9
+ hostSourceVersion: number | null;
10
+ isVisible: boolean;
11
+ readySequence: number | null;
12
+ sourceKey: string | null;
13
+ };
14
+ export type ProviderWidgetHostFlowState = {
15
+ height?: number;
16
+ flowHandleId?: string;
17
+ instanceId: string;
18
+ isLoading: boolean;
19
+ isOpen: boolean;
20
+ };
21
+ export declare const HIDDEN_PROVIDER_WIDGET_HOST_VISIBILITY: ProviderWidgetHostVisibility;
22
+ export type ProviderWidgetHostLifecycleController = {
23
+ clearFlowHandleFromVisibility: (visibility: ProviderWidgetHostVisibility, flowHandleId: string) => ProviderWidgetHostVisibility;
24
+ getCurrentHostEpoch: () => number | null;
25
+ isWidgetHostVisible: (visibility: ProviderWidgetHostVisibility, hasSheetHostViewport: boolean) => boolean;
26
+ markNotReady: () => {
27
+ wasReady: boolean;
28
+ };
29
+ markReady: (event: {
30
+ readySequence: number;
31
+ sourceKey: string;
32
+ }, options: {
33
+ hasDeliveredPendingCommands: boolean;
34
+ }) => {
35
+ alreadySeen: boolean;
36
+ didResetHostEpoch: boolean;
37
+ previousReady: ProviderWidgetHostReadyState | null;
38
+ };
39
+ syncHostSourceKey: (hostSourceKey: string) => boolean;
40
+ toFlowStateVisibility: (detail: ProviderWidgetHostFlowState, instanceId: string) => ProviderWidgetHostVisibility | null;
41
+ toReadyVisibility: (visibility: ProviderWidgetHostVisibility, event: {
42
+ readySequence: number;
43
+ sourceKey: string;
44
+ }) => ProviderWidgetHostVisibility;
45
+ };
46
+ export declare function createProviderWidgetHostLifecycleController(initialHostSourceKey: string): ProviderWidgetHostLifecycleController;
@@ -0,0 +1,82 @@
1
+ export const HIDDEN_PROVIDER_WIDGET_HOST_VISIBILITY = {
2
+ hostSourceVersion: null,
3
+ isVisible: false,
4
+ readySequence: null,
5
+ sourceKey: null,
6
+ };
7
+ export function createProviderWidgetHostLifecycleController(initialHostSourceKey) {
8
+ let hostEpoch = 0;
9
+ let hostSourceKey = initialHostSourceKey;
10
+ let hostSourceVersion = 0;
11
+ let lastReady = null;
12
+ return {
13
+ clearFlowHandleFromVisibility: (visibility, flowHandleId) => visibility.flowHandleId === flowHandleId
14
+ ? Object.assign(Object.assign({}, visibility), { flowHandleId: undefined }) : visibility,
15
+ getCurrentHostEpoch: () => { var _a; return (_a = lastReady === null || lastReady === void 0 ? void 0 : lastReady.hostEpoch) !== null && _a !== void 0 ? _a : null; },
16
+ isWidgetHostVisible: (visibility, hasSheetHostViewport) => visibility.isVisible &&
17
+ hasSheetHostViewport &&
18
+ visibility.hostSourceVersion === hostSourceVersion &&
19
+ visibility.sourceKey === (lastReady === null || lastReady === void 0 ? void 0 : lastReady.sourceKey) &&
20
+ visibility.readySequence === (lastReady === null || lastReady === void 0 ? void 0 : lastReady.readySequence),
21
+ markNotReady: () => {
22
+ const wasReady = lastReady !== null;
23
+ if (wasReady) {
24
+ hostEpoch += 1;
25
+ }
26
+ lastReady = null;
27
+ return { wasReady };
28
+ },
29
+ markReady: (event, { hasDeliveredPendingCommands }) => {
30
+ const previousReady = lastReady;
31
+ const alreadySeen = (previousReady === null || previousReady === void 0 ? void 0 : previousReady.sourceKey) === event.sourceKey &&
32
+ previousReady.readySequence === event.readySequence;
33
+ if (alreadySeen) {
34
+ return {
35
+ alreadySeen: true,
36
+ didResetHostEpoch: false,
37
+ previousReady,
38
+ };
39
+ }
40
+ const didResetHostEpoch = previousReady !== null || hasDeliveredPendingCommands;
41
+ if (didResetHostEpoch) {
42
+ hostEpoch += 1;
43
+ }
44
+ lastReady = Object.assign(Object.assign({}, event), { hostEpoch });
45
+ return {
46
+ alreadySeen: false,
47
+ didResetHostEpoch,
48
+ previousReady,
49
+ };
50
+ },
51
+ syncHostSourceKey: (nextHostSourceKey) => {
52
+ if (hostSourceKey === nextHostSourceKey) {
53
+ return false;
54
+ }
55
+ hostSourceKey = nextHostSourceKey;
56
+ hostSourceVersion += 1;
57
+ lastReady = null;
58
+ return true;
59
+ },
60
+ toFlowStateVisibility: (detail, instanceId) => {
61
+ if (detail.instanceId !== instanceId || lastReady === null) {
62
+ return null;
63
+ }
64
+ return {
65
+ flowHeight: detail.height,
66
+ flowHandleId: detail.flowHandleId,
67
+ hostSourceVersion,
68
+ isVisible: detail.isOpen || detail.isLoading,
69
+ readySequence: lastReady.readySequence,
70
+ sourceKey: lastReady.sourceKey,
71
+ };
72
+ },
73
+ toReadyVisibility: (visibility, event) => {
74
+ if (!visibility.isVisible ||
75
+ visibility.sourceKey !== event.sourceKey ||
76
+ visibility.hostSourceVersion !== hostSourceVersion) {
77
+ return visibility;
78
+ }
79
+ return Object.assign(Object.assign({}, visibility), { readySequence: event.readySequence });
80
+ },
81
+ };
82
+ }
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 : "1.3.12";
6
+ export const REACT_NATIVE_SDK_VERSION = reactNativeSdkVersion.length > 0 ? reactNativeSdkVersion : "1.3.14";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getuserfeedback/react-native",
3
- "version": "1.3.12",
3
+ "version": "1.3.14",
4
4
  "description": "getuserfeedback React Native SDK",
5
5
  "keywords": [
6
6
  "getuserfeedback",
@@ -44,8 +44,8 @@
44
44
  "lint": "ultracite check ."
45
45
  },
46
46
  "dependencies": {
47
- "@getuserfeedback/protocol": "^1.6.0",
48
- "@getuserfeedback/sdk": "^0.7.5",
47
+ "@getuserfeedback/protocol": "^2.0.0",
48
+ "@getuserfeedback/sdk": "^0.7.7",
49
49
  "robot3": "^1.2.0"
50
50
  },
51
51
  "peerDependencies": {