@solucx/react-native-solucx-widget 0.1.15 → 0.2.0

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.
Files changed (46) hide show
  1. package/README.intern.md +513 -513
  2. package/README.md +285 -285
  3. package/package.json +50 -23
  4. package/src/SoluCXWidget.tsx +172 -119
  5. package/src/__mocks__/expo-modules-core-web.js +16 -0
  6. package/src/__mocks__/expo-modules-core.js +33 -0
  7. package/src/__tests__/ClientVersionCollector.test.ts +55 -0
  8. package/src/__tests__/CloseButton.test.tsx +47 -0
  9. package/src/__tests__/Constants.test.ts +17 -0
  10. package/src/__tests__/InlineWidget.rendering.test.tsx +81 -0
  11. package/src/__tests__/ModalWidget.rendering.test.tsx +157 -0
  12. package/src/__tests__/OverlayWidget.rendering.test.tsx +123 -0
  13. package/src/__tests__/SoluCXWidget.rendering.test.tsx +315 -0
  14. package/src/__tests__/e2e/widget-lifecycle.test.tsx +353 -0
  15. package/src/__tests__/integration/webview-communication-simple.test.tsx +147 -0
  16. package/src/__tests__/integration/webview-communication.test.tsx +417 -0
  17. package/src/__tests__/urlUtils.test.ts +56 -56
  18. package/src/__tests__/useDeviceInfoCollector.test.ts +109 -0
  19. package/src/__tests__/useWidgetState.test.ts +181 -189
  20. package/src/__tests__/widgetBootstrapService.test.ts +182 -0
  21. package/src/components/CloseButton.tsx +36 -36
  22. package/src/components/InlineWidget.tsx +36 -36
  23. package/src/components/ModalWidget.tsx +57 -59
  24. package/src/components/OverlayWidget.tsx +88 -88
  25. package/src/constants/Constants.ts +4 -0
  26. package/src/constants/webViewConstants.ts +15 -14
  27. package/src/hooks/index.ts +2 -2
  28. package/src/hooks/useDeviceInfoCollector.ts +67 -0
  29. package/src/hooks/useHeightAnimation.ts +22 -22
  30. package/src/hooks/useWidgetHeight.ts +38 -38
  31. package/src/hooks/useWidgetState.ts +101 -101
  32. package/src/index.ts +12 -8
  33. package/src/interfaces/WidgetCallbacks.ts +15 -0
  34. package/src/interfaces/WidgetData.ts +19 -19
  35. package/src/interfaces/WidgetOptions.ts +7 -7
  36. package/src/interfaces/WidgetResponse.ts +15 -15
  37. package/src/interfaces/WidgetSamplerLog.ts +5 -5
  38. package/src/interfaces/index.ts +25 -24
  39. package/src/services/ClientVersionCollector.ts +15 -0
  40. package/src/services/storage.ts +21 -21
  41. package/src/services/widgetBootstrapService.ts +67 -0
  42. package/src/services/widgetEventService.ts +110 -111
  43. package/src/services/widgetValidationService.ts +102 -86
  44. package/src/setupTests.js +43 -0
  45. package/src/styles/widgetStyles.ts +58 -58
  46. package/src/utils/urlUtils.ts +13 -13
@@ -1,101 +1,101 @@
1
- import { useState, useCallback, useMemo } from 'react';
2
- import { Dimensions } from 'react-native';
3
- import {
4
- WidgetData,
5
- WidgetOptions,
6
- WidgetType,
7
- WidgetSamplerLog
8
- } from '../interfaces';
9
- import { StorageService } from '../services/storage';
10
-
11
- function getUserId(widgetData: WidgetData): string {
12
- return (
13
- widgetData.customer_id ??
14
- widgetData.document ??
15
- widgetData.email ??
16
- 'default_user'
17
- );
18
- }
19
-
20
- export const useWidgetState = (
21
- data: WidgetData,
22
- options?: WidgetOptions,
23
- type?: WidgetType
24
- ) => {
25
- const [savedData, setSavedData] = useState<WidgetSamplerLog | null>(null);
26
- const [isWidgetVisible, setIsWidgetVisible] = useState<boolean>(false);
27
-
28
- const userId = getUserId(data);
29
-
30
- const storageService = useMemo(() => new StorageService(userId), [userId]);
31
-
32
- const screenHeight = Dimensions.get('screen').height;
33
- const height = options?.height ? Number(options.height) : undefined;
34
-
35
- const [widgetHeight, setWidgetHeight] = useState<number>(height ?? 300);
36
-
37
- const loadSavedData = useCallback(async () => {
38
- try {
39
- const jsonValue = await storageService.read();
40
- setSavedData(jsonValue);
41
- } catch (error) {
42
- console.error('Error loading storage data:', error);
43
- }
44
- }, [storageService]);
45
-
46
- const saveData = useCallback(
47
- async (data: WidgetSamplerLog) => {
48
- try {
49
- await storageService.write(data);
50
- setSavedData(data);
51
- } catch (error) {
52
- console.error('Error saving storage data:', error);
53
- }
54
- },
55
- [storageService]
56
- );
57
-
58
- const open = useCallback(async () => {
59
- const userLogs = await storageService.read();
60
- userLogs.attempts = (userLogs.attempts || 0) + 1;
61
- userLogs.lastAttempt = Date.now();
62
- try {
63
- await storageService.write(userLogs);
64
- setSavedData(userLogs);
65
- } catch (error) {
66
- console.error('Error saving storage data:', error);
67
- }
68
- setIsWidgetVisible(true);
69
- }, [storageService]);
70
-
71
- const close = useCallback(() => {
72
- setIsWidgetVisible(false);
73
- }, []);
74
-
75
- const resize = useCallback(
76
- (value: string) => {
77
- const receivedHeight = Number(value);
78
-
79
- if (height !== undefined) {
80
- setWidgetHeight(height);
81
- } else if (receivedHeight > 0) {
82
- setWidgetHeight(receivedHeight);
83
- }
84
- },
85
- [height]
86
- );
87
-
88
- return {
89
- savedData,
90
- widgetHeight,
91
- isWidgetVisible,
92
- setIsWidgetVisible,
93
- loadSavedData,
94
- saveData,
95
- open,
96
- close,
97
- resize,
98
- userId,
99
- screenHeight
100
- };
101
- };
1
+ import { useState, useCallback, useMemo } from 'react';
2
+ import { Dimensions } from 'react-native';
3
+ import type {
4
+ WidgetData,
5
+ WidgetOptions,
6
+ WidgetType,
7
+ WidgetSamplerLog
8
+ } from '../interfaces';
9
+ import { StorageService } from '../services/storage';
10
+
11
+ function getUserId(widgetData: WidgetData): string {
12
+ return (
13
+ widgetData.customer_id ??
14
+ widgetData.document ??
15
+ widgetData.email ??
16
+ 'default_user'
17
+ );
18
+ }
19
+
20
+ export const useWidgetState = (
21
+ data: WidgetData,
22
+ options?: WidgetOptions,
23
+ type?: WidgetType
24
+ ) => {
25
+ const [savedData, setSavedData] = useState<WidgetSamplerLog | null>(null);
26
+ const [isWidgetVisible, setIsWidgetVisible] = useState<boolean>(false);
27
+
28
+ const userId = getUserId(data);
29
+
30
+ const storageService = useMemo(() => new StorageService(userId), [userId]);
31
+
32
+ const screenHeight = Dimensions.get('screen').height;
33
+ const height = options?.height ? Number(options.height) : undefined;
34
+
35
+ const [widgetHeight, setWidgetHeight] = useState<number>(height ?? 300);
36
+
37
+ const loadSavedData = useCallback(async () => {
38
+ try {
39
+ const jsonValue = await storageService.read();
40
+ setSavedData(jsonValue);
41
+ } catch (error) {
42
+ console.error('Error loading storage data:', error);
43
+ }
44
+ }, [storageService]);
45
+
46
+ const saveData = useCallback(
47
+ async (data: WidgetSamplerLog) => {
48
+ try {
49
+ await storageService.write(data);
50
+ setSavedData(data);
51
+ } catch (error) {
52
+ console.error('Error saving storage data:', error);
53
+ }
54
+ },
55
+ [storageService]
56
+ );
57
+
58
+ const open = useCallback(async () => {
59
+ const userLogs = await storageService.read();
60
+ userLogs.attempts = (userLogs.attempts || 0) + 1;
61
+ userLogs.lastAttempt = Date.now();
62
+ try {
63
+ await storageService.write(userLogs);
64
+ setSavedData(userLogs);
65
+ } catch (error) {
66
+ console.error('Error saving storage data:', error);
67
+ }
68
+ setIsWidgetVisible(true);
69
+ }, [storageService]);
70
+
71
+ const close = useCallback(() => {
72
+ setIsWidgetVisible(false);
73
+ }, []);
74
+
75
+ const resize = useCallback(
76
+ (value: string) => {
77
+ const receivedHeight = Number(value);
78
+
79
+ if (height !== undefined) {
80
+ setWidgetHeight(height);
81
+ } else if (receivedHeight > 0) {
82
+ setWidgetHeight(receivedHeight);
83
+ }
84
+ },
85
+ [height]
86
+ );
87
+
88
+ return {
89
+ savedData,
90
+ widgetHeight,
91
+ isWidgetVisible,
92
+ setIsWidgetVisible,
93
+ loadSavedData,
94
+ saveData,
95
+ open,
96
+ close,
97
+ resize,
98
+ userId,
99
+ screenHeight
100
+ };
101
+ };
package/src/index.ts CHANGED
@@ -1,8 +1,12 @@
1
- export { SoluCXWidget } from './SoluCXWidget';
2
- export { useWidgetState } from './hooks/useWidgetState';
3
- export { WidgetEventService } from './services/widgetEventService';
4
- export { StorageService } from './services/storage';
5
- export { buildWidgetURL } from './utils/urlUtils';
6
- export { ModalWidget } from './components/ModalWidget';
7
- export { getWidgetStyles, styles } from './styles/widgetStyles';
8
- export * from './interfaces';
1
+ export { SoluCXWidget } from './SoluCXWidget';
2
+ export { useWidgetState } from './hooks/useWidgetState';
3
+ export { WidgetEventService } from './services/widgetEventService';
4
+ export { StorageService } from './services/storage';
5
+ export { getDeviceInfo, type DeviceInfo } from './hooks/useDeviceInfoCollector';
6
+ export { requestWidgetUrl } from './services/widgetBootstrapService';
7
+ export { buildWidgetURL } from './utils/urlUtils';
8
+ export { ModalWidget } from './components/ModalWidget';
9
+ export { getWidgetStyles, styles } from './styles/widgetStyles';
10
+ export { SDK_NAME, SDK_VERSION } from './constants/Constants';
11
+ export { getClientVersion } from './services/ClientVersionCollector';
12
+ export * from './interfaces';
@@ -0,0 +1,15 @@
1
+ import { BlockReason } from "../services/widgetValidationService";
2
+
3
+ export interface WidgetCallbacks {
4
+ onPreOpen?: (userId: string) => void;
5
+ onOpened?: (userId: string) => void;
6
+ onBlock?: (blockReason: BlockReason | undefined) => void;
7
+ onPingError?: (error: unknown) => void;
8
+ onClosed?: () => void;
9
+ onError?: (message: string) => void;
10
+ onPageChanged?: (page: string) => void;
11
+ onQuestionAnswered?: () => void;
12
+ onCompleted?: (userId: string) => void;
13
+ onPartialCompleted?: (userId: string) => void;
14
+ onResize?: (height: string) => void;
15
+ }
@@ -1,20 +1,20 @@
1
- export interface WidgetData {
2
- transaction_id?: string;
3
- attempt_id?: string;
4
- form_id?: string;
5
- customer_id?: string;
6
- name?: string;
7
- email?: string;
8
- phone?: string;
9
- phone2?: string;
10
- document?: string;
11
- birth_date?: string;
12
- store_id?: string;
13
- store_name?: string;
14
- employee_id?: string;
15
- employee_name?: string;
16
- amount?: number;
17
- score?: number;
18
- journey?: string;
19
- [key: string]: string | number | undefined;
1
+ export interface WidgetData {
2
+ transaction_id?: string;
3
+ attempt_id?: string;
4
+ form_id?: string;
5
+ customer_id?: string;
6
+ name?: string;
7
+ email?: string;
8
+ phone?: string;
9
+ phone2?: string;
10
+ document?: string;
11
+ birth_date?: string;
12
+ store_id?: string;
13
+ store_name?: string;
14
+ employee_id?: string;
15
+ employee_name?: string;
16
+ amount?: number;
17
+ score?: number;
18
+ journey?: string;
19
+ [key: string]: string | number | undefined;
20
20
  }
@@ -1,8 +1,8 @@
1
- export interface WidgetOptions {
2
- height?: number;
3
- retry?: {
4
- attempts?: number;
5
- interval?: number;
6
- };
7
- waitDelayAfterRating?: number;
1
+ export interface WidgetOptions {
2
+ height?: number;
3
+ retry?: {
4
+ attempts?: number;
5
+ interval?: number;
6
+ };
7
+ waitDelayAfterRating?: number;
8
8
  }
@@ -1,15 +1,15 @@
1
- export interface WidgetResponse {
2
- status?: "success" | "error";
3
- message?: string;
4
- }
5
-
6
- export class WidgetError extends Error implements WidgetResponse {
7
- status: "error";
8
- message: string;
9
-
10
- constructor(message: string) {
11
- super(message);
12
- this.status = "error";
13
- this.message = message;
14
- }
15
- }
1
+ export interface WidgetResponse {
2
+ status?: "success" | "error";
3
+ message?: string;
4
+ }
5
+
6
+ export class WidgetError extends Error implements WidgetResponse {
7
+ status: "error";
8
+ message: string;
9
+
10
+ constructor(message: string) {
11
+ super(message);
12
+ this.status = "error";
13
+ this.message = message;
14
+ }
15
+ }
@@ -1,6 +1,6 @@
1
- export interface WidgetSamplerLog {
2
- attempts: number;
3
- lastAttempt: number;
4
- lastRating: number;
5
- lastParcial: number;
1
+ export interface WidgetSamplerLog {
2
+ attempts: number;
3
+ lastAttempt: number;
4
+ lastRating: number;
5
+ lastParcial: number;
6
6
  }
@@ -1,24 +1,25 @@
1
- export type SoluCXKey = string;
2
- export type WidgetType = "bottom" | "top" | "inline" | "modal";
3
- export type EventKey =
4
- | "FORM_OPENED"
5
- | "FORM_CLOSE"
6
- | "FORM_ERROR"
7
- | "FORM_PAGECHANGED"
8
- | "QUESTION_ANSWERED"
9
- | "FORM_COMPLETED"
10
- | "FORM_PARTIALCOMPLETED"
11
- | "FORM_RESIZE";
12
- export type SurveyEventKey =
13
- | "closeSoluCXWidget"
14
- | "dismissSoluCXWidget"
15
- | "completeSoluCXWidget"
16
- | "partialSoluCXWidget"
17
- | "resizeSoluCXWidget"
18
- | "openSoluCXWidget"
19
- | `errorSoluCXWidget`;
20
- export type { WidgetResponse } from './WidgetResponse';
21
- export type { WidgetData } from './WidgetData';
22
- export type { WidgetOptions } from './WidgetOptions';
23
- export type { WidgetSamplerLog } from './WidgetSamplerLog';
24
- export type { WidgetError } from './WidgetResponse';
1
+ export type SoluCXKey = string;
2
+ export type WidgetType = "bottom" | "top" | "inline" | "modal";
3
+ export type EventKey =
4
+ | "FORM_CLOSE"
5
+ | "FORM_ERROR"
6
+ | "FORM_PAGECHANGED"
7
+ | "QUESTION_ANSWERED"
8
+ | "FORM_COMPLETED"
9
+ | "FORM_PARTIALCOMPLETED"
10
+ | "FORM_RESIZE";
11
+ export type SurveyEventKey =
12
+ | "closeSoluCXWidget"
13
+ | "dismissSoluCXWidget"
14
+ | "completeSoluCXWidget"
15
+ | "partialSoluCXWidget"
16
+ | "resizeSoluCXWidget"
17
+ | "openSoluCXWidget"
18
+ | `errorSoluCXWidget`;
19
+ export type { WidgetResponse } from './WidgetResponse';
20
+ export type { WidgetData } from './WidgetData';
21
+ export type { WidgetOptions } from './WidgetOptions';
22
+ export type { WidgetSamplerLog } from './WidgetSamplerLog';
23
+ export type { WidgetError } from './WidgetResponse';
24
+ export type { WidgetCallbacks } from './WidgetCallbacks';
25
+ export type { WidgetDisplayResult } from '../services/widgetValidationService';
@@ -0,0 +1,15 @@
1
+ export const getClientVersion = (): string => {
2
+ try {
3
+ const DeviceInfo = require("react-native-device-info");
4
+ if (DeviceInfo?.default?.getVersion) {
5
+ return DeviceInfo.default.getVersion();
6
+ }
7
+ if (DeviceInfo?.getVersion) {
8
+ return DeviceInfo.getVersion();
9
+ }
10
+ } catch (error) {
11
+ // react-native-device-info não disponível
12
+ }
13
+
14
+ return "unknown";
15
+ };
@@ -1,21 +1,21 @@
1
- import AsyncStorage from '@react-native-async-storage/async-storage';
2
- import { WidgetSamplerLog } from '../interfaces';
3
- import { STORAGE_KEY } from '../constants/webViewConstants';
4
-
5
- export class StorageService {
6
- private key: string;
7
-
8
- constructor(key: string) {
9
- this.key = `${STORAGE_KEY}_${key}`;
10
- }
11
-
12
- async write(data: WidgetSamplerLog): Promise<void> {
13
- const json = JSON.stringify(data);
14
- await AsyncStorage.setItem(this.key, json);
15
- }
16
-
17
- async read(): Promise<WidgetSamplerLog> {
18
- const json = await AsyncStorage.getItem(this.key);
19
- return json ? JSON.parse(json) as WidgetSamplerLog : {} as WidgetSamplerLog;
20
- }
21
- }
1
+ import AsyncStorage from '@react-native-async-storage/async-storage';
2
+ import type { WidgetSamplerLog } from '../interfaces';
3
+ import { STORAGE_KEY } from '../constants/webViewConstants';
4
+
5
+ export class StorageService {
6
+ private key: string;
7
+
8
+ constructor(key: string) {
9
+ this.key = `${STORAGE_KEY}_${key}`;
10
+ }
11
+
12
+ async write(data: WidgetSamplerLog): Promise<void> {
13
+ const json = JSON.stringify(data);
14
+ await AsyncStorage.setItem(this.key, json);
15
+ }
16
+
17
+ async read(): Promise<WidgetSamplerLog> {
18
+ const json = await AsyncStorage.getItem(this.key);
19
+ return json ? JSON.parse(json) as WidgetSamplerLog : {} as WidgetSamplerLog;
20
+ }
21
+ }
@@ -0,0 +1,67 @@
1
+ import type { SoluCXKey, WidgetData } from "../interfaces";
2
+ import { RATING_FORM_ENDPOINT } from "../constants/webViewConstants";
3
+ import { getClientVersion } from "../services/ClientVersionCollector";
4
+ import { getDeviceInfo } from "../hooks/useDeviceInfoCollector";
5
+ import { SDK_NAME, SDK_VERSION } from "../constants/Constants";
6
+
7
+ type Payload = {
8
+ url: string;
9
+ };
10
+
11
+ const buildRequestParams = (requestParams: WidgetData): URLSearchParams => {
12
+ const params = new URLSearchParams();
13
+
14
+ Object.entries(requestParams).forEach(([key, value]) => {
15
+ if (value === undefined || value === null) return;
16
+ params.append(key, typeof value === "number" ? value.toString() : String(value));
17
+ });
18
+
19
+ params.set("transactionId", "");
20
+ params.set("attemptId", "");
21
+
22
+ return params;
23
+ };
24
+
25
+ const buildRequestHeaders = (instanceKey: SoluCXKey, userId: string): Record<string, string> => {
26
+ const deviceInfo = getDeviceInfo();
27
+ const appVersion = getClientVersion();
28
+ const isMobile = deviceInfo.deviceType === "phone" ? "?1" : "?0";
29
+ const userAgent = `${SDK_NAME}/${SDK_VERSION} (${deviceInfo.platform}; OS ${deviceInfo.osVersion}; ${deviceInfo.model}) App/${appVersion}`;
30
+
31
+ return {
32
+ accept: "application/json, text/plain, */*",
33
+ "x-solucx-api-key": instanceKey,
34
+ "x-solucx-device-id": userId,
35
+ "x-client-id": userId,
36
+ "x-sdk-name": SDK_NAME,
37
+ "x-sdk-version": SDK_VERSION,
38
+ "x-solucx-app-version": appVersion,
39
+ "x-solucx-client-platform": deviceInfo.platform,
40
+ "Sec-CH-UA": `"${SDK_NAME}";v="${SDK_VERSION}", "app";v="${appVersion}"`,
41
+ "Sec-CH-UA-Platform": `"${deviceInfo.platform}"`,
42
+ "Sec-CH-UA-Mobile": isMobile,
43
+ "Sec-CH-UA-Platform-Version": `"${deviceInfo.osVersion}"`,
44
+ "Sec-CH-UA-Model": `"${deviceInfo.model}"`,
45
+ "User-Agent": userAgent,
46
+ };
47
+ };
48
+
49
+ export async function requestWidgetUrl(
50
+ instanceKey: SoluCXKey,
51
+ requestParams: WidgetData,
52
+ userId: string,
53
+ ): Promise<string | undefined> {
54
+ if (typeof fetch !== "function") return;
55
+
56
+ const params = buildRequestParams(requestParams);
57
+ const url = `${RATING_FORM_ENDPOINT}?${params.toString()}`;
58
+ const headers = buildRequestHeaders(instanceKey, userId);
59
+ const response = await fetch(url, { method: "GET", headers });
60
+
61
+ if (!response.ok) return;
62
+
63
+ const payload: Payload = await response.json();
64
+ return payload?.url;
65
+ }
66
+
67
+ export { buildRequestParams };