@solucx/react-native-solucx-widget 0.2.5 → 2.0.7
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/README.md +526 -182
- package/lib/SoluCXWidget.d.ts +50 -7
- package/lib/SoluCXWidget.d.ts.map +1 -1
- package/lib/SoluCXWidget.js +105 -101
- package/lib/SoluCXWidget.js.map +1 -1
- package/lib/SoluCXWidgetHost.d.ts +3 -0
- package/lib/SoluCXWidgetHost.d.ts.map +1 -0
- package/lib/SoluCXWidgetHost.js +34 -0
- package/lib/SoluCXWidgetHost.js.map +1 -0
- package/lib/SoluCXWidgetView.d.ts +12 -0
- package/lib/SoluCXWidgetView.d.ts.map +1 -0
- package/lib/SoluCXWidgetView.js +61 -0
- package/lib/SoluCXWidgetView.js.map +1 -0
- package/lib/components/CloseButton.d.ts +1 -1
- package/lib/components/CloseButton.d.ts.map +1 -1
- package/lib/components/CloseButton.js +4 -1
- package/lib/components/CloseButton.js.map +1 -1
- package/lib/components/InlineWidget.d.ts.map +1 -1
- package/lib/components/InlineWidget.js +2 -7
- package/lib/components/InlineWidget.js.map +1 -1
- package/lib/components/ModalWidget.d.ts +1 -1
- package/lib/components/ModalWidget.d.ts.map +1 -1
- package/lib/components/ModalWidget.js +3 -16
- package/lib/components/ModalWidget.js.map +1 -1
- package/lib/components/OverlayWidget.d.ts.map +1 -1
- package/lib/components/OverlayWidget.js +5 -15
- package/lib/components/OverlayWidget.js.map +1 -1
- package/lib/components/index.d.ts +5 -0
- package/lib/components/index.d.ts.map +1 -0
- package/lib/components/index.js +12 -0
- package/lib/components/index.js.map +1 -0
- package/lib/constants/Constants.d.ts +11 -0
- package/lib/constants/Constants.d.ts.map +1 -1
- package/lib/constants/Constants.js +16 -1
- package/lib/constants/Constants.js.map +1 -1
- package/lib/{interfaces → domain}/WidgetCallbacks.d.ts +2 -2
- package/lib/domain/WidgetCallbacks.d.ts.map +1 -0
- package/lib/domain/WidgetCallbacks.js.map +1 -0
- package/{src/interfaces/WidgetData.ts → lib/domain/WidgetData.d.ts} +5 -2
- package/lib/domain/WidgetData.d.ts.map +1 -0
- package/lib/{interfaces → domain}/WidgetData.js.map +1 -1
- package/lib/domain/WidgetDisplayResult.d.ts +6 -0
- package/lib/domain/WidgetDisplayResult.d.ts.map +1 -0
- package/lib/domain/WidgetDisplayResult.js +3 -0
- package/lib/domain/WidgetDisplayResult.js.map +1 -0
- package/lib/domain/WidgetOptions.d.ts +27 -0
- package/lib/domain/WidgetOptions.d.ts.map +1 -0
- package/lib/domain/WidgetOptions.js +30 -0
- package/lib/domain/WidgetOptions.js.map +1 -0
- package/lib/domain/WidgetResponse.d.ts +5 -0
- package/lib/domain/WidgetResponse.d.ts.map +1 -0
- package/lib/{interfaces/WidgetOptions.js → domain/WidgetResponse.js} +1 -1
- package/lib/domain/WidgetResponse.js.map +1 -0
- package/lib/domain/WidgetSamplerLog.d.ts +12 -0
- package/lib/domain/WidgetSamplerLog.d.ts.map +1 -0
- package/lib/domain/WidgetSamplerLog.js.map +1 -0
- package/lib/{interfaces → domain}/index.d.ts +1 -2
- package/lib/domain/index.d.ts.map +1 -0
- package/lib/{interfaces → domain}/index.js.map +1 -1
- package/lib/hooks/index.d.ts +2 -2
- package/lib/hooks/index.d.ts.map +1 -1
- package/lib/hooks/index.js +5 -5
- package/lib/hooks/index.js.map +1 -1
- package/lib/hooks/useClientVersionCollector.d.ts +3 -0
- package/lib/hooks/useClientVersionCollector.d.ts.map +1 -0
- package/lib/{services/ClientVersionCollector.js → hooks/useClientVersionCollector.js} +7 -2
- package/lib/hooks/useClientVersionCollector.js.map +1 -0
- package/lib/hooks/useHeightAnimation.d.ts +0 -1
- package/lib/hooks/useHeightAnimation.d.ts.map +1 -1
- package/lib/hooks/useHeightAnimation.js +4 -2
- package/lib/hooks/useHeightAnimation.js.map +1 -1
- package/lib/hooks/useWidget.d.ts +13 -0
- package/lib/hooks/useWidget.d.ts.map +1 -0
- package/lib/hooks/useWidget.js +44 -0
- package/lib/hooks/useWidget.js.map +1 -0
- package/lib/hooks/useWidgetBootstrap.d.ts +21 -0
- package/lib/hooks/useWidgetBootstrap.d.ts.map +1 -0
- package/lib/hooks/useWidgetBootstrap.js +87 -0
- package/lib/hooks/useWidgetBootstrap.js.map +1 -0
- package/lib/hooks/useWidgetServices.d.ts +19 -0
- package/lib/hooks/useWidgetServices.d.ts.map +1 -0
- package/lib/hooks/useWidgetServices.js +34 -0
- package/lib/hooks/useWidgetServices.js.map +1 -0
- package/lib/hooks/useWidgetUI.d.ts +9 -0
- package/lib/hooks/useWidgetUI.d.ts.map +1 -0
- package/lib/hooks/useWidgetUI.js +33 -0
- package/lib/hooks/useWidgetUI.js.map +1 -0
- package/lib/index.d.ts +10 -11
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +13 -38
- package/lib/index.js.map +1 -1
- package/lib/services/UserIdentificationService.d.ts +3 -0
- package/lib/services/UserIdentificationService.d.ts.map +1 -0
- package/lib/services/UserIdentificationService.js +17 -0
- package/lib/services/UserIdentificationService.js.map +1 -0
- package/lib/services/WidgetBootstrapService.d.ts +12 -0
- package/lib/services/WidgetBootstrapService.d.ts.map +1 -0
- package/lib/services/{widgetBootstrapService.js → WidgetBootstrapService.js} +36 -15
- package/lib/services/WidgetBootstrapService.js.map +1 -0
- package/lib/services/WidgetEventService.d.ts +8 -0
- package/lib/services/WidgetEventService.d.ts.map +1 -0
- package/lib/services/WidgetEventService.js +14 -0
- package/lib/services/WidgetEventService.js.map +1 -0
- package/lib/services/WidgetStateManager.d.ts +20 -0
- package/lib/services/WidgetStateManager.d.ts.map +1 -0
- package/lib/services/WidgetStateManager.js +93 -0
- package/lib/services/WidgetStateManager.js.map +1 -0
- package/lib/services/WidgetValidationService.d.ts +17 -0
- package/lib/services/WidgetValidationService.d.ts.map +1 -0
- package/lib/services/WidgetValidationService.js +132 -0
- package/lib/services/WidgetValidationService.js.map +1 -0
- package/lib/services/events/EventHandlerFactory.d.ts +18 -0
- package/lib/services/events/EventHandlerFactory.d.ts.map +1 -0
- package/lib/services/events/EventHandlerFactory.js +67 -0
- package/lib/services/events/EventHandlerFactory.js.map +1 -0
- package/lib/services/events/EventHandlers.d.ts +10 -0
- package/lib/services/events/EventHandlers.d.ts.map +1 -0
- package/lib/services/events/EventHandlers.js +72 -0
- package/lib/services/events/EventHandlers.js.map +1 -0
- package/lib/services/events/index.d.ts +3 -0
- package/lib/services/events/index.d.ts.map +1 -0
- package/lib/services/events/index.js +21 -0
- package/lib/services/events/index.js.map +1 -0
- package/lib/services/height/HeightStrategies.d.ts +3 -0
- package/lib/services/height/HeightStrategies.d.ts.map +1 -0
- package/lib/services/height/HeightStrategies.js +14 -0
- package/lib/services/height/HeightStrategies.js.map +1 -0
- package/lib/services/storage/AsyncStorageService.d.ts +13 -0
- package/lib/services/storage/AsyncStorageService.d.ts.map +1 -0
- package/lib/services/storage/AsyncStorageService.js +73 -0
- package/lib/services/storage/AsyncStorageService.js.map +1 -0
- package/lib/services/storage/IStorageService.d.ts +30 -0
- package/lib/services/storage/IStorageService.d.ts.map +1 -0
- package/lib/services/storage/IStorageService.js +3 -0
- package/lib/services/storage/IStorageService.js.map +1 -0
- package/lib/services/storage/StorageIdBuilder.d.ts +11 -0
- package/lib/services/storage/StorageIdBuilder.d.ts.map +1 -0
- package/lib/services/storage/StorageIdBuilder.js +17 -0
- package/lib/services/storage/StorageIdBuilder.js.map +1 -0
- package/lib/services/storage/index.d.ts +3 -0
- package/lib/services/storage/index.d.ts.map +1 -0
- package/lib/services/storage/index.js +6 -0
- package/lib/services/storage/index.js.map +1 -0
- package/lib/styles/widgetStyles.d.ts +1 -1
- package/lib/styles/widgetStyles.d.ts.map +1 -1
- package/package.json +8 -2
- package/src/SoluCXWidget.ts +144 -0
- package/src/SoluCXWidgetHost.tsx +44 -0
- package/src/SoluCXWidgetView.tsx +97 -0
- package/src/__tests__/ClientVersionCollector.test.ts +5 -5
- package/src/__tests__/OverlayWidget.rendering.test.tsx +12 -14
- package/src/__tests__/SoluCXWidget.rendering.test.tsx +103 -60
- package/src/__tests__/SoluCXWidget.test.ts +448 -0
- package/src/__tests__/WidgetValidationService.test.ts +408 -0
- package/src/__tests__/e2e/widget-lifecycle.test.tsx +14 -23
- package/src/__tests__/index.test.tsx +39 -0
- package/src/__tests__/integration/webview-communication-simple.test.tsx +8 -6
- package/src/__tests__/integration/webview-communication.test.tsx +127 -130
- package/src/__tests__/normalizeWidgetOptions.test.ts +80 -0
- package/src/__tests__/useWidgetBootstrap.test.ts +634 -0
- package/src/__tests__/useWidgetState.test.ts +56 -13
- package/src/__tests__/widgetBootstrapService.test.ts +15 -17
- package/src/components/CloseButton.tsx +6 -2
- package/src/components/InlineWidget.tsx +4 -9
- package/src/components/ModalWidget.tsx +15 -45
- package/src/components/OverlayWidget.tsx +5 -15
- package/src/components/index.ts +4 -0
- package/src/constants/Constants.ts +15 -0
- package/src/{interfaces → domain}/WidgetCallbacks.ts +2 -2
- package/{lib/interfaces/WidgetData.d.ts → src/domain/WidgetData.ts} +3 -2
- package/src/domain/WidgetDisplayResult.ts +16 -0
- package/src/domain/WidgetOptions.ts +53 -0
- package/src/domain/WidgetResponse.ts +5 -0
- package/src/domain/WidgetSamplerLog.ts +11 -0
- package/src/{interfaces → domain}/index.ts +1 -2
- package/src/hooks/index.ts +2 -2
- package/src/{services/ClientVersionCollector.ts → hooks/useClientVersionCollector.ts} +6 -0
- package/src/hooks/useHeightAnimation.ts +6 -3
- package/src/hooks/useWidget.ts +46 -0
- package/src/hooks/useWidgetBootstrap.ts +117 -0
- package/src/hooks/useWidgetServices.ts +44 -0
- package/src/hooks/useWidgetUI.ts +38 -0
- package/src/index.ts +16 -11
- package/src/services/UserIdentificationService.ts +14 -0
- package/src/services/{widgetBootstrapService.ts → WidgetBootstrapService.ts} +43 -19
- package/src/services/WidgetEventService.ts +15 -0
- package/src/services/WidgetStateManager.ts +115 -0
- package/src/services/WidgetValidationService.ts +149 -0
- package/src/services/events/EventHandlerFactory.ts +70 -0
- package/src/services/events/EventHandlers.ts +67 -0
- package/src/services/events/index.ts +2 -0
- package/src/services/height/HeightStrategies.ts +15 -0
- package/src/services/storage/AsyncStorageService.ts +74 -0
- package/src/services/storage/IStorageService.ts +32 -0
- package/src/services/storage/StorageIdBuilder.ts +15 -0
- package/src/services/storage/index.ts +2 -0
- package/src/styles/widgetStyles.ts +1 -1
- package/README.intern.md +0 -490
- package/lib/constants/webViewConstants.d.ts +0 -12
- package/lib/constants/webViewConstants.d.ts.map +0 -1
- package/lib/constants/webViewConstants.js +0 -19
- package/lib/constants/webViewConstants.js.map +0 -1
- package/lib/hooks/useWidgetHeight.d.ts +0 -13
- package/lib/hooks/useWidgetHeight.d.ts.map +0 -1
- package/lib/hooks/useWidgetHeight.js +0 -21
- package/lib/hooks/useWidgetHeight.js.map +0 -1
- package/lib/hooks/useWidgetState.d.ts +0 -15
- package/lib/hooks/useWidgetState.d.ts.map +0 -1
- package/lib/hooks/useWidgetState.js +0 -79
- package/lib/hooks/useWidgetState.js.map +0 -1
- package/lib/interfaces/WidgetCallbacks.d.ts.map +0 -1
- package/lib/interfaces/WidgetCallbacks.js.map +0 -1
- package/lib/interfaces/WidgetData.d.ts.map +0 -1
- package/lib/interfaces/WidgetOptions.d.ts +0 -9
- package/lib/interfaces/WidgetOptions.d.ts.map +0 -1
- package/lib/interfaces/WidgetOptions.js.map +0 -1
- package/lib/interfaces/WidgetResponse.d.ts +0 -10
- package/lib/interfaces/WidgetResponse.d.ts.map +0 -1
- package/lib/interfaces/WidgetResponse.js +0 -12
- package/lib/interfaces/WidgetResponse.js.map +0 -1
- package/lib/interfaces/WidgetSamplerLog.d.ts +0 -7
- package/lib/interfaces/WidgetSamplerLog.d.ts.map +0 -1
- package/lib/interfaces/WidgetSamplerLog.js.map +0 -1
- package/lib/interfaces/index.d.ts.map +0 -1
- package/lib/services/ClientVersionCollector.d.ts +0 -2
- package/lib/services/ClientVersionCollector.d.ts.map +0 -1
- package/lib/services/ClientVersionCollector.js.map +0 -1
- package/lib/services/storage.d.ts +0 -8
- package/lib/services/storage.d.ts.map +0 -1
- package/lib/services/storage.js +0 -23
- package/lib/services/storage.js.map +0 -1
- package/lib/services/widgetBootstrapService.d.ts +0 -6
- package/lib/services/widgetBootstrapService.d.ts.map +0 -1
- package/lib/services/widgetBootstrapService.js.map +0 -1
- package/lib/services/widgetEventService.d.ts +0 -19
- package/lib/services/widgetEventService.d.ts.map +0 -1
- package/lib/services/widgetEventService.js +0 -79
- package/lib/services/widgetEventService.js.map +0 -1
- package/lib/services/widgetValidationService.d.ts +0 -18
- package/lib/services/widgetValidationService.d.ts.map +0 -1
- package/lib/services/widgetValidationService.js +0 -71
- package/lib/services/widgetValidationService.js.map +0 -1
- package/src/SoluCXWidget.tsx +0 -179
- package/src/constants/webViewConstants.ts +0 -15
- package/src/hooks/useWidgetHeight.ts +0 -38
- package/src/hooks/useWidgetState.ts +0 -101
- package/src/interfaces/WidgetOptions.ts +0 -8
- package/src/interfaces/WidgetResponse.ts +0 -15
- package/src/interfaces/WidgetSamplerLog.ts +0 -6
- package/src/services/storage.ts +0 -21
- package/src/services/widgetEventService.ts +0 -110
- package/src/services/widgetValidationService.ts +0 -102
- /package/lib/{interfaces → domain}/WidgetCallbacks.js +0 -0
- /package/lib/{interfaces → domain}/WidgetData.js +0 -0
- /package/lib/{interfaces → domain}/WidgetSamplerLog.js +0 -0
- /package/lib/{interfaces → domain}/index.js +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useCallback, useRef } from "react";
|
|
2
|
+
import type { WidgetData, WidgetOptions } from "../domain";
|
|
3
|
+
import { AsyncStorageService } from "../services/storage/AsyncStorageService";
|
|
4
|
+
import { buildStorageId } from "../services/storage/StorageIdBuilder";
|
|
5
|
+
import { getUserId } from "../services/UserIdentificationService";
|
|
6
|
+
import { WidgetStateManager } from "../services/WidgetStateManager";
|
|
7
|
+
import { useWidgetUI } from "./useWidgetUI";
|
|
8
|
+
|
|
9
|
+
export const useWidget = (soluCXKey: string, data: WidgetData, options?: WidgetOptions) => {
|
|
10
|
+
const userId = getUserId(data);
|
|
11
|
+
const storageId = buildStorageId(soluCXKey, data);
|
|
12
|
+
|
|
13
|
+
const storageService = useRef(null as AsyncStorageService | null);
|
|
14
|
+
if (!storageService.current) {
|
|
15
|
+
storageService.current = new AsyncStorageService(storageId);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ui = useWidgetUI(options);
|
|
19
|
+
|
|
20
|
+
const _stateManager = useRef(null as WidgetStateManager | null);
|
|
21
|
+
if (!_stateManager.current) {
|
|
22
|
+
_stateManager.current = new WidgetStateManager(storageService.current, storageId);
|
|
23
|
+
}
|
|
24
|
+
const stateManager = _stateManager.current;
|
|
25
|
+
|
|
26
|
+
const open = useCallback(async () => {
|
|
27
|
+
ui.show();
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
const resize = useCallback((value: string) => {
|
|
31
|
+
if (options?.height) return;
|
|
32
|
+
const height = Number(value);
|
|
33
|
+
ui.resize(height);
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
userId,
|
|
38
|
+
widgetHeight: ui.widgetHeight,
|
|
39
|
+
isWidgetVisible: ui.isWidgetVisible,
|
|
40
|
+
open,
|
|
41
|
+
hide: ui.hide,
|
|
42
|
+
show: ui.show,
|
|
43
|
+
resize,
|
|
44
|
+
stateManager,
|
|
45
|
+
};
|
|
46
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { SoluCXKey, WidgetData, WidgetOptions, WidgetCallbacks } from "../domain";
|
|
3
|
+
import { normalizeWidgetOptions } from "../domain/WidgetOptions";
|
|
4
|
+
import { requestWidgetUrl, requestWidgetOptions } from "../services/WidgetBootstrapService";
|
|
5
|
+
import type { WidgetStateManager } from "../services/WidgetStateManager";
|
|
6
|
+
import type { WidgetValidationService } from "../services/WidgetValidationService";
|
|
7
|
+
|
|
8
|
+
interface UseWidgetBootstrapParams {
|
|
9
|
+
soluCXKey: SoluCXKey;
|
|
10
|
+
data: WidgetData;
|
|
11
|
+
userId: string;
|
|
12
|
+
options?: WidgetOptions;
|
|
13
|
+
callbacks?: WidgetCallbacks;
|
|
14
|
+
open: () => Promise<void>;
|
|
15
|
+
hide: () => void;
|
|
16
|
+
validationService: WidgetValidationService;
|
|
17
|
+
stateManager: WidgetStateManager;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const useWidgetBootstrap = ({
|
|
21
|
+
soluCXKey,
|
|
22
|
+
data,
|
|
23
|
+
userId,
|
|
24
|
+
options,
|
|
25
|
+
callbacks,
|
|
26
|
+
open,
|
|
27
|
+
hide,
|
|
28
|
+
validationService,
|
|
29
|
+
stateManager,
|
|
30
|
+
}: UseWidgetBootstrapParams): { widgetUri: string | null; widgetOptions: WidgetOptions | null; bootstrap: () => Promise<void> } => {
|
|
31
|
+
const [widgetUri, setWidgetUri] = useState<string | null>(null);
|
|
32
|
+
const [widgetOptions, setWidgetOptions] = useState<WidgetOptions | null>(null);
|
|
33
|
+
|
|
34
|
+
const bootstrap = async () => {
|
|
35
|
+
if (!data) {
|
|
36
|
+
callbacks?.onError?.("Widget data is required but was not provided");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setWidgetUri(null);
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const transactionResult = await validationService.shouldDisplayForTransactionAlreadyAnswered(data.transaction_id);
|
|
44
|
+
if (!transactionResult.canDisplay) {
|
|
45
|
+
callbacks?.onBlocked?.(transactionResult.blockReason);
|
|
46
|
+
hide();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const rawOptions = options && Object.keys(options).length ? options : await requestWidgetOptions(soluCXKey, data.journey);
|
|
51
|
+
const effectiveOptions = normalizeWidgetOptions(rawOptions);
|
|
52
|
+
setWidgetOptions(effectiveOptions);
|
|
53
|
+
|
|
54
|
+
// Validate BEFORE preflight to avoid unnecessary API calls at scale
|
|
55
|
+
const result = await validationService.shouldDisplayWidget(effectiveOptions, data.transaction_id);
|
|
56
|
+
|
|
57
|
+
if (!result.canDisplay) {
|
|
58
|
+
const blockReason = result?.blockReason;
|
|
59
|
+
callbacks?.onBlocked?.(blockReason);
|
|
60
|
+
try {
|
|
61
|
+
await stateManager.updateTimestamp("lastDisplayAttempt");
|
|
62
|
+
} catch (err) {
|
|
63
|
+
callbacks?.onError?.("Cannot save display attempt timestamp");
|
|
64
|
+
}
|
|
65
|
+
hide();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Only call preflight after validation passes
|
|
70
|
+
const widgetUrlPayload = await requestWidgetUrl(soluCXKey, data, userId);
|
|
71
|
+
|
|
72
|
+
if (!widgetUrlPayload.available) {
|
|
73
|
+
const reason = widgetUrlPayload.reason || "for the given parameters";
|
|
74
|
+
callbacks?.onBlocked?.(reason);
|
|
75
|
+
hide();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!widgetUrlPayload.url) {
|
|
80
|
+
callbacks?.onError?.("Widget URL is missing in the response");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
callbacks?.onPreOpen?.(userId);
|
|
85
|
+
await open();
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
await stateManager.incrementAttempt();
|
|
89
|
+
const logs = await stateManager.getLogs();
|
|
90
|
+
if (!logs.lastFirstAccess) {
|
|
91
|
+
await stateManager.updateTimestamp("lastFirstAccess");
|
|
92
|
+
}
|
|
93
|
+
await stateManager.updateTimestamp("lastDisplay");
|
|
94
|
+
} catch (err) {
|
|
95
|
+
callbacks?.onError?.("Error saving display timestamps");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
setWidgetUri(widgetUrlPayload.url);
|
|
99
|
+
callbacks?.onOpened?.(userId);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
let errorMessage = "Unknown error";
|
|
102
|
+
|
|
103
|
+
if (error instanceof Error) errorMessage = error.message;
|
|
104
|
+
else if (typeof error === "string") errorMessage = error;
|
|
105
|
+
else if (error !== null && typeof error === "object") errorMessage = JSON.stringify(error);
|
|
106
|
+
|
|
107
|
+
callbacks?.onError?.(errorMessage);
|
|
108
|
+
hide();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
widgetUri,
|
|
114
|
+
widgetOptions,
|
|
115
|
+
bootstrap,
|
|
116
|
+
};
|
|
117
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useCallback, useRef, useMemo } from "react";
|
|
2
|
+
import type { WidgetCallbacks, WidgetData } from "../domain";
|
|
3
|
+
import { WidgetEventService } from "../services/WidgetEventService";
|
|
4
|
+
import { WidgetValidationService } from "../services/WidgetValidationService";
|
|
5
|
+
import type { WidgetStateManager } from "../services/WidgetStateManager";
|
|
6
|
+
|
|
7
|
+
interface UseWidgetServicesParams {
|
|
8
|
+
hide: () => void;
|
|
9
|
+
resize: (height: string) => void;
|
|
10
|
+
userId: string;
|
|
11
|
+
callbacks?: WidgetCallbacks;
|
|
12
|
+
data: WidgetData;
|
|
13
|
+
stateManager: WidgetStateManager;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const useWidgetServices = ({ hide, resize, userId, callbacks, data, stateManager }: UseWidgetServicesParams) => {
|
|
17
|
+
const eventServiceRef = useRef(null as WidgetEventService | null);
|
|
18
|
+
if (!eventServiceRef.current) {
|
|
19
|
+
eventServiceRef.current = new WidgetEventService(hide, resize, userId, data?.transaction_id, callbacks, stateManager);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const validationServiceRef = useRef(null as WidgetValidationService | null);
|
|
23
|
+
if (!validationServiceRef.current) {
|
|
24
|
+
validationServiceRef.current = new WidgetValidationService(stateManager);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const isForm = useMemo(() => Boolean(data?.form_id), [data?.form_id]);
|
|
28
|
+
|
|
29
|
+
const handleWebViewMessage = useCallback(async (message: string) => {
|
|
30
|
+
if (message && message.length > 0) {
|
|
31
|
+
try {
|
|
32
|
+
await eventServiceRef.current!.handleMessage(message, isForm);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error("[useWidgetServices] Error handling widget message:", error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
eventService: eventServiceRef.current,
|
|
41
|
+
validationService: validationServiceRef.current,
|
|
42
|
+
handleWebViewMessage,
|
|
43
|
+
};
|
|
44
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useState, useCallback, useMemo } from "react";
|
|
2
|
+
import type { WidgetOptions } from "../domain";
|
|
3
|
+
import { calculateHeight } from "../services/height/HeightStrategies";
|
|
4
|
+
|
|
5
|
+
export const useWidgetUI = (options?: WidgetOptions) => {
|
|
6
|
+
const [isWidgetVisible, setIsWidgetVisible] = useState<boolean>(false);
|
|
7
|
+
const initialHeight = calculateHeight(options);
|
|
8
|
+
const [widgetHeight, setWidgetHeight] = useState<number>(initialHeight);
|
|
9
|
+
|
|
10
|
+
const resize = useCallback(
|
|
11
|
+
(receivedHeight: number) => {
|
|
12
|
+
if (receivedHeight > 0) {
|
|
13
|
+
const newHeight = calculateHeight(options, receivedHeight);
|
|
14
|
+
setWidgetHeight(newHeight);
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
[options],
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const show = useCallback(() => {
|
|
21
|
+
setIsWidgetVisible(true);
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
const hide = useCallback(() => {
|
|
25
|
+
setIsWidgetVisible(false);
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
// State
|
|
30
|
+
isWidgetVisible,
|
|
31
|
+
widgetHeight,
|
|
32
|
+
|
|
33
|
+
// Actions
|
|
34
|
+
resize,
|
|
35
|
+
show,
|
|
36
|
+
hide,
|
|
37
|
+
};
|
|
38
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export
|
|
1
|
+
import { SoluCXWidgetView } from './SoluCXWidgetView';
|
|
2
|
+
import { SoluCXWidget as SoluCXWidgetController } from './SoluCXWidget';
|
|
3
|
+
|
|
4
|
+
type SoluCXWidgetPublicApi = typeof SoluCXWidgetView & Pick<typeof SoluCXWidgetController, 'create' | 'dismiss'>;
|
|
5
|
+
|
|
6
|
+
export const SoluCXWidget = Object.assign(SoluCXWidgetView, {
|
|
7
|
+
create: SoluCXWidgetController.create,
|
|
8
|
+
dismiss: SoluCXWidgetController.dismiss,
|
|
9
|
+
}) as SoluCXWidgetPublicApi;
|
|
10
|
+
|
|
11
|
+
export { SoluCXWidgetView };
|
|
12
|
+
export { SoluCXWidgetHost } from './SoluCXWidgetHost';
|
|
13
|
+
export { SoluCXWidgetController };
|
|
14
|
+
export type { WidgetConfig } from './SoluCXWidget';
|
|
15
|
+
export { WidgetStateManager } from './services/WidgetStateManager';
|
|
16
|
+
export type { TimestampField } from './services/WidgetStateManager';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { WidgetData } from "../domain";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_USER_ID = "default_user";
|
|
4
|
+
|
|
5
|
+
export function getUserId(data: WidgetData): string {
|
|
6
|
+
if (data.user_id) return String(data.user_id);
|
|
7
|
+
if (data.email) return String(data.email);
|
|
8
|
+
if (data.cpf) return String(data.cpf);
|
|
9
|
+
|
|
10
|
+
const journey = data.journey || data.form_id;
|
|
11
|
+
if (journey) return `${DEFAULT_USER_ID}_${journey}`;
|
|
12
|
+
|
|
13
|
+
return DEFAULT_USER_ID;
|
|
14
|
+
}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import type { SoluCXKey, WidgetData } from "../
|
|
2
|
-
import {
|
|
3
|
-
import { getClientVersion } from "../
|
|
1
|
+
import type { SoluCXKey, WidgetData, WidgetOptions } from "../domain";
|
|
2
|
+
import { WIDGET_API_BASE_URL } from "../constants/Constants";
|
|
3
|
+
import { getClientVersion } from "../hooks/useClientVersionCollector";
|
|
4
4
|
import { getDeviceInfo } from "../hooks/useDeviceInfoCollector";
|
|
5
5
|
import { SDK_NAME, SDK_VERSION } from "../constants/Constants";
|
|
6
|
-
import { URLSearchParams } from
|
|
6
|
+
import { URLSearchParams } from "react-native-url-polyfill";
|
|
7
7
|
|
|
8
|
-
type
|
|
9
|
-
|
|
8
|
+
type UrlRequestPayload = {
|
|
9
|
+
available: boolean;
|
|
10
|
+
url?: string;
|
|
11
|
+
reason?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const DEFAULT_WIDGET_OPTIONS: WidgetOptions = {
|
|
15
|
+
height: 600,
|
|
10
16
|
};
|
|
11
17
|
|
|
12
18
|
const buildRequestParams = (requestParams: WidgetData): URLSearchParams => {
|
|
@@ -17,9 +23,6 @@ const buildRequestParams = (requestParams: WidgetData): URLSearchParams => {
|
|
|
17
23
|
params.append(key, typeof value === "number" ? value.toString() : String(value));
|
|
18
24
|
});
|
|
19
25
|
|
|
20
|
-
params.set("transactionId", "");
|
|
21
|
-
params.set("attemptId", "");
|
|
22
|
-
|
|
23
26
|
return params;
|
|
24
27
|
};
|
|
25
28
|
|
|
@@ -47,17 +50,13 @@ const buildRequestHeaders = (instanceKey: SoluCXKey, userId: string): Record<str
|
|
|
47
50
|
};
|
|
48
51
|
};
|
|
49
52
|
|
|
50
|
-
export async function requestWidgetUrl(
|
|
51
|
-
instanceKey: SoluCXKey,
|
|
52
|
-
requestParams: WidgetData,
|
|
53
|
-
userId: string,
|
|
54
|
-
): Promise<string> {
|
|
53
|
+
export async function requestWidgetUrl(instanceKey: SoluCXKey, requestParams: WidgetData, userId: string): Promise<UrlRequestPayload> {
|
|
55
54
|
if (typeof fetch !== "function") {
|
|
56
55
|
throw new Error("Fetch is not available");
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
const params = buildRequestParams(requestParams);
|
|
60
|
-
const url = `${
|
|
59
|
+
const url = `${WIDGET_API_BASE_URL}/widget/available?${params.toString()}`;
|
|
61
60
|
const headers = buildRequestHeaders(instanceKey, userId);
|
|
62
61
|
const response = await fetch(url, { method: "GET", headers });
|
|
63
62
|
|
|
@@ -67,12 +66,37 @@ export async function requestWidgetUrl(
|
|
|
67
66
|
throw new Error(`status=${response?.status} message=${errorPayload?.message}`);
|
|
68
67
|
}
|
|
69
68
|
|
|
70
|
-
const payload:
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
const payload: UrlRequestPayload = await response?.json();
|
|
70
|
+
|
|
71
|
+
return payload;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function requestWidgetOptions(instanceKey: SoluCXKey, journey?: string): Promise<WidgetOptions> {
|
|
75
|
+
if (typeof fetch !== "function") {
|
|
76
|
+
console.warn("[WidgetBootstrap] Fetch is not available, using default options");
|
|
77
|
+
return DEFAULT_WIDGET_OPTIONS;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const params = new URLSearchParams();
|
|
81
|
+
if (journey) {
|
|
82
|
+
params.append("journey", journey);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const url = `${WIDGET_API_BASE_URL}/widget/parameters${params.toString() ? `?${params.toString()}` : ""}`;
|
|
86
|
+
const headers = {
|
|
87
|
+
"x-solucx-api-key": instanceKey,
|
|
88
|
+
accept: "application/json",
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const response = await fetch(url, { method: "GET", headers });
|
|
92
|
+
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
console.warn(`[WidgetBootstrap] Failed to fetch widget options: ${response.status} ${response.statusText}`);
|
|
95
|
+
return DEFAULT_WIDGET_OPTIONS;
|
|
73
96
|
}
|
|
74
97
|
|
|
75
|
-
|
|
98
|
+
const options: WidgetOptions = await response.json();
|
|
99
|
+
return options || DEFAULT_WIDGET_OPTIONS;
|
|
76
100
|
}
|
|
77
101
|
|
|
78
102
|
export { buildRequestParams };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { WidgetResponse, WidgetCallbacks } from "../domain";
|
|
2
|
+
import type { WidgetStateManager } from "./WidgetStateManager";
|
|
3
|
+
import { EventHandlerFactory } from "./events/EventHandlerFactory";
|
|
4
|
+
|
|
5
|
+
export class WidgetEventService {
|
|
6
|
+
private handlerFactory: EventHandlerFactory;
|
|
7
|
+
|
|
8
|
+
constructor(hide: () => void, resize: (height: string) => void, userId: string, transactionId?: string, callbacks?: WidgetCallbacks, stateManager?: WidgetStateManager) {
|
|
9
|
+
this.handlerFactory = new EventHandlerFactory(hide, resize, userId, transactionId, callbacks, stateManager);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async handleMessage(message: string, isForm: boolean): Promise<WidgetResponse> {
|
|
13
|
+
return await this.handlerFactory.processMessage(message, isForm);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { IStorageService } from './storage/IStorageService';
|
|
2
|
+
import type { WidgetSamplerLog } from '../domain';
|
|
3
|
+
|
|
4
|
+
export type TimestampField = keyof Pick<WidgetSamplerLog, 'lastDisplayAttempt' | 'lastFirstAccess' | 'lastDisplay' | 'lastDismiss' | 'lastSubmit' | 'lastPartialSubmit'>;
|
|
5
|
+
|
|
6
|
+
export class WidgetStateManager {
|
|
7
|
+
private storageService: IStorageService<WidgetSamplerLog>;
|
|
8
|
+
private storageKey: string;
|
|
9
|
+
|
|
10
|
+
constructor(storageService: IStorageService<WidgetSamplerLog>, storageKey: string) {
|
|
11
|
+
this.storageService = storageService;
|
|
12
|
+
this.storageKey = storageKey;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async getLogs(): Promise<WidgetSamplerLog> {
|
|
16
|
+
try {
|
|
17
|
+
return await this.storageService.read(this.storageKey);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error('[WidgetStateManager] Error reading logs:', error);
|
|
20
|
+
return this.getDefaultLogs();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async incrementAttempt(): Promise<WidgetSamplerLog> {
|
|
25
|
+
const logs = await this.getLogs();
|
|
26
|
+
|
|
27
|
+
const updatedLogs: WidgetSamplerLog = {
|
|
28
|
+
...logs,
|
|
29
|
+
attempts: (logs.attempts || 0) + 1,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
await this.storageService.write(this.storageKey, updatedLogs);
|
|
33
|
+
return updatedLogs;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async saveLogs(logs: WidgetSamplerLog): Promise<void> {
|
|
37
|
+
await this.storageService.write(this.storageKey, logs);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async resetAttempts(): Promise<WidgetSamplerLog> {
|
|
41
|
+
const logs = await this.getLogs();
|
|
42
|
+
|
|
43
|
+
const updatedLogs: WidgetSamplerLog = {
|
|
44
|
+
...logs,
|
|
45
|
+
attempts: 0,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
await this.storageService.write(this.storageKey, updatedLogs);
|
|
49
|
+
return updatedLogs;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async hasAnsweredTransaction(transactionId?: string): Promise<boolean> {
|
|
53
|
+
if (!transactionId) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const logs = await this.getLogs();
|
|
58
|
+
return (logs.answeredTransactionIds || []).includes(transactionId);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async markTransactionAnswered(transactionId?: string): Promise<WidgetSamplerLog> {
|
|
62
|
+
const logs = await this.getLogs();
|
|
63
|
+
|
|
64
|
+
if (!transactionId) {
|
|
65
|
+
return logs;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const answeredTransactionIds = logs.answeredTransactionIds || [];
|
|
69
|
+
if (answeredTransactionIds.includes(transactionId)) {
|
|
70
|
+
return logs;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const updatedLogs: WidgetSamplerLog = {
|
|
74
|
+
...logs,
|
|
75
|
+
answeredTransactionIds: [...answeredTransactionIds, transactionId],
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
await this.storageService.write(this.storageKey, updatedLogs);
|
|
79
|
+
return updatedLogs;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async updateTimestamp(field: TimestampField): Promise<void> {
|
|
83
|
+
const logs = await this.getLogs();
|
|
84
|
+
const updatedLogs: WidgetSamplerLog = {
|
|
85
|
+
...logs,
|
|
86
|
+
[field]: Date.now(),
|
|
87
|
+
};
|
|
88
|
+
await this.storageService.write(this.storageKey, updatedLogs);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async overrideTimestamp(field: TimestampField, date: Date | number): Promise<void> {
|
|
92
|
+
const logs = await this.getLogs();
|
|
93
|
+
const timestamp = date instanceof Date ? date.getTime() : date;
|
|
94
|
+
const updatedLogs: WidgetSamplerLog = {
|
|
95
|
+
...logs,
|
|
96
|
+
[field]: timestamp,
|
|
97
|
+
};
|
|
98
|
+
await this.storageService.write(this.storageKey, updatedLogs);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async clearLogs(): Promise<void> {
|
|
102
|
+
await this.storageService.delete(this.storageKey);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async hasLogs(): Promise<boolean> {
|
|
106
|
+
return await this.storageService.exists(this.storageKey);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private getDefaultLogs(): WidgetSamplerLog {
|
|
110
|
+
return {
|
|
111
|
+
attempts: 0,
|
|
112
|
+
answeredTransactionIds: [],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { WidgetOptions, WidgetSamplerLog } from "../domain";
|
|
2
|
+
import type { BlockReason, WidgetDisplayResult } from "../domain/WidgetDisplayResult";
|
|
3
|
+
import type { WidgetStateManager } from "./WidgetStateManager";
|
|
4
|
+
|
|
5
|
+
export class WidgetValidationService {
|
|
6
|
+
private stateManager: WidgetStateManager;
|
|
7
|
+
|
|
8
|
+
constructor(stateManager: WidgetStateManager) {
|
|
9
|
+
this.stateManager = stateManager;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async shouldDisplayForTransactionAlreadyAnswered(transactionId?: string): Promise<WidgetDisplayResult> {
|
|
13
|
+
if (!transactionId) return { canDisplay: true };
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const alreadyAnswered = await this.stateManager.hasAnsweredTransaction(transactionId);
|
|
17
|
+
if (alreadyAnswered) {
|
|
18
|
+
return { canDisplay: false, blockReason: "BLOCKED_BY_TRANSACTION_ALREADY_ANSWERED" };
|
|
19
|
+
}
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error("Error reading answered transaction log:", error);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return { canDisplay: true };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async shouldDisplayWidget(widgetOptions: WidgetOptions, transactionId?: string): Promise<WidgetDisplayResult> {
|
|
28
|
+
if (widgetOptions.enabled === false) {
|
|
29
|
+
return { canDisplay: false, blockReason: "BLOCKED_BY_DISABLED" };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const userLog = await this.resolveUserLog(transactionId);
|
|
33
|
+
|
|
34
|
+
const maxAttemptsResult = this.checkMaxAttempts(widgetOptions, userLog);
|
|
35
|
+
if (!maxAttemptsResult.canDisplay) return maxAttemptsResult;
|
|
36
|
+
|
|
37
|
+
const intervalResult = this.checkIntervals(widgetOptions, userLog);
|
|
38
|
+
if (!intervalResult.canDisplay) return intervalResult;
|
|
39
|
+
|
|
40
|
+
const samplingResult = this.checkSampling(widgetOptions);
|
|
41
|
+
if (!samplingResult.canDisplay) return samplingResult;
|
|
42
|
+
|
|
43
|
+
return { canDisplay: true };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private async resolveUserLog(transactionId?: string): Promise<WidgetSamplerLog> {
|
|
47
|
+
let userLog = await this.getLog();
|
|
48
|
+
|
|
49
|
+
if (transactionId && userLog.lastExperienceId !== transactionId) {
|
|
50
|
+
userLog = { ...userLog, attempts: 0, lastExperienceId: transactionId };
|
|
51
|
+
await this.saveLog(userLog);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return userLog;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private checkMaxAttempts(widgetOptions: WidgetOptions, userLog: WidgetSamplerLog): WidgetDisplayResult {
|
|
58
|
+
if (widgetOptions.maxAttemptsAfterDismiss !== undefined && widgetOptions.maxAttemptsAfterDismiss > 0) {
|
|
59
|
+
if (userLog.attempts >= widgetOptions.maxAttemptsAfterDismiss) {
|
|
60
|
+
return { canDisplay: false, blockReason: "BLOCKED_BY_MAX_ATTEMPTS" };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { canDisplay: true };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private checkIntervals(widgetOptions: WidgetOptions, userLog: WidgetSamplerLog): WidgetDisplayResult {
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
const dayInMilliseconds = 86400000;
|
|
69
|
+
|
|
70
|
+
const checks: Array<{
|
|
71
|
+
timestamp: number | undefined;
|
|
72
|
+
waitDays: number | undefined;
|
|
73
|
+
blockReason: BlockReason;
|
|
74
|
+
}> = [
|
|
75
|
+
{
|
|
76
|
+
timestamp: userLog.lastDisplayAttempt,
|
|
77
|
+
waitDays: widgetOptions.waitDaysAfterWidgetDisplayAttempt,
|
|
78
|
+
blockReason: "BLOCKED_BY_WIDGET_DISPLAY_ATTEMPT_INTERVAL",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
timestamp: userLog.lastFirstAccess,
|
|
82
|
+
waitDays: widgetOptions.waitDaysAfterWidgetFirstAccess,
|
|
83
|
+
blockReason: "BLOCKED_BY_WIDGET_FIRST_ACCESS_INTERVAL",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
timestamp: userLog.lastDisplay,
|
|
87
|
+
waitDays: widgetOptions.waitDaysAfterWidgetDisplay,
|
|
88
|
+
blockReason: "BLOCKED_BY_WIDGET_DISPLAY_INTERVAL",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
timestamp: userLog.lastDismiss,
|
|
92
|
+
waitDays: widgetOptions.waitDaysAfterWidgetDismiss,
|
|
93
|
+
blockReason: "BLOCKED_BY_WIDGET_DISMISS_INTERVAL",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
timestamp: userLog.lastSubmit,
|
|
97
|
+
waitDays: widgetOptions.waitDaysAfterWidgetSubmit,
|
|
98
|
+
blockReason: "BLOCKED_BY_WIDGET_SUBMIT_INTERVAL",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
timestamp: userLog.lastPartialSubmit,
|
|
102
|
+
waitDays: widgetOptions.waitDaysAfterWidgetPartialSubmit,
|
|
103
|
+
blockReason: "BLOCKED_BY_WIDGET_PARTIAL_SUBMIT_INTERVAL",
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
for (const check of checks) {
|
|
108
|
+
if (this.isWithinInterval(check.timestamp, check.waitDays, now, dayInMilliseconds)) {
|
|
109
|
+
return { canDisplay: false, blockReason: check.blockReason };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { canDisplay: true };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private checkSampling(widgetOptions: WidgetOptions): WidgetDisplayResult {
|
|
117
|
+
if (widgetOptions.samplingPercentage !== undefined && widgetOptions.samplingPercentage >= 0 && widgetOptions.samplingPercentage < 100) {
|
|
118
|
+
const random = Math.random() * 100;
|
|
119
|
+
if (random >= widgetOptions.samplingPercentage) {
|
|
120
|
+
return { canDisplay: false, blockReason: "BLOCKED_BY_SAMPLING" };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { canDisplay: true };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private async saveLog(userLog: WidgetSamplerLog): Promise<void> {
|
|
127
|
+
try {
|
|
128
|
+
await this.stateManager.saveLogs(userLog);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error("Error writing widget log:", error);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private async getLog(): Promise<WidgetSamplerLog> {
|
|
135
|
+
try {
|
|
136
|
+
return await this.stateManager.getLogs();
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error("Error reading widget log:", error);
|
|
139
|
+
return { attempts: 0, answeredTransactionIds: [] };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private isWithinInterval(timestamp: number | undefined, waitDays: number | undefined, now: number, dayInMilliseconds: number): boolean {
|
|
144
|
+
if (!timestamp || timestamp <= 0) return false;
|
|
145
|
+
if (!waitDays || waitDays <= 0) return false;
|
|
146
|
+
const elapsed = now - timestamp;
|
|
147
|
+
return elapsed < waitDays * dayInMilliseconds;
|
|
148
|
+
}
|
|
149
|
+
}
|