@getuserfeedback/react-native 1.3.7 → 1.3.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.
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ import { useResolvedHostViewport } from "./host-viewport.js";
16
16
  import { toCommandFlowId, toCommandSettlementError, toRetryWithoutFlowHandle, } from "./provider-command-helpers.js";
17
17
  import { clearProviderFlowHandleCache, createProviderFlowHandleCache, deleteCachedProviderFlowHandleId, deleteCachedProviderFlowId, getCachedProviderFlowHandleId, snapshotProviderFlowCommand, writeSettledProviderFlowHandleId, } from "./provider-flow-handle-cache.js";
18
18
  import { enqueueProviderNativeMessage, removeProviderNativeMessageId, removeProviderNativeMessageIds, replaceProviderNativeMessages, } from "./provider-message-queue.js";
19
- import { applyProviderOverlayFlowState, applyProviderOverlayHandleInvalidated, createDefaultProviderOverlayState, DEFAULT_PROVIDER_OVERLAY_VISIBILITY, updateProviderOverlayReadySequence, } from "./provider-overlay-state.js";
19
+ import { cancelProviderStartupCommands, clearProviderPendingCommands, createProviderPendingCommandRegistry, getProviderPendingCommand, getProviderPendingCommandEntries, getProviderPendingCommands, hasDeliveredProviderPendingCommands, isProviderPendingMessageStartup, registerProviderPendingCommand, reissueProviderPendingCommand, takeProviderPendingCommand, } from "./provider-pending-commands.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,6 +33,12 @@ 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
+ };
36
42
  const createUniqueId = (prefix) => {
37
43
  if (typeof globalThis.crypto !== "undefined" &&
38
44
  typeof globalThis.crypto.randomUUID === "function") {
@@ -70,14 +76,24 @@ const FallbackNativeView = (_a) => {
70
76
  var { children } = _a, props = __rest(_a, ["children"]);
71
77
  return createElement("div", props, children);
72
78
  };
73
- function resolveNativeView() {
74
- var _a;
79
+ const FallbackNativePressable = FallbackNativeView;
80
+ const FallbackNativeText = FallbackNativeView;
81
+ function resolveReactNativeComponents() {
82
+ var _a, _b, _c;
75
83
  try {
76
84
  const reactNativeModule = require("react-native");
77
- return (_a = reactNativeModule.View) !== null && _a !== void 0 ? _a : FallbackNativeView;
85
+ return {
86
+ Pressable: (_a = reactNativeModule.Pressable) !== null && _a !== void 0 ? _a : FallbackNativePressable,
87
+ Text: (_b = reactNativeModule.Text) !== null && _b !== void 0 ? _b : FallbackNativeText,
88
+ View: (_c = reactNativeModule.View) !== null && _c !== void 0 ? _c : FallbackNativeView,
89
+ };
78
90
  }
79
- catch (_b) {
80
- return FallbackNativeView;
91
+ catch (_d) {
92
+ return {
93
+ Pressable: FallbackNativePressable,
94
+ Text: FallbackNativeText,
95
+ View: FallbackNativeView,
96
+ };
81
97
  }
82
98
  }
83
99
  const toIdentifyCommandPayload = (identifyInput, traitsOrOptions, options) => {
@@ -109,16 +125,15 @@ const toIdentifyCommandPayload = (identifyInput, traitsOrOptions, options) => {
109
125
  };
110
126
  export function GetUserFeedbackProvider({ autoInit = true, children, commandTimeoutMs = DEFAULT_COMMAND_TIMEOUT_MS, configureOptions, hostViewport, instanceId: instanceIdProp, initOptions, loaderUrl, onCommandError, onInvalidWebMessage, onWebMessage, source, webViewComponent, webViewProps, }) {
111
127
  var _a, _b;
112
- const NativeView = useMemo(resolveNativeView, []);
128
+ const { Pressable: NativePressable, Text: NativeText, View: NativeView, } = useMemo(resolveReactNativeComponents, []);
113
129
  const [instanceId] = useState(() => { var _a; return (_a = normalizeOptionalString(instanceIdProp)) !== null && _a !== void 0 ? _a : createUniqueId("rn"); });
114
130
  const [nativeMessages, setNativeMessages] = useState([]);
115
- const [widgetHostVisibility, setWidgetHostVisibility] = useState(DEFAULT_PROVIDER_OVERLAY_VISIBILITY);
131
+ const [widgetHostVisibility, setWidgetHostVisibility] = useState(HIDDEN_PROVIDER_WIDGET_HOST_VISIBILITY);
116
132
  const resolvedHostViewport = useResolvedHostViewport(hostViewport);
117
133
  const nativeMessagesRef = useRef([]);
118
- const pendingCommandsRef = useRef(new Map());
134
+ const pendingCommandsRef = useRef(createProviderPendingCommandRegistry());
119
135
  const hostEpochRef = useRef(0);
120
136
  const flowHandleCacheRef = useRef(createProviderFlowHandleCache());
121
- const providerOverlayStateRef = useRef(createDefaultProviderOverlayState());
122
137
  const initOptionsRef = useRef(initOptions);
123
138
  const configureOptionsRef = useRef(configureOptions);
124
139
  const initOptionsKey = useMemo(() => toJsonKey(initOptions), [initOptions]);
@@ -134,7 +149,7 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
134
149
  if (hostSourceKeyRef.current !== hostSourceKey) {
135
150
  hostSourceKeyRef.current = hostSourceKey;
136
151
  hostSourceVersionRef.current += 1;
137
- for (const pendingCommand of pendingCommandsRef.current.values()) {
152
+ for (const pendingCommand of getProviderPendingCommands(pendingCommandsRef.current)) {
138
153
  if (!pendingCommand.timeout) {
139
154
  continue;
140
155
  }
@@ -144,7 +159,6 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
144
159
  lastReadyRef.current = null;
145
160
  startupQueuedKeyRef.current = null;
146
161
  clearProviderFlowHandleCache(flowHandleCacheRef.current);
147
- providerOverlayStateRef.current = createDefaultProviderOverlayState();
148
162
  }
149
163
  initOptionsRef.current = initOptions;
150
164
  configureOptionsRef.current = configureOptions;
@@ -163,7 +177,7 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
163
177
  }, 0);
164
178
  }, [onCommandError]);
165
179
  const rejectPendingCommand = useCallback((requestId, pendingCommand, error) => {
166
- pendingCommandsRef.current.delete(requestId);
180
+ takeProviderPendingCommand(pendingCommandsRef.current, requestId);
167
181
  if (pendingCommand.timeout) {
168
182
  clearTimeout(pendingCommand.timeout);
169
183
  pendingCommand.timeout = null;
@@ -188,12 +202,12 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
188
202
  }, commandTimeoutMs);
189
203
  }, [commandTimeoutMs, rejectPendingCommand]);
190
204
  const startPendingCommandTimeouts = useCallback(() => {
191
- for (const [requestId, pendingCommand] of pendingCommandsRef.current) {
205
+ for (const [requestId, pendingCommand] of getProviderPendingCommandEntries(pendingCommandsRef.current)) {
192
206
  startPendingCommandTimeout(requestId, pendingCommand);
193
207
  }
194
208
  }, [startPendingCommandTimeout]);
195
209
  const pausePendingCommandTimeouts = useCallback(() => {
196
- for (const pendingCommand of pendingCommandsRef.current.values()) {
210
+ for (const pendingCommand of getProviderPendingCommands(pendingCommandsRef.current)) {
197
211
  if (!pendingCommand.timeout) {
198
212
  continue;
199
213
  }
@@ -234,7 +248,7 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
234
248
  };
235
249
  const flowCommandSnapshot = snapshotProviderFlowCommand(flowHandleCacheRef.current, validatedMessage.envelope.command);
236
250
  const promise = new Promise((resolve, reject) => {
237
- pendingCommandsRef.current.set(requestId, {
251
+ const pendingCommand = {
238
252
  flowCommandSequence: flowCommandSnapshot.flowCommandSequence,
239
253
  flowHandleCacheEpoch: flowCommandSnapshot.flowHandleCacheEpoch,
240
254
  hostEpoch: null,
@@ -244,17 +258,15 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
244
258
  resolve,
245
259
  result,
246
260
  timeout: null,
247
- });
261
+ };
262
+ registerProviderPendingCommand(pendingCommandsRef.current, requestId, pendingCommand);
248
263
  if (lastReadyRef.current !== null) {
249
- startPendingCommandTimeout(requestId, pendingCommandsRef.current.get(requestId));
264
+ startPendingCommandTimeout(requestId, pendingCommand);
250
265
  }
251
266
  });
252
267
  setNativeMessages((messages) => enqueueProviderNativeMessage(messages, {
253
268
  isStartup,
254
- isStartupMessage: (entry) => {
255
- var _a;
256
- return ((_a = pendingCommandsRef.current.get(entry.envelope.requestId)) === null || _a === void 0 ? void 0 : _a.isStartup) === true;
257
- },
269
+ isStartupMessage: (entry) => isProviderPendingMessageStartup(pendingCommandsRef.current, entry.envelope.requestId),
258
270
  message: validatedMessage,
259
271
  prioritizeStartup,
260
272
  }));
@@ -263,13 +275,10 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
263
275
  const reissueDeliveredPendingActionCommands = useCallback(() => {
264
276
  var _a;
265
277
  const replacements = new Map();
266
- for (const [requestId, pendingCommand] of [
267
- ...pendingCommandsRef.current.entries(),
268
- ]) {
278
+ for (const [requestId, pendingCommand] of getProviderPendingCommandEntries(pendingCommandsRef.current)) {
269
279
  if (pendingCommand.isStartup || pendingCommand.hostEpoch === null) {
270
280
  continue;
271
281
  }
272
- pendingCommandsRef.current.delete(requestId);
273
282
  if (pendingCommand.timeout) {
274
283
  clearTimeout(pendingCommand.timeout);
275
284
  pendingCommand.timeout = null;
@@ -281,16 +290,20 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
281
290
  id: createTransportMessageId(),
282
291
  envelope: nextEnvelope,
283
292
  });
284
- const previousMessageId = pendingCommand.messageId;
285
- pendingCommand.flowHandleCacheEpoch = flowHandleCacheRef.current.epoch;
286
- pendingCommand.hostEpoch = null;
287
- pendingCommand.messageId = nextMessage.id;
288
- pendingCommand.result = {
289
- envelope: nextMessage.envelope,
290
- message: nextMessage,
291
- };
292
- pendingCommandsRef.current.set(nextEnvelope.requestId, pendingCommand);
293
- replacements.set(previousMessageId, nextMessage);
293
+ const reissuedCommand = reissueProviderPendingCommand(pendingCommandsRef.current, {
294
+ flowHandleCacheEpoch: flowHandleCacheRef.current.epoch,
295
+ nextMessageId: nextMessage.id,
296
+ nextRequestId: nextEnvelope.requestId,
297
+ nextResult: {
298
+ envelope: nextMessage.envelope,
299
+ message: nextMessage,
300
+ },
301
+ requestId,
302
+ });
303
+ if (!reissuedCommand) {
304
+ continue;
305
+ }
306
+ replacements.set(reissuedCommand.previousMessageId, nextMessage);
294
307
  }
295
308
  if (replacements.size === 0) {
296
309
  return false;
@@ -299,22 +312,17 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
299
312
  return true;
300
313
  }, []);
301
314
  const cancelPendingStartupCommands = useCallback(() => {
302
- const canceledMessageIds = new Set();
303
- for (const [requestId, pendingCommand] of pendingCommandsRef.current) {
304
- if (!pendingCommand.isStartup) {
305
- continue;
306
- }
307
- pendingCommandsRef.current.delete(requestId);
308
- canceledMessageIds.add(pendingCommand.messageId);
315
+ const canceled = cancelProviderStartupCommands(pendingCommandsRef.current);
316
+ for (const pendingCommand of canceled.commands) {
309
317
  if (pendingCommand.timeout) {
310
318
  clearTimeout(pendingCommand.timeout);
311
319
  }
312
320
  pendingCommand.reject(new Error(STARTUP_COMMAND_REPLACED_ERROR_MESSAGE));
313
321
  }
314
- if (canceledMessageIds.size === 0) {
322
+ if (canceled.messageIds.size === 0) {
315
323
  return;
316
324
  }
317
- setNativeMessages((messages) => removeProviderNativeMessageIds(messages, canceledMessageIds));
325
+ setNativeMessages((messages) => removeProviderNativeMessageIds(messages, canceled.messageIds));
318
326
  }, []);
319
327
  const enqueueStartupCommands = useCallback(({ force = false } = {}) => {
320
328
  if (!autoInit) {
@@ -353,9 +361,7 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
353
361
  if (alreadySeen) {
354
362
  return false;
355
363
  }
356
- const hasDeliveredPendingCommands = [
357
- ...pendingCommandsRef.current.values(),
358
- ].some((pendingCommand) => pendingCommand.hostEpoch !== null);
364
+ const hasDeliveredPendingCommands = hasDeliveredProviderPendingCommands(pendingCommandsRef.current);
359
365
  let didReissueDeliveredCommands = false;
360
366
  if (previous !== null || hasDeliveredPendingCommands) {
361
367
  hostEpochRef.current += 1;
@@ -364,16 +370,14 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
364
370
  didReissueDeliveredCommands = reissueDeliveredPendingActionCommands();
365
371
  }
366
372
  lastReadyRef.current = Object.assign(Object.assign({}, event), { hostEpoch: hostEpochRef.current });
367
- const nextVisibility = updateProviderOverlayReadySequence({
368
- ready: {
369
- hostSourceVersion: hostSourceVersionRef.current,
370
- readySequence: event.readySequence,
371
- sourceKey: event.sourceKey,
372
- },
373
- visibility: providerOverlayStateRef.current.visibility,
373
+ setWidgetHostVisibility((visibility) => {
374
+ if (!visibility.isVisible ||
375
+ visibility.sourceKey !== event.sourceKey ||
376
+ visibility.hostSourceVersion !== hostSourceVersionRef.current) {
377
+ return visibility;
378
+ }
379
+ return Object.assign(Object.assign({}, visibility), { readySequence: event.readySequence });
374
380
  });
375
- providerOverlayStateRef.current = Object.assign(Object.assign({}, providerOverlayStateRef.current), { visibility: nextVisibility });
376
- setWidgetHostVisibility(nextVisibility);
377
381
  enqueueStartupCommands({ force: previous !== null });
378
382
  startPendingCommandTimeouts();
379
383
  return didReissueDeliveredCommands;
@@ -399,8 +403,7 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
399
403
  reissueDeliveredPendingActionCommands();
400
404
  }
401
405
  lastReadyRef.current = null;
402
- providerOverlayStateRef.current = createDefaultProviderOverlayState();
403
- setWidgetHostVisibility(providerOverlayStateRef.current.visibility);
406
+ setWidgetHostVisibility(HIDDEN_PROVIDER_WIDGET_HOST_VISIBILITY);
404
407
  pausePendingCommandTimeouts();
405
408
  }, [
406
409
  handleWidgetReady,
@@ -412,14 +415,10 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
412
415
  }, []);
413
416
  const handleFlowHandleInvalidated = useCallback((flowHandleId) => {
414
417
  deleteCachedFlowHandleId(flowHandleId);
415
- const nextState = applyProviderOverlayHandleInvalidated({
416
- flowHandleId,
417
- state: providerOverlayStateRef.current,
418
- });
419
- providerOverlayStateRef.current = nextState;
420
- setWidgetHostVisibility(nextState.visibility);
418
+ setWidgetHostVisibility((visibility) => visibility.flowHandleId === flowHandleId
419
+ ? Object.assign(Object.assign({}, visibility), { flowHandleId: undefined }) : visibility);
421
420
  }, [deleteCachedFlowHandleId]);
422
- const handleFlowStateChanged = useCallback((detail, options) => {
421
+ const handleInstanceFlowStateChanged = useCallback((detail) => {
423
422
  if (detail.instanceId !== instanceId) {
424
423
  return;
425
424
  }
@@ -427,44 +426,37 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
427
426
  if (!readyState) {
428
427
  return;
429
428
  }
430
- const nextState = applyProviderOverlayFlowState({
431
- detail,
432
- origin: options.origin,
433
- ready: {
434
- hostSourceVersion: hostSourceVersionRef.current,
435
- readySequence: readyState.readySequence,
436
- sourceKey: readyState.sourceKey,
437
- },
438
- state: providerOverlayStateRef.current,
429
+ const isVisible = detail.isOpen || detail.isLoading;
430
+ setWidgetHostVisibility({
431
+ flowHandleId: detail.flowHandleId,
432
+ hostSourceVersion: hostSourceVersionRef.current,
433
+ isVisible,
434
+ readySequence: readyState.readySequence,
435
+ sourceKey: readyState.sourceKey,
439
436
  });
440
- providerOverlayStateRef.current = nextState;
441
- setWidgetHostVisibility(nextState.visibility);
442
437
  }, [instanceId]);
443
438
  const handleWebMessage = useCallback((message) => {
444
439
  var _a;
445
440
  if (message.kind === "hostEvent" &&
446
- message.event.name === "instance:handle:invalidated") {
441
+ message.event.name === "instance:handle:invalidated" &&
442
+ message.event.detail.handleKind === "flow") {
447
443
  handleFlowHandleInvalidated(message.event.detail.handleId);
448
444
  }
449
- if (message.kind === "hostEvent" &&
450
- message.event.name === "instance:flow:state-changed") {
451
- handleFlowStateChanged(message.event.detail, { origin: "handle" });
452
- }
453
445
  if (message.kind === "hostEvent" &&
454
446
  message.event.name === "instance:flow:state-changed:instance") {
455
- handleFlowStateChanged(message.event.detail, { origin: "aggregate" });
447
+ handleInstanceFlowStateChanged(message.event.detail);
456
448
  }
457
449
  if (message.kind === "hostEvent" &&
458
450
  message.event.name === "instance:command:settled") {
459
451
  const requestId = message.event.detail.requestId;
460
- const pendingCommand = pendingCommandsRef.current.get(requestId);
452
+ const pendingCommand = getProviderPendingCommand(pendingCommandsRef.current, requestId);
461
453
  if (pendingCommand) {
462
454
  if (pendingCommand.hostEpoch !== null &&
463
455
  pendingCommand.hostEpoch !== ((_a = lastReadyRef.current) === null || _a === void 0 ? void 0 : _a.hostEpoch)) {
464
456
  onWebMessage === null || onWebMessage === void 0 ? void 0 : onWebMessage(message);
465
457
  return;
466
458
  }
467
- pendingCommandsRef.current.delete(requestId);
459
+ takeProviderPendingCommand(pendingCommandsRef.current, requestId);
468
460
  setNativeMessages((messages) => removeProviderNativeMessageId(messages, pendingCommand.messageId));
469
461
  if (pendingCommand.timeout) {
470
462
  clearTimeout(pendingCommand.timeout);
@@ -509,26 +501,25 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
509
501
  onWebMessage === null || onWebMessage === void 0 ? void 0 : onWebMessage(message);
510
502
  }, [
511
503
  enqueueTransportCommand,
512
- handleFlowStateChanged,
504
+ handleInstanceFlowStateChanged,
513
505
  handleFlowHandleInvalidated,
514
506
  onWebMessage,
515
507
  ]);
516
508
  useEffect(() => () => {
517
- for (const pendingCommand of pendingCommandsRef.current.values()) {
509
+ for (const pendingCommand of getProviderPendingCommands(pendingCommandsRef.current)) {
518
510
  if (pendingCommand.timeout) {
519
511
  clearTimeout(pendingCommand.timeout);
520
512
  }
521
513
  pendingCommand.reject(new Error(PROVIDER_UNMOUNTED_ERROR_MESSAGE));
522
514
  }
523
- pendingCommandsRef.current.clear();
515
+ clearProviderPendingCommands(pendingCommandsRef.current);
524
516
  }, []);
525
517
  useEffect(() => {
526
518
  if (visibilitySourceKeyRef.current === hostSourceKey) {
527
519
  return;
528
520
  }
529
521
  visibilitySourceKeyRef.current = hostSourceKey;
530
- providerOverlayStateRef.current = createDefaultProviderOverlayState();
531
- setWidgetHostVisibility(providerOverlayStateRef.current.visibility);
522
+ setWidgetHostVisibility(HIDDEN_PROVIDER_WIDGET_HOST_VISIBILITY);
532
523
  }, [hostSourceKey]);
533
524
  const contextValue = useMemo(() => ({
534
525
  instanceId,
@@ -615,11 +606,20 @@ export function GetUserFeedbackProvider({ autoInit = true, children, commandTime
615
606
  webViewComponent,
616
607
  webViewProps: Object.assign(Object.assign({}, webViewProps), { pointerEvents: isWidgetHostVisible ? "auto" : "none", style: composedWebViewStyle }),
617
608
  });
609
+ const handleWidgetHostClose = useCallback(() => {
610
+ enqueueCommand(Object.assign({ kind: "close" }, (widgetHostVisibility.flowHandleId === undefined
611
+ ? {}
612
+ : { flowHandleId: widgetHostVisibility.flowHandleId }))).catch(reportCommandError);
613
+ }, [enqueueCommand, reportCommandError, widgetHostVisibility.flowHandleId]);
618
614
  return createElement(GetUserFeedbackNativeContext.Provider, {
619
615
  value: contextValue,
620
616
  }, children, createElement(ProviderWidgetHostOverlay, {
621
617
  isVisible: isWidgetHostVisible,
618
+ isCloseVisible: true,
619
+ nativePressable: NativePressable,
620
+ nativeText: NativeText,
622
621
  nativeView: NativeView,
622
+ onRequestClose: handleWidgetHostClose,
623
623
  sheetHostViewport,
624
624
  }, widgetHost));
625
625
  }
@@ -0,0 +1,37 @@
1
+ export type ProviderPendingCommand<Result> = {
2
+ flowCommandSequence: number | null;
3
+ flowHandleCacheEpoch: number;
4
+ hostEpoch: number | null;
5
+ isStartup: boolean;
6
+ messageId: string;
7
+ reject: (error: Error) => void;
8
+ resolve: (result: Result) => void;
9
+ result: Result;
10
+ timeout: ReturnType<typeof setTimeout> | null;
11
+ };
12
+ export type ProviderPendingCommandRegistry<Result> = Map<string, ProviderPendingCommand<Result>>;
13
+ export type ProviderCanceledStartupCommands<Result> = {
14
+ commands: ProviderPendingCommand<Result>[];
15
+ messageIds: Set<string>;
16
+ };
17
+ export type ProviderReissuedPendingCommand<Result> = {
18
+ command: ProviderPendingCommand<Result>;
19
+ previousMessageId: string;
20
+ };
21
+ export declare function createProviderPendingCommandRegistry<Result>(): ProviderPendingCommandRegistry<Result>;
22
+ export declare function registerProviderPendingCommand<Result>(registry: ProviderPendingCommandRegistry<Result>, requestId: string, command: ProviderPendingCommand<Result>): void;
23
+ export declare function getProviderPendingCommand<Result>(registry: ProviderPendingCommandRegistry<Result>, requestId: string): ProviderPendingCommand<Result> | undefined;
24
+ export declare function takeProviderPendingCommand<Result>(registry: ProviderPendingCommandRegistry<Result>, requestId: string): ProviderPendingCommand<Result> | undefined;
25
+ export declare function hasDeliveredProviderPendingCommands<Result>(registry: ProviderPendingCommandRegistry<Result>): boolean;
26
+ export declare function isProviderPendingMessageStartup<Result>(registry: ProviderPendingCommandRegistry<Result>, requestId: string): boolean;
27
+ export declare function getProviderPendingCommandEntries<Result>(registry: ProviderPendingCommandRegistry<Result>): Array<[string, ProviderPendingCommand<Result>]>;
28
+ export declare function getProviderPendingCommands<Result>(registry: ProviderPendingCommandRegistry<Result>): ProviderPendingCommand<Result>[];
29
+ export declare function cancelProviderStartupCommands<Result>(registry: ProviderPendingCommandRegistry<Result>): ProviderCanceledStartupCommands<Result>;
30
+ export declare function reissueProviderPendingCommand<Result>(registry: ProviderPendingCommandRegistry<Result>, { flowHandleCacheEpoch, nextMessageId, nextRequestId, nextResult, requestId, }: {
31
+ flowHandleCacheEpoch: number;
32
+ nextMessageId: string;
33
+ nextRequestId: string;
34
+ nextResult: Result;
35
+ requestId: string;
36
+ }): ProviderReissuedPendingCommand<Result> | null;
37
+ export declare function clearProviderPendingCommands<Result>(registry: ProviderPendingCommandRegistry<Result>): void;
@@ -0,0 +1,59 @@
1
+ export function createProviderPendingCommandRegistry() {
2
+ return new Map();
3
+ }
4
+ export function registerProviderPendingCommand(registry, requestId, command) {
5
+ registry.set(requestId, command);
6
+ }
7
+ export function getProviderPendingCommand(registry, requestId) {
8
+ return registry.get(requestId);
9
+ }
10
+ export function takeProviderPendingCommand(registry, requestId) {
11
+ const command = registry.get(requestId);
12
+ if (command) {
13
+ registry.delete(requestId);
14
+ }
15
+ return command;
16
+ }
17
+ export function hasDeliveredProviderPendingCommands(registry) {
18
+ return [...registry.values()].some((command) => command.hostEpoch !== null);
19
+ }
20
+ export function isProviderPendingMessageStartup(registry, requestId) {
21
+ var _a;
22
+ return ((_a = registry.get(requestId)) === null || _a === void 0 ? void 0 : _a.isStartup) === true;
23
+ }
24
+ export function getProviderPendingCommandEntries(registry) {
25
+ return [...registry.entries()];
26
+ }
27
+ export function getProviderPendingCommands(registry) {
28
+ return [...registry.values()];
29
+ }
30
+ export function cancelProviderStartupCommands(registry) {
31
+ const commands = [];
32
+ const messageIds = new Set();
33
+ for (const [requestId, command] of registry) {
34
+ if (!command.isStartup) {
35
+ continue;
36
+ }
37
+ registry.delete(requestId);
38
+ commands.push(command);
39
+ messageIds.add(command.messageId);
40
+ }
41
+ return { commands, messageIds };
42
+ }
43
+ export function reissueProviderPendingCommand(registry, { flowHandleCacheEpoch, nextMessageId, nextRequestId, nextResult, requestId, }) {
44
+ const command = registry.get(requestId);
45
+ if (!command || command.isStartup || command.hostEpoch === null) {
46
+ return null;
47
+ }
48
+ registry.delete(requestId);
49
+ const previousMessageId = command.messageId;
50
+ command.flowHandleCacheEpoch = flowHandleCacheEpoch;
51
+ command.hostEpoch = null;
52
+ command.messageId = nextMessageId;
53
+ command.result = nextResult;
54
+ registry.set(nextRequestId, command);
55
+ return { command, previousMessageId };
56
+ }
57
+ export function clearProviderPendingCommands(registry) {
58
+ registry.clear();
59
+ }
@@ -1,15 +1,21 @@
1
1
  import { type ComponentType, type ReactElement, type ReactNode } from "react";
2
2
  import type { WidgetHostProps } from "./widget-host.js";
3
3
  type NativeViewComponent = ComponentType<Record<string, unknown>>;
4
+ type NativePressableComponent = ComponentType<Record<string, unknown>>;
5
+ type NativeTextComponent = ComponentType<Record<string, unknown>>;
4
6
  export declare function toProviderWidgetSheetHostViewport(viewport: WidgetHostProps["hostViewport"]): WidgetHostProps["hostViewport"];
5
7
  export declare function toProviderWidgetHostStyle({ isVisible, webViewStyle, }: {
6
8
  isVisible: boolean;
7
9
  webViewStyle: unknown;
8
10
  }): unknown;
9
- export declare function ProviderWidgetHostOverlay({ children, isVisible, nativeView, sheetHostViewport, }: {
11
+ export declare function ProviderWidgetHostOverlay({ children, isVisible, isCloseVisible, nativeView, nativePressable, nativeText, onRequestClose, sheetHostViewport, }: {
10
12
  children?: ReactNode;
13
+ isCloseVisible: boolean;
11
14
  isVisible: boolean;
15
+ nativePressable: NativePressableComponent;
16
+ nativeText: NativeTextComponent;
12
17
  nativeView: NativeViewComponent;
18
+ onRequestClose: () => void;
13
19
  sheetHostViewport: WidgetHostProps["hostViewport"];
14
20
  }): ReactElement;
15
21
  export {};
@@ -38,6 +38,23 @@ const WIDGET_SHEET_STYLE = {
38
38
  shadowRadius: 18,
39
39
  elevation: 18,
40
40
  };
41
+ const WIDGET_SHEET_CLOSE_STYLE = {
42
+ alignItems: "center",
43
+ backgroundColor: "rgba(15, 23, 42, 0.08)",
44
+ borderRadius: 18,
45
+ height: 36,
46
+ justifyContent: "center",
47
+ position: "absolute",
48
+ right: 12,
49
+ top: 12,
50
+ width: 72,
51
+ zIndex: 1,
52
+ };
53
+ const WIDGET_SHEET_CLOSE_TEXT_STYLE = {
54
+ color: "#0f172a",
55
+ fontSize: 14,
56
+ fontWeight: "600",
57
+ };
41
58
  const VISIBLE_WIDGET_HOST_STYLE = {
42
59
  flex: 1,
43
60
  };
@@ -115,7 +132,7 @@ export function toProviderWidgetHostStyle({ isVisible, webViewStyle, }) {
115
132
  ? [visiblePresentationStyle, VISIBLE_WIDGET_HOST_STYLE]
116
133
  : VISIBLE_WIDGET_HOST_STYLE;
117
134
  }
118
- export function ProviderWidgetHostOverlay({ children, isVisible, nativeView, sheetHostViewport, }) {
135
+ export function ProviderWidgetHostOverlay({ children, isVisible, isCloseVisible, nativeView, nativePressable, nativeText, onRequestClose, sheetHostViewport, }) {
119
136
  const widgetSheetStyle = sheetHostViewport
120
137
  ? Object.assign(Object.assign({}, WIDGET_SHEET_STYLE), { height: sheetHostViewport.height }) : Object.assign(Object.assign({}, WIDGET_SHEET_STYLE), { height: "86%" });
121
138
  const pointerEvents = isVisible ? "auto" : "none";
@@ -131,5 +148,15 @@ export function ProviderWidgetHostOverlay({ children, isVisible, nativeView, she
131
148
  pointerEvents,
132
149
  style: isVisible ? widgetSheetStyle : HIDDEN_WIDGET_HOST_STYLE,
133
150
  testID: "gx-widget-host-sheet",
134
- }, children));
151
+ }, children, isVisible && isCloseVisible
152
+ ? createElement(nativePressable, {
153
+ accessibilityLabel: "Close widget",
154
+ accessibilityRole: "button",
155
+ onPress: onRequestClose,
156
+ style: WIDGET_SHEET_CLOSE_STYLE,
157
+ testID: "gx-widget-host-close",
158
+ }, createElement(nativeText, {
159
+ style: WIDGET_SHEET_CLOSE_TEXT_STYLE,
160
+ }, "Close"))
161
+ : null));
135
162
  }
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.7";
6
+ export const REACT_NATIVE_SDK_VERSION = reactNativeSdkVersion.length > 0 ? reactNativeSdkVersion : "1.3.9";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getuserfeedback/react-native",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
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.3.0",
48
- "@getuserfeedback/sdk": "^0.7.1",
47
+ "@getuserfeedback/protocol": "^1.3.1",
48
+ "@getuserfeedback/sdk": "^0.7.2",
49
49
  "robot3": "^1.2.0"
50
50
  },
51
51
  "peerDependencies": {
@@ -1,43 +0,0 @@
1
- export type ProviderOverlayVisibilityOrigin = "aggregate" | "handle";
2
- export type ProviderOverlayVisibility = {
3
- flowHandleId: string | null;
4
- hostSourceVersion: number | null;
5
- isVisible: boolean;
6
- origin: ProviderOverlayVisibilityOrigin | null;
7
- readySequence: number | null;
8
- sourceKey: string | null;
9
- };
10
- export type ProviderOverlayReadyIdentity = {
11
- hostSourceVersion: number;
12
- readySequence: number;
13
- sourceKey: string;
14
- };
15
- export type ProviderOverlayFlowStateDetail = {
16
- flowHandleId?: string;
17
- isLoading: boolean;
18
- isOpen: boolean;
19
- };
20
- export type ProviderOverlayState = {
21
- visibility: ProviderOverlayVisibility;
22
- visibleFlowHandleIds: Set<string>;
23
- };
24
- export type ProviderOverlayStateInput = {
25
- visibility: ProviderOverlayVisibility;
26
- visibleFlowHandleIds: ReadonlySet<string>;
27
- };
28
- export declare const DEFAULT_PROVIDER_OVERLAY_VISIBILITY: ProviderOverlayVisibility;
29
- export declare const createDefaultProviderOverlayState: () => ProviderOverlayState;
30
- export declare function updateProviderOverlayReadySequence({ ready, visibility, }: {
31
- ready: ProviderOverlayReadyIdentity;
32
- visibility: ProviderOverlayVisibility;
33
- }): ProviderOverlayVisibility;
34
- export declare function applyProviderOverlayFlowState({ detail, origin, ready, state, }: {
35
- detail: ProviderOverlayFlowStateDetail;
36
- origin: ProviderOverlayVisibilityOrigin;
37
- ready: ProviderOverlayReadyIdentity;
38
- state: ProviderOverlayStateInput;
39
- }): ProviderOverlayState;
40
- export declare function applyProviderOverlayHandleInvalidated({ flowHandleId, state, }: {
41
- flowHandleId: string;
42
- state: ProviderOverlayStateInput;
43
- }): ProviderOverlayState;
@@ -1,134 +0,0 @@
1
- export const DEFAULT_PROVIDER_OVERLAY_VISIBILITY = {
2
- flowHandleId: null,
3
- hostSourceVersion: null,
4
- isVisible: false,
5
- origin: null,
6
- readySequence: null,
7
- sourceKey: null,
8
- };
9
- export const createDefaultProviderOverlayState = () => ({
10
- visibility: DEFAULT_PROVIDER_OVERLAY_VISIBILITY,
11
- visibleFlowHandleIds: new Set(),
12
- });
13
- const toVisibleProviderOverlayVisibility = ({ flowHandleId, origin, ready, }) => ({
14
- flowHandleId,
15
- hostSourceVersion: ready.hostSourceVersion,
16
- isVisible: true,
17
- origin,
18
- readySequence: ready.readySequence,
19
- sourceKey: ready.sourceKey,
20
- });
21
- const toHiddenProviderOverlayVisibility = (visibility) => ({
22
- flowHandleId: null,
23
- hostSourceVersion: visibility.hostSourceVersion,
24
- isVisible: false,
25
- origin: null,
26
- readySequence: visibility.readySequence,
27
- sourceKey: visibility.sourceKey,
28
- });
29
- const isVisibilityForReadyIdentity = (visibility, ready) => visibility.hostSourceVersion === ready.hostSourceVersion &&
30
- visibility.readySequence === ready.readySequence &&
31
- visibility.sourceKey === ready.sourceKey;
32
- export function updateProviderOverlayReadySequence({ ready, visibility, }) {
33
- if (!visibility.isVisible ||
34
- visibility.sourceKey !== ready.sourceKey ||
35
- visibility.hostSourceVersion !== ready.hostSourceVersion) {
36
- return visibility;
37
- }
38
- return Object.assign(Object.assign({}, visibility), { readySequence: ready.readySequence });
39
- }
40
- export function applyProviderOverlayFlowState({ detail, origin, ready, state, }) {
41
- var _a;
42
- const isVisible = detail.isOpen || detail.isLoading;
43
- const visibleFlowHandleIds = new Set(state.visibleFlowHandleIds);
44
- if (origin === "aggregate") {
45
- if (isVisible) {
46
- visibleFlowHandleIds.clear();
47
- return {
48
- visibleFlowHandleIds,
49
- visibility: toVisibleProviderOverlayVisibility({
50
- flowHandleId: null,
51
- origin,
52
- ready,
53
- }),
54
- };
55
- }
56
- if (visibleFlowHandleIds.size > 0) {
57
- return {
58
- visibleFlowHandleIds,
59
- visibility: toVisibleProviderOverlayVisibility({
60
- flowHandleId: (_a = visibleFlowHandleIds.values().next().value) !== null && _a !== void 0 ? _a : null,
61
- origin: "handle",
62
- ready,
63
- }),
64
- };
65
- }
66
- return {
67
- visibleFlowHandleIds,
68
- visibility: {
69
- flowHandleId: null,
70
- hostSourceVersion: ready.hostSourceVersion,
71
- isVisible: false,
72
- origin,
73
- readySequence: ready.readySequence,
74
- sourceKey: ready.sourceKey,
75
- },
76
- };
77
- }
78
- if (detail.flowHandleId === undefined) {
79
- return {
80
- visibleFlowHandleIds,
81
- visibility: state.visibility,
82
- };
83
- }
84
- if (isVisible) {
85
- visibleFlowHandleIds.add(detail.flowHandleId);
86
- if (state.visibility.isVisible &&
87
- state.visibility.origin === "aggregate" &&
88
- isVisibilityForReadyIdentity(state.visibility, ready)) {
89
- return {
90
- visibleFlowHandleIds,
91
- visibility: state.visibility,
92
- };
93
- }
94
- return {
95
- visibleFlowHandleIds,
96
- visibility: toVisibleProviderOverlayVisibility({
97
- flowHandleId: detail.flowHandleId,
98
- origin,
99
- ready,
100
- }),
101
- };
102
- }
103
- visibleFlowHandleIds.delete(detail.flowHandleId);
104
- if (visibleFlowHandleIds.size > 0) {
105
- return {
106
- visibleFlowHandleIds,
107
- visibility: state.visibility,
108
- };
109
- }
110
- if (state.visibility.origin !== "handle") {
111
- return {
112
- visibleFlowHandleIds,
113
- visibility: state.visibility,
114
- };
115
- }
116
- return {
117
- visibleFlowHandleIds,
118
- visibility: toHiddenProviderOverlayVisibility(state.visibility),
119
- };
120
- }
121
- export function applyProviderOverlayHandleInvalidated({ flowHandleId, state, }) {
122
- const visibleFlowHandleIds = new Set(state.visibleFlowHandleIds);
123
- visibleFlowHandleIds.delete(flowHandleId);
124
- if (visibleFlowHandleIds.size > 0 || state.visibility.origin !== "handle") {
125
- return {
126
- visibleFlowHandleIds,
127
- visibility: state.visibility,
128
- };
129
- }
130
- return {
131
- visibleFlowHandleIds,
132
- visibility: toHiddenProviderOverlayVisibility(state.visibility),
133
- };
134
- }