@solucx/react-native-solucx-widget 0.2.4 → 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.
Files changed (256) hide show
  1. package/README.md +526 -182
  2. package/lib/SoluCXWidget.d.ts +50 -7
  3. package/lib/SoluCXWidget.d.ts.map +1 -1
  4. package/lib/SoluCXWidget.js +105 -100
  5. package/lib/SoluCXWidget.js.map +1 -1
  6. package/lib/SoluCXWidgetHost.d.ts +3 -0
  7. package/lib/SoluCXWidgetHost.d.ts.map +1 -0
  8. package/lib/SoluCXWidgetHost.js +34 -0
  9. package/lib/SoluCXWidgetHost.js.map +1 -0
  10. package/lib/SoluCXWidgetView.d.ts +12 -0
  11. package/lib/SoluCXWidgetView.d.ts.map +1 -0
  12. package/lib/SoluCXWidgetView.js +61 -0
  13. package/lib/SoluCXWidgetView.js.map +1 -0
  14. package/lib/components/CloseButton.d.ts +1 -1
  15. package/lib/components/CloseButton.d.ts.map +1 -1
  16. package/lib/components/CloseButton.js +4 -1
  17. package/lib/components/CloseButton.js.map +1 -1
  18. package/lib/components/InlineWidget.d.ts.map +1 -1
  19. package/lib/components/InlineWidget.js +2 -7
  20. package/lib/components/InlineWidget.js.map +1 -1
  21. package/lib/components/ModalWidget.d.ts +1 -1
  22. package/lib/components/ModalWidget.d.ts.map +1 -1
  23. package/lib/components/ModalWidget.js +3 -16
  24. package/lib/components/ModalWidget.js.map +1 -1
  25. package/lib/components/OverlayWidget.d.ts.map +1 -1
  26. package/lib/components/OverlayWidget.js +5 -15
  27. package/lib/components/OverlayWidget.js.map +1 -1
  28. package/lib/components/index.d.ts +5 -0
  29. package/lib/components/index.d.ts.map +1 -0
  30. package/lib/components/index.js +12 -0
  31. package/lib/components/index.js.map +1 -0
  32. package/lib/constants/Constants.d.ts +11 -0
  33. package/lib/constants/Constants.d.ts.map +1 -1
  34. package/lib/constants/Constants.js +16 -1
  35. package/lib/constants/Constants.js.map +1 -1
  36. package/lib/{interfaces → domain}/WidgetCallbacks.d.ts +2 -2
  37. package/lib/domain/WidgetCallbacks.d.ts.map +1 -0
  38. package/lib/domain/WidgetCallbacks.js.map +1 -0
  39. package/{src/interfaces/WidgetData.ts → lib/domain/WidgetData.d.ts} +5 -2
  40. package/lib/domain/WidgetData.d.ts.map +1 -0
  41. package/lib/{interfaces → domain}/WidgetData.js.map +1 -1
  42. package/lib/domain/WidgetDisplayResult.d.ts +6 -0
  43. package/lib/domain/WidgetDisplayResult.d.ts.map +1 -0
  44. package/lib/domain/WidgetDisplayResult.js +3 -0
  45. package/lib/domain/WidgetDisplayResult.js.map +1 -0
  46. package/lib/domain/WidgetOptions.d.ts +27 -0
  47. package/lib/domain/WidgetOptions.d.ts.map +1 -0
  48. package/lib/domain/WidgetOptions.js +30 -0
  49. package/lib/domain/WidgetOptions.js.map +1 -0
  50. package/lib/domain/WidgetResponse.d.ts +5 -0
  51. package/lib/domain/WidgetResponse.d.ts.map +1 -0
  52. package/lib/{interfaces/WidgetOptions.js → domain/WidgetResponse.js} +1 -1
  53. package/lib/domain/WidgetResponse.js.map +1 -0
  54. package/lib/domain/WidgetSamplerLog.d.ts +12 -0
  55. package/lib/domain/WidgetSamplerLog.d.ts.map +1 -0
  56. package/lib/domain/WidgetSamplerLog.js.map +1 -0
  57. package/lib/{interfaces → domain}/index.d.ts +1 -2
  58. package/lib/domain/index.d.ts.map +1 -0
  59. package/lib/{interfaces → domain}/index.js.map +1 -1
  60. package/lib/hooks/index.d.ts +2 -2
  61. package/lib/hooks/index.d.ts.map +1 -1
  62. package/lib/hooks/index.js +5 -5
  63. package/lib/hooks/index.js.map +1 -1
  64. package/lib/hooks/useClientVersionCollector.d.ts +3 -0
  65. package/lib/hooks/useClientVersionCollector.d.ts.map +1 -0
  66. package/lib/{services/ClientVersionCollector.js → hooks/useClientVersionCollector.js} +7 -2
  67. package/lib/hooks/useClientVersionCollector.js.map +1 -0
  68. package/lib/hooks/useHeightAnimation.d.ts +0 -1
  69. package/lib/hooks/useHeightAnimation.d.ts.map +1 -1
  70. package/lib/hooks/useHeightAnimation.js +4 -2
  71. package/lib/hooks/useHeightAnimation.js.map +1 -1
  72. package/lib/hooks/useWidget.d.ts +13 -0
  73. package/lib/hooks/useWidget.d.ts.map +1 -0
  74. package/lib/hooks/useWidget.js +44 -0
  75. package/lib/hooks/useWidget.js.map +1 -0
  76. package/lib/hooks/useWidgetBootstrap.d.ts +21 -0
  77. package/lib/hooks/useWidgetBootstrap.d.ts.map +1 -0
  78. package/lib/hooks/useWidgetBootstrap.js +87 -0
  79. package/lib/hooks/useWidgetBootstrap.js.map +1 -0
  80. package/lib/hooks/useWidgetServices.d.ts +19 -0
  81. package/lib/hooks/useWidgetServices.d.ts.map +1 -0
  82. package/lib/hooks/useWidgetServices.js +34 -0
  83. package/lib/hooks/useWidgetServices.js.map +1 -0
  84. package/lib/hooks/useWidgetUI.d.ts +9 -0
  85. package/lib/hooks/useWidgetUI.d.ts.map +1 -0
  86. package/lib/hooks/useWidgetUI.js +33 -0
  87. package/lib/hooks/useWidgetUI.js.map +1 -0
  88. package/lib/index.d.ts +10 -11
  89. package/lib/index.d.ts.map +1 -1
  90. package/lib/index.js +13 -38
  91. package/lib/index.js.map +1 -1
  92. package/lib/services/UserIdentificationService.d.ts +3 -0
  93. package/lib/services/UserIdentificationService.d.ts.map +1 -0
  94. package/lib/services/UserIdentificationService.js +17 -0
  95. package/lib/services/UserIdentificationService.js.map +1 -0
  96. package/lib/services/WidgetBootstrapService.d.ts +12 -0
  97. package/lib/services/WidgetBootstrapService.d.ts.map +1 -0
  98. package/lib/services/{widgetBootstrapService.js → WidgetBootstrapService.js} +36 -15
  99. package/lib/services/WidgetBootstrapService.js.map +1 -0
  100. package/lib/services/WidgetEventService.d.ts +8 -0
  101. package/lib/services/WidgetEventService.d.ts.map +1 -0
  102. package/lib/services/WidgetEventService.js +14 -0
  103. package/lib/services/WidgetEventService.js.map +1 -0
  104. package/lib/services/WidgetStateManager.d.ts +20 -0
  105. package/lib/services/WidgetStateManager.d.ts.map +1 -0
  106. package/lib/services/WidgetStateManager.js +93 -0
  107. package/lib/services/WidgetStateManager.js.map +1 -0
  108. package/lib/services/WidgetValidationService.d.ts +17 -0
  109. package/lib/services/WidgetValidationService.d.ts.map +1 -0
  110. package/lib/services/WidgetValidationService.js +132 -0
  111. package/lib/services/WidgetValidationService.js.map +1 -0
  112. package/lib/services/events/EventHandlerFactory.d.ts +18 -0
  113. package/lib/services/events/EventHandlerFactory.d.ts.map +1 -0
  114. package/lib/services/events/EventHandlerFactory.js +67 -0
  115. package/lib/services/events/EventHandlerFactory.js.map +1 -0
  116. package/lib/services/events/EventHandlers.d.ts +10 -0
  117. package/lib/services/events/EventHandlers.d.ts.map +1 -0
  118. package/lib/services/events/EventHandlers.js +72 -0
  119. package/lib/services/events/EventHandlers.js.map +1 -0
  120. package/lib/services/events/index.d.ts +3 -0
  121. package/lib/services/events/index.d.ts.map +1 -0
  122. package/lib/services/events/index.js +21 -0
  123. package/lib/services/events/index.js.map +1 -0
  124. package/lib/services/height/HeightStrategies.d.ts +3 -0
  125. package/lib/services/height/HeightStrategies.d.ts.map +1 -0
  126. package/lib/services/height/HeightStrategies.js +14 -0
  127. package/lib/services/height/HeightStrategies.js.map +1 -0
  128. package/lib/services/storage/AsyncStorageService.d.ts +13 -0
  129. package/lib/services/storage/AsyncStorageService.d.ts.map +1 -0
  130. package/lib/services/storage/AsyncStorageService.js +73 -0
  131. package/lib/services/storage/AsyncStorageService.js.map +1 -0
  132. package/lib/services/storage/IStorageService.d.ts +30 -0
  133. package/lib/services/storage/IStorageService.d.ts.map +1 -0
  134. package/lib/services/storage/IStorageService.js +3 -0
  135. package/lib/services/storage/IStorageService.js.map +1 -0
  136. package/lib/services/storage/StorageIdBuilder.d.ts +11 -0
  137. package/lib/services/storage/StorageIdBuilder.d.ts.map +1 -0
  138. package/lib/services/storage/StorageIdBuilder.js +17 -0
  139. package/lib/services/storage/StorageIdBuilder.js.map +1 -0
  140. package/lib/services/storage/index.d.ts +3 -0
  141. package/lib/services/storage/index.d.ts.map +1 -0
  142. package/lib/services/storage/index.js +6 -0
  143. package/lib/services/storage/index.js.map +1 -0
  144. package/lib/styles/widgetStyles.d.ts +1 -1
  145. package/lib/styles/widgetStyles.d.ts.map +1 -1
  146. package/package.json +8 -2
  147. package/src/SoluCXWidget.ts +144 -0
  148. package/src/SoluCXWidgetHost.tsx +44 -0
  149. package/src/SoluCXWidgetView.tsx +97 -0
  150. package/src/__tests__/ClientVersionCollector.test.ts +5 -5
  151. package/src/__tests__/OverlayWidget.rendering.test.tsx +12 -14
  152. package/src/__tests__/SoluCXWidget.rendering.test.tsx +103 -60
  153. package/src/__tests__/SoluCXWidget.test.ts +448 -0
  154. package/src/__tests__/WidgetValidationService.test.ts +408 -0
  155. package/src/__tests__/e2e/widget-lifecycle.test.tsx +14 -23
  156. package/src/__tests__/index.test.tsx +39 -0
  157. package/src/__tests__/integration/webview-communication-simple.test.tsx +8 -6
  158. package/src/__tests__/integration/webview-communication.test.tsx +127 -130
  159. package/src/__tests__/normalizeWidgetOptions.test.ts +80 -0
  160. package/src/__tests__/useWidgetBootstrap.test.ts +634 -0
  161. package/src/__tests__/useWidgetState.test.ts +56 -13
  162. package/src/__tests__/widgetBootstrapService.test.ts +15 -17
  163. package/src/components/CloseButton.tsx +6 -2
  164. package/src/components/InlineWidget.tsx +4 -9
  165. package/src/components/ModalWidget.tsx +15 -45
  166. package/src/components/OverlayWidget.tsx +5 -15
  167. package/src/components/index.ts +4 -0
  168. package/src/constants/Constants.ts +15 -0
  169. package/src/{interfaces → domain}/WidgetCallbacks.ts +2 -2
  170. package/{lib/interfaces/WidgetData.d.ts → src/domain/WidgetData.ts} +3 -2
  171. package/src/domain/WidgetDisplayResult.ts +16 -0
  172. package/src/domain/WidgetOptions.ts +53 -0
  173. package/src/domain/WidgetResponse.ts +5 -0
  174. package/src/domain/WidgetSamplerLog.ts +11 -0
  175. package/src/{interfaces → domain}/index.ts +1 -2
  176. package/src/hooks/index.ts +2 -2
  177. package/src/{services/ClientVersionCollector.ts → hooks/useClientVersionCollector.ts} +6 -0
  178. package/src/hooks/useHeightAnimation.ts +6 -3
  179. package/src/hooks/useWidget.ts +46 -0
  180. package/src/hooks/useWidgetBootstrap.ts +117 -0
  181. package/src/hooks/useWidgetServices.ts +44 -0
  182. package/src/hooks/useWidgetUI.ts +38 -0
  183. package/src/index.ts +16 -11
  184. package/src/services/UserIdentificationService.ts +14 -0
  185. package/src/services/{widgetBootstrapService.ts → WidgetBootstrapService.ts} +43 -19
  186. package/src/services/WidgetEventService.ts +15 -0
  187. package/src/services/WidgetStateManager.ts +115 -0
  188. package/src/services/WidgetValidationService.ts +149 -0
  189. package/src/services/events/EventHandlerFactory.ts +70 -0
  190. package/src/services/events/EventHandlers.ts +67 -0
  191. package/src/services/events/index.ts +2 -0
  192. package/src/services/height/HeightStrategies.ts +15 -0
  193. package/src/services/storage/AsyncStorageService.ts +74 -0
  194. package/src/services/storage/IStorageService.ts +32 -0
  195. package/src/services/storage/StorageIdBuilder.ts +15 -0
  196. package/src/services/storage/index.ts +2 -0
  197. package/src/styles/widgetStyles.ts +1 -1
  198. package/README.intern.md +0 -490
  199. package/lib/constants/webViewConstants.d.ts +0 -12
  200. package/lib/constants/webViewConstants.d.ts.map +0 -1
  201. package/lib/constants/webViewConstants.js +0 -19
  202. package/lib/constants/webViewConstants.js.map +0 -1
  203. package/lib/hooks/useWidgetHeight.d.ts +0 -13
  204. package/lib/hooks/useWidgetHeight.d.ts.map +0 -1
  205. package/lib/hooks/useWidgetHeight.js +0 -21
  206. package/lib/hooks/useWidgetHeight.js.map +0 -1
  207. package/lib/hooks/useWidgetState.d.ts +0 -15
  208. package/lib/hooks/useWidgetState.d.ts.map +0 -1
  209. package/lib/hooks/useWidgetState.js +0 -79
  210. package/lib/hooks/useWidgetState.js.map +0 -1
  211. package/lib/interfaces/WidgetCallbacks.d.ts.map +0 -1
  212. package/lib/interfaces/WidgetCallbacks.js.map +0 -1
  213. package/lib/interfaces/WidgetData.d.ts.map +0 -1
  214. package/lib/interfaces/WidgetOptions.d.ts +0 -9
  215. package/lib/interfaces/WidgetOptions.d.ts.map +0 -1
  216. package/lib/interfaces/WidgetOptions.js.map +0 -1
  217. package/lib/interfaces/WidgetResponse.d.ts +0 -10
  218. package/lib/interfaces/WidgetResponse.d.ts.map +0 -1
  219. package/lib/interfaces/WidgetResponse.js +0 -12
  220. package/lib/interfaces/WidgetResponse.js.map +0 -1
  221. package/lib/interfaces/WidgetSamplerLog.d.ts +0 -7
  222. package/lib/interfaces/WidgetSamplerLog.d.ts.map +0 -1
  223. package/lib/interfaces/WidgetSamplerLog.js.map +0 -1
  224. package/lib/interfaces/index.d.ts.map +0 -1
  225. package/lib/services/ClientVersionCollector.d.ts +0 -2
  226. package/lib/services/ClientVersionCollector.d.ts.map +0 -1
  227. package/lib/services/ClientVersionCollector.js.map +0 -1
  228. package/lib/services/storage.d.ts +0 -8
  229. package/lib/services/storage.d.ts.map +0 -1
  230. package/lib/services/storage.js +0 -23
  231. package/lib/services/storage.js.map +0 -1
  232. package/lib/services/widgetBootstrapService.d.ts +0 -6
  233. package/lib/services/widgetBootstrapService.d.ts.map +0 -1
  234. package/lib/services/widgetBootstrapService.js.map +0 -1
  235. package/lib/services/widgetEventService.d.ts +0 -19
  236. package/lib/services/widgetEventService.d.ts.map +0 -1
  237. package/lib/services/widgetEventService.js +0 -79
  238. package/lib/services/widgetEventService.js.map +0 -1
  239. package/lib/services/widgetValidationService.d.ts +0 -18
  240. package/lib/services/widgetValidationService.d.ts.map +0 -1
  241. package/lib/services/widgetValidationService.js +0 -71
  242. package/lib/services/widgetValidationService.js.map +0 -1
  243. package/src/SoluCXWidget.tsx +0 -178
  244. package/src/constants/webViewConstants.ts +0 -15
  245. package/src/hooks/useWidgetHeight.ts +0 -38
  246. package/src/hooks/useWidgetState.ts +0 -101
  247. package/src/interfaces/WidgetOptions.ts +0 -8
  248. package/src/interfaces/WidgetResponse.ts +0 -15
  249. package/src/interfaces/WidgetSamplerLog.ts +0 -6
  250. package/src/services/storage.ts +0 -21
  251. package/src/services/widgetEventService.ts +0 -110
  252. package/src/services/widgetValidationService.ts +0 -102
  253. /package/lib/{interfaces → domain}/WidgetCallbacks.js +0 -0
  254. /package/lib/{interfaces → domain}/WidgetData.js +0 -0
  255. /package/lib/{interfaces → domain}/WidgetSamplerLog.js +0 -0
  256. /package/lib/{interfaces → domain}/index.js +0 -0
@@ -1,19 +1,23 @@
1
- import { WidgetEventService } from '../services/widgetEventService';
2
- import type { WidgetCallbacks } from '../interfaces';
1
+ import { WidgetEventService } from '../services/WidgetEventService';
2
+ import type { WidgetCallbacks } from '../domain';
3
+ import type { WidgetStateManager } from '../services/WidgetStateManager';
3
4
 
4
5
  describe('WidgetEventService', () => {
5
- let mockSetIsWidgetVisible: jest.Mock;
6
+ let mockHide: jest.Mock;
6
7
  let mockResize: jest.Mock;
7
8
  let mockUserId: string;
9
+ let mockTransactionId: string;
8
10
  let mockCallbacks: WidgetCallbacks;
11
+ let mockStateManager: jest.Mocked<WidgetStateManager>;
9
12
  let service: WidgetEventService;
10
13
 
11
14
  beforeEach(() => {
12
15
  jest.clearAllMocks();
13
16
 
14
- mockSetIsWidgetVisible = jest.fn();
17
+ mockHide = jest.fn();
15
18
  mockResize = jest.fn();
16
19
  mockUserId = 'test-user-123';
20
+ mockTransactionId = 'txn-456';
17
21
  mockCallbacks = {
18
22
  onClosed: jest.fn(),
19
23
  onError: jest.fn(),
@@ -24,11 +28,26 @@ describe('WidgetEventService', () => {
24
28
  onResize: jest.fn(),
25
29
  };
26
30
 
31
+ mockStateManager = {
32
+ getLogs: jest.fn(),
33
+ saveLogs: jest.fn(),
34
+ incrementAttempt: jest.fn(),
35
+ resetAttempts: jest.fn(),
36
+ markTransactionAnswered: jest.fn(),
37
+ hasAnsweredTransaction: jest.fn(),
38
+ clearLogs: jest.fn(),
39
+ hasLogs: jest.fn(),
40
+ updateTimestamp: jest.fn(),
41
+ overrideTimestamp: jest.fn(),
42
+ } as unknown as jest.Mocked<WidgetStateManager>;
43
+
27
44
  service = new WidgetEventService(
28
- mockSetIsWidgetVisible,
45
+ mockHide,
29
46
  mockResize,
30
47
  mockUserId,
48
+ mockTransactionId,
31
49
  mockCallbacks,
50
+ mockStateManager,
32
51
  );
33
52
  });
34
53
 
@@ -40,7 +59,7 @@ describe('WidgetEventService', () => {
40
59
  it('should handle FORM_CLOSE event correctly', async () => {
41
60
  const result = await service.handleMessage('FORM_CLOSE', true);
42
61
 
43
- expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(false);
62
+ expect(mockHide).toHaveBeenCalledTimes(1);
44
63
  expect(mockCallbacks.onClosed).toHaveBeenCalledTimes(1);
45
64
  expect(result).toEqual({ status: 'success' });
46
65
  });
@@ -56,11 +75,19 @@ describe('WidgetEventService', () => {
56
75
  it('should handle FORM_ERROR event correctly', async () => {
57
76
  const result = await service.handleMessage('FORM_ERROR-Something went wrong', true);
58
77
 
59
- expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(false);
78
+ expect(mockHide).toHaveBeenCalledTimes(1);
60
79
  expect(mockCallbacks.onError).toHaveBeenCalledWith('Something went wrong');
61
80
  expect(result).toEqual({ status: 'error', message: 'Something went wrong' });
62
81
  });
63
82
 
83
+ it('should preserve hyphenated FORM_ERROR payloads', async () => {
84
+ const result = await service.handleMessage('FORM_ERROR-timeout-api-gateway', true);
85
+
86
+ expect(mockHide).toHaveBeenCalledTimes(1);
87
+ expect(mockCallbacks.onError).toHaveBeenCalledWith('timeout-api-gateway');
88
+ expect(result).toEqual({ status: 'error', message: 'timeout-api-gateway' });
89
+ });
90
+
64
91
  it('should handle FORM_PAGECHANGED event correctly', async () => {
65
92
  const result = await service.handleMessage('FORM_PAGECHANGED-page2', true);
66
93
 
@@ -71,6 +98,7 @@ describe('WidgetEventService', () => {
71
98
  it('should handle QUESTION_ANSWERED event correctly', async () => {
72
99
  const result = await service.handleMessage('QUESTION_ANSWERED', true);
73
100
 
101
+ expect(mockStateManager.resetAttempts).toHaveBeenCalledTimes(1);
74
102
  expect(mockCallbacks.onQuestionAnswered).toHaveBeenCalledTimes(1);
75
103
  expect(result).toEqual({ status: 'success' });
76
104
  });
@@ -78,6 +106,9 @@ describe('WidgetEventService', () => {
78
106
  it('should handle FORM_COMPLETED event correctly', async () => {
79
107
  const result = await service.handleMessage('FORM_COMPLETED', true);
80
108
 
109
+ expect(mockStateManager.resetAttempts).toHaveBeenCalledTimes(1);
110
+ expect(mockStateManager.markTransactionAnswered).toHaveBeenCalledWith('txn-456');
111
+ expect(mockStateManager.updateTimestamp).toHaveBeenCalledWith('lastSubmit');
81
112
  expect(mockCallbacks.onCompleted).toHaveBeenCalledWith('test-user-123');
82
113
  expect(result).toEqual({ status: 'success' });
83
114
  });
@@ -85,10 +116,19 @@ describe('WidgetEventService', () => {
85
116
  it('should handle FORM_PARTIALCOMPLETED event correctly', async () => {
86
117
  const result = await service.handleMessage('FORM_PARTIALCOMPLETED', true);
87
118
 
119
+ expect(mockStateManager.resetAttempts).toHaveBeenCalledTimes(1);
120
+ expect(mockStateManager.markTransactionAnswered).toHaveBeenCalledWith('txn-456');
121
+ expect(mockStateManager.updateTimestamp).toHaveBeenCalledWith('lastPartialSubmit');
88
122
  expect(mockCallbacks.onPartialCompleted).toHaveBeenCalledWith('test-user-123');
89
123
  expect(result).toEqual({ status: 'success' });
90
124
  });
91
125
 
126
+ it('should not mark a transaction as answered on QUESTION_ANSWERED', async () => {
127
+ await service.handleMessage('QUESTION_ANSWERED', true);
128
+
129
+ expect(mockStateManager.markTransactionAnswered).not.toHaveBeenCalled();
130
+ });
131
+
92
132
  it('should return error for unknown events', async () => {
93
133
  const result = await service.handleMessage('UNKNOWN_EVENT', true);
94
134
 
@@ -104,7 +144,7 @@ describe('WidgetEventService', () => {
104
144
  it('should adapt closeSoluCXWidget to FORM_CLOSE', async () => {
105
145
  const result = await service.handleMessage('closeSoluCXWidget', false);
106
146
 
107
- expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(false);
147
+ expect(mockHide).toHaveBeenCalledTimes(1);
108
148
  expect(mockCallbacks.onClosed).toHaveBeenCalledTimes(1);
109
149
  expect(result).toEqual({ status: 'success' });
110
150
  });
@@ -126,7 +166,7 @@ describe('WidgetEventService', () => {
126
166
  it('should adapt dismissSoluCXWidget to FORM_CLOSE', async () => {
127
167
  const result = await service.handleMessage('dismissSoluCXWidget', false);
128
168
 
129
- expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(false);
169
+ expect(mockHide).toHaveBeenCalledTimes(1);
130
170
  expect(mockCallbacks.onClosed).toHaveBeenCalledTimes(1);
131
171
  expect(result).toEqual({ status: 'success' });
132
172
  });
@@ -134,7 +174,7 @@ describe('WidgetEventService', () => {
134
174
  it('should adapt errorSoluCXWidget to FORM_ERROR', async () => {
135
175
  const result = await service.handleMessage('errorSoluCXWidget-Network error', false);
136
176
 
137
- expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(false);
177
+ expect(mockHide).toHaveBeenCalledTimes(1);
138
178
  expect(mockCallbacks.onError).toHaveBeenCalledWith('Network error');
139
179
  expect(result).toEqual({ status: 'error', message: 'Network error' });
140
180
  });
@@ -156,7 +196,7 @@ describe('WidgetEventService', () => {
156
196
  it('should not call any callback when event is unknown', async () => {
157
197
  await service.handleMessage('SOME_RANDOM_EVENT', true);
158
198
 
159
- expect(mockSetIsWidgetVisible).not.toHaveBeenCalled();
199
+ expect(mockHide).not.toHaveBeenCalled();
160
200
  expect(mockResize).not.toHaveBeenCalled();
161
201
  expect(mockCallbacks.onClosed).not.toHaveBeenCalled();
162
202
  expect(mockCallbacks.onError).not.toHaveBeenCalled();
@@ -168,14 +208,17 @@ describe('WidgetEventService', () => {
168
208
 
169
209
  it('should work without callbacks when none are provided', async () => {
170
210
  const serviceWithoutCallbacks = new WidgetEventService(
171
- mockSetIsWidgetVisible,
211
+ mockHide,
172
212
  mockResize,
173
213
  mockUserId,
214
+ mockTransactionId,
215
+ undefined,
216
+ mockStateManager,
174
217
  );
175
218
 
176
219
  const result = await serviceWithoutCallbacks.handleMessage('FORM_CLOSE', true);
177
220
 
178
- expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(false);
221
+ expect(mockHide).toHaveBeenCalledTimes(1);
179
222
  expect(result).toEqual({ status: 'success' });
180
223
  });
181
224
  });
@@ -1,5 +1,5 @@
1
- import { requestWidgetUrl, buildRequestParams } from '../services/widgetBootstrapService';
2
- import { WidgetData } from '../interfaces';
1
+ import { requestWidgetUrl, buildRequestParams } from '../services/WidgetBootstrapService';
2
+ import { WidgetData } from '../domain';
3
3
 
4
4
  // Mock das dependências
5
5
  jest.mock('../constants/Constants', () => ({
@@ -7,7 +7,7 @@ jest.mock('../constants/Constants', () => ({
7
7
  SDK_VERSION: '0.1.16',
8
8
  }));
9
9
 
10
- jest.mock('../services/ClientVersionCollector', () => ({
10
+ jest.mock('../hooks/useClientVersionCollector', () => ({
11
11
  getClientVersion: jest.fn(() => '1.0.0'),
12
12
  }));
13
13
 
@@ -23,7 +23,7 @@ jest.mock('../hooks/useDeviceInfoCollector', () => ({
23
23
  // Mock global do fetch
24
24
  const originalFetch = global.fetch;
25
25
 
26
- describe('widgetBootstrapService', () => {
26
+ describe('WidgetBootstrapService', () => {
27
27
  beforeEach(() => {
28
28
  jest.clearAllMocks();
29
29
  global.fetch = jest.fn();
@@ -46,8 +46,8 @@ describe('widgetBootstrapService', () => {
46
46
  expect(params.get('customer_id')).toBe('cust-123');
47
47
  expect(params.get('email')).toBe('test@example.com');
48
48
  expect(params.get('journey')).toBe('checkout');
49
- expect(params.get('transactionId')).toBe('');
50
- expect(params.get('attemptId')).toBe('');
49
+ expect(params.get('transactionId')).toBeNull();
50
+ expect(params.get('attemptId')).toBeNull();
51
51
  });
52
52
 
53
53
  it('should skip undefined and null values', () => {
@@ -153,7 +153,7 @@ describe('widgetBootstrapService', () => {
153
153
  'user-id'
154
154
  );
155
155
 
156
- expect(result).toBe('https://widget.url/test');
156
+ expect(result).toEqual({ url: 'https://widget.url/test' });
157
157
  });
158
158
 
159
159
  it('should throw error when response is not ok', async () => {
@@ -190,28 +190,26 @@ describe('widgetBootstrapService', () => {
190
190
  ).rejects.toThrow('Fetch is not available');
191
191
  });
192
192
 
193
- it('should throw error when response payload does not contain url', async () => {
193
+ it('should return payload without url when response does not contain url', async () => {
194
194
  const mockResponse = {
195
195
  ok: true,
196
- json: jest.fn().mockResolvedValue({}),
196
+ json: jest.fn().mockResolvedValue({ available: false, reason: 'No survey available' }),
197
197
  };
198
198
  (global.fetch as jest.Mock).mockResolvedValue(mockResponse);
199
199
 
200
- await expect(
201
- requestWidgetUrl('api-key', { customer_id: 'cust' }, 'user-id')
202
- ).rejects.toThrow('Failed to get the widget api response.');
200
+ const result = await requestWidgetUrl('api-key', { customer_id: 'cust' }, 'user-id');
201
+ expect(result).toEqual({ available: false, reason: 'No survey available' });
203
202
  });
204
203
 
205
- it('should throw error when response payload url is null', async () => {
204
+ it('should return payload when url is null', async () => {
206
205
  const mockResponse = {
207
206
  ok: true,
208
- json: jest.fn().mockResolvedValue({ url: null }),
207
+ json: jest.fn().mockResolvedValue({ available: true, url: null }),
209
208
  };
210
209
  (global.fetch as jest.Mock).mockResolvedValue(mockResponse);
211
210
 
212
- await expect(
213
- requestWidgetUrl('api-key', { customer_id: 'cust' }, 'user-id')
214
- ).rejects.toThrow('Failed to get the widget api response.');
211
+ const result = await requestWidgetUrl('api-key', { customer_id: 'cust' }, 'user-id');
212
+ expect(result).toEqual({ available: true, url: null });
215
213
  });
216
214
  });
217
215
  });
@@ -2,15 +2,19 @@ import React from 'react';
2
2
  import { TouchableOpacity, Text, StyleSheet } from 'react-native';
3
3
 
4
4
  interface CloseButtonProps {
5
- onPress: () => void;
5
+ onPress?: () => void;
6
6
  visible?: boolean;
7
7
  }
8
8
 
9
9
  export const CloseButton: React.FC<CloseButtonProps> = ({ onPress, visible = true }) => {
10
10
  if (!visible) return null;
11
11
 
12
+ const handlePress = () => {
13
+ onPress?.();
14
+ };
15
+
12
16
  return (
13
- <TouchableOpacity style={styles.closeButton} onPress={onPress}>
17
+ <TouchableOpacity style={styles.closeButton} onPress={handlePress}>
14
18
  <Text style={styles.closeButtonText}>✕</Text>
15
19
  </TouchableOpacity>
16
20
  );
@@ -1,8 +1,7 @@
1
- import React, { useEffect } from 'react';
2
- import { View } from 'react-native';
1
+ import React from 'react';
2
+ import { View, Animated } from 'react-native';
3
3
  import { styles, getWidgetVisibility } from '../styles/widgetStyles';
4
4
  import { CloseButton } from './CloseButton';
5
- import { Animated } from 'react-native';
6
5
  import { useHeightAnimation } from '../hooks/useHeightAnimation';
7
6
 
8
7
  interface InlineWidgetProps {
@@ -18,18 +17,14 @@ export const InlineWidget: React.FC<InlineWidgetProps> = ({
18
17
  children,
19
18
  onClose,
20
19
  }) => {
21
- const { animatedHeightStyle, updateHeight } = useHeightAnimation(height);
22
-
23
- useEffect(() => {
24
- updateHeight(height);
25
- }, [height, updateHeight]);
20
+ const { animatedHeightStyle } = useHeightAnimation(height);
26
21
 
27
22
  return (
28
23
  <View style={[styles.inlineWrapper, getWidgetVisibility(visible)]}>
29
24
  <Animated.View
30
25
  style={[styles.inline, animatedHeightStyle, getWidgetVisibility(visible)]}>
31
26
  {children}
32
- <CloseButton visible={visible} onPress={onClose || (() => { })} />
27
+ <CloseButton visible={visible} onPress={onClose} />
33
28
  </Animated.View>
34
29
  </View>
35
30
  );
@@ -1,9 +1,8 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { Modal, View, Animated } from 'react-native';
3
- import { SafeAreaView } from 'react-native-safe-area-context';
4
- import { styles, getWidgetVisibility } from '../styles/widgetStyles';
5
- import { CloseButton } from './CloseButton';
6
- import { useHeightAnimation } from '../hooks/useHeightAnimation';
1
+ import React from "react";
2
+ import { Modal, View, Animated } from "react-native";
3
+ import { styles, getWidgetVisibility } from "../styles/widgetStyles";
4
+ import { CloseButton } from "./CloseButton";
5
+ import { useHeightAnimation } from "../hooks/useHeightAnimation";
7
6
 
8
7
  interface ModalWidgetProps {
9
8
  visible: boolean;
@@ -12,46 +11,17 @@ interface ModalWidgetProps {
12
11
  onClose?: () => void;
13
12
  }
14
13
 
15
- export const ModalWidget: React.FC<ModalWidgetProps> = ({
16
- visible,
17
- height,
18
- children,
19
- onClose,
20
- }) => {
21
- const { animatedHeightStyle, updateHeight } = useHeightAnimation(height);
22
-
23
- useEffect(() => {
24
- updateHeight(height);
25
- }, [height, updateHeight]);
14
+ export const ModalWidget: React.FC<ModalWidgetProps> = ({ visible, height, children, onClose }) => {
15
+ const { animatedHeightStyle } = useHeightAnimation(height);
26
16
 
27
17
  return (
28
- <SafeAreaView>
29
- <Modal
30
- transparent
31
- visible={visible}
32
- animationType="slide"
33
- hardwareAccelerated
34
- >
35
- <View style={[styles.modalOverlay, getWidgetVisibility(visible)]}>
36
- <Animated.View
37
- style={[
38
- styles.modalContent,
39
- getWidgetVisibility(visible),
40
- animatedHeightStyle,
41
- ]}
42
- >
43
- {children}
44
- <CloseButton
45
- visible={visible}
46
- onPress={() => {
47
- if (onClose) {
48
- onClose();
49
- }
50
- }}
51
- />
52
- </Animated.View>
53
- </View>
54
- </Modal>
55
- </SafeAreaView>
18
+ <Modal transparent visible={visible} animationType="slide" hardwareAccelerated>
19
+ <View style={[styles.modalOverlay, getWidgetVisibility(visible)]}>
20
+ <Animated.View style={[styles.modalContent, getWidgetVisibility(visible), animatedHeightStyle]}>
21
+ {children}
22
+ <CloseButton visible={visible} onPress={onClose} />
23
+ </Animated.View>
24
+ </View>
25
+ </Modal>
56
26
  );
57
27
  };
@@ -1,8 +1,8 @@
1
- import React, { useState, useEffect } from 'react';
1
+ import React from 'react';
2
2
  import { View, type ViewStyle, Animated } from 'react-native';
3
3
  import { initialWindowMetrics } from 'react-native-safe-area-context';
4
4
  import { getWidgetStyles, getWidgetVisibility } from '../styles/widgetStyles';
5
- import { FIXED_Z_INDEX } from '../constants/webViewConstants';
5
+ import { FIXED_Z_INDEX } from '../constants/Constants';
6
6
  import { CloseButton } from './CloseButton';
7
7
  import { useHeightAnimation } from '../hooks/useHeightAnimation';
8
8
 
@@ -25,13 +25,8 @@ export const OverlayWidget: React.FC<OverlayWidgetProps> = ({
25
25
  }) => {
26
26
  const insets =
27
27
  initialWindowMetrics?.insets ?? { top: 0, bottom: 0, left: 0, right: 0 };
28
- const [isWidgetVisible, setIsWidgetVisible] = useState<boolean>(true);
29
28
 
30
- const { animatedHeightStyle, updateHeight } = useHeightAnimation(height);
31
-
32
- useEffect(() => {
33
- updateHeight(height);
34
- }, [height, updateHeight]);
29
+ const { animatedHeightStyle } = useHeightAnimation(height);
35
30
 
36
31
  const containerStyle: ViewStyle = {
37
32
  position: 'absolute',
@@ -61,7 +56,7 @@ export const OverlayWidget: React.FC<OverlayWidgetProps> = ({
61
56
 
62
57
  return (
63
58
  <>
64
- {isWidgetVisible && (
59
+ {visible && (
65
60
  <View style={[containerStyle, getWidgetVisibility(visible)]}>
66
61
  <Animated.View
67
62
  style={[
@@ -73,12 +68,7 @@ export const OverlayWidget: React.FC<OverlayWidgetProps> = ({
73
68
  {children}
74
69
  <CloseButton
75
70
  visible={visible}
76
- onPress={() => {
77
- setIsWidgetVisible(false);
78
- if (onClose) {
79
- onClose();
80
- }
81
- }}
71
+ onPress={onClose}
82
72
  />
83
73
  </Animated.View>
84
74
  </View>
@@ -0,0 +1,4 @@
1
+ export { CloseButton } from './CloseButton';
2
+ export { InlineWidget } from './InlineWidget';
3
+ export { ModalWidget } from './ModalWidget';
4
+ export { OverlayWidget } from './OverlayWidget';
@@ -2,3 +2,18 @@ import packageJson from "../../package.json";
2
2
 
3
3
  export const SDK_VERSION = packageJson.version;
4
4
  export const SDK_NAME = "rn-widget-sdk";
5
+ export const BASE_URL = 'https://survey-link.solucx.com.br/link';
6
+ export const WIDGET_API_BASE_URL = 'https://widget-api.solucx.com.br';
7
+ export const STORAGE_KEY = '@solucxWidgetLog';
8
+ export const DEFAULT_CHANNEL_NUMBER = 1;
9
+ export const DEFAULT_CHANNEL = 'widget';
10
+ export const DEFAULT_WIDTH = 380;
11
+ export const MIN_HEIGHT = 200;
12
+ export const FIXED_Z_INDEX = 9999;
13
+ export const MODAL_Z_INDEX = 10000;
14
+ export const DESIGN_HEIGHT = 700;
15
+ export const WEB_VIEW_MESSAGE_LISTENER = `
16
+ window.addEventListener('message', function(event) {
17
+ window.ReactNativeWebView.postMessage(event.data);
18
+ });
19
+ `;
@@ -1,9 +1,9 @@
1
- import { BlockReason } from "../services/widgetValidationService";
1
+ import type { BlockReason } from "./WidgetDisplayResult";
2
2
 
3
3
  export interface WidgetCallbacks {
4
4
  onPreOpen?: (userId: string) => void;
5
5
  onOpened?: (userId: string) => void;
6
- onBlock?: (blockReason: BlockReason | undefined) => void;
6
+ onBlocked?: (blockReason: BlockReason | string | undefined) => void;
7
7
  onClosed?: () => void;
8
8
  onError?: (message: string) => void;
9
9
  onPageChanged?: (page: string) => void;
@@ -16,6 +16,7 @@ export interface WidgetData {
16
16
  amount?: number;
17
17
  score?: number;
18
18
  journey?: string;
19
- [key: string]: string | number | undefined;
19
+ user_id?: string;
20
+ cpf?: string;
21
+ [key: `param_${string}`]: string | number | undefined;
20
22
  }
21
- //# sourceMappingURL=WidgetData.d.ts.map
@@ -0,0 +1,16 @@
1
+ export type BlockReason =
2
+ | "BLOCKED_BY_DISABLED"
3
+ | "BLOCKED_BY_SAMPLING"
4
+ | "BLOCKED_BY_TRANSACTION_ALREADY_ANSWERED"
5
+ | "BLOCKED_BY_MAX_ATTEMPTS"
6
+ | "BLOCKED_BY_WIDGET_DISPLAY_ATTEMPT_INTERVAL"
7
+ | "BLOCKED_BY_WIDGET_FIRST_ACCESS_INTERVAL"
8
+ | "BLOCKED_BY_WIDGET_DISPLAY_INTERVAL"
9
+ | "BLOCKED_BY_WIDGET_DISMISS_INTERVAL"
10
+ | "BLOCKED_BY_WIDGET_SUBMIT_INTERVAL"
11
+ | "BLOCKED_BY_WIDGET_PARTIAL_SUBMIT_INTERVAL";
12
+
13
+ export interface WidgetDisplayResult {
14
+ canDisplay: boolean;
15
+ blockReason?: BlockReason;
16
+ }
@@ -0,0 +1,53 @@
1
+ export interface WidgetRetryOptions {
2
+ attempts?: number;
3
+ interval?: number;
4
+ }
5
+
6
+ export interface WidgetOptions {
7
+ enabled?: boolean;
8
+ samplingPercentage?: number;
9
+ type?: string;
10
+ height?: number;
11
+ maxAttemptsAfterDismiss?: number;
12
+ waitDaysAfterWidgetDisplayAttempt?: number;
13
+ waitDaysAfterWidgetFirstAccess?: number;
14
+ waitDaysAfterWidgetDisplay?: number;
15
+ waitDaysAfterWidgetDismiss?: number;
16
+ waitDaysAfterWidgetSubmit?: number;
17
+ waitDaysAfterWidgetPartialSubmit?: number;
18
+
19
+ /** @deprecated Use maxAttemptsAfterDismiss and waitDaysAfterWidgetDismiss instead */
20
+ retry?: WidgetRetryOptions;
21
+ /** @deprecated Use waitDaysAfterWidgetSubmit and waitDaysAfterWidgetPartialSubmit instead */
22
+ waitDelayAfterRating?: number;
23
+ }
24
+
25
+ /**
26
+ * Normalizes legacy option fields (retry, waitDelayAfterRating) into the
27
+ * canonical internal fields. Canonical fields take precedence over legacy ones.
28
+ */
29
+ export function normalizeWidgetOptions(options: WidgetOptions): WidgetOptions {
30
+ if (!options) return options;
31
+
32
+ const normalized: WidgetOptions = { ...options };
33
+
34
+ if (options.retry) {
35
+ if (options.retry.attempts !== undefined && normalized.maxAttemptsAfterDismiss === undefined) {
36
+ normalized.maxAttemptsAfterDismiss = options.retry.attempts;
37
+ }
38
+ if (options.retry.interval !== undefined && normalized.waitDaysAfterWidgetDismiss === undefined) {
39
+ normalized.waitDaysAfterWidgetDismiss = options.retry.interval;
40
+ }
41
+ }
42
+
43
+ if (options.waitDelayAfterRating !== undefined) {
44
+ if (normalized.waitDaysAfterWidgetSubmit === undefined) {
45
+ normalized.waitDaysAfterWidgetSubmit = options.waitDelayAfterRating;
46
+ }
47
+ if (normalized.waitDaysAfterWidgetPartialSubmit === undefined) {
48
+ normalized.waitDaysAfterWidgetPartialSubmit = options.waitDelayAfterRating;
49
+ }
50
+ }
51
+
52
+ return normalized;
53
+ }
@@ -0,0 +1,5 @@
1
+ export interface WidgetResponse {
2
+ status?: "success" | "error";
3
+ message?: string;
4
+ }
5
+
@@ -0,0 +1,11 @@
1
+ export interface WidgetSamplerLog {
2
+ attempts: number;
3
+ answeredTransactionIds?: string[];
4
+ lastExperienceId?: string;
5
+ lastDisplayAttempt?: number;
6
+ lastFirstAccess?: number;
7
+ lastDisplay?: number;
8
+ lastDismiss?: number;
9
+ lastSubmit?: number;
10
+ lastPartialSubmit?: number;
11
+ }
@@ -20,6 +20,5 @@ export type { WidgetResponse } from './WidgetResponse';
20
20
  export type { WidgetData } from './WidgetData';
21
21
  export type { WidgetOptions } from './WidgetOptions';
22
22
  export type { WidgetSamplerLog } from './WidgetSamplerLog';
23
- export type { WidgetError } from './WidgetResponse';
24
23
  export type { WidgetCallbacks } from './WidgetCallbacks';
25
- export type { WidgetDisplayResult } from '../services/widgetValidationService';
24
+ export type { BlockReason, WidgetDisplayResult } from './WidgetDisplayResult';
@@ -1,2 +1,2 @@
1
- export { useWidgetState } from './useWidgetState';
2
- export { useWidgetHeight } from './useWidgetHeight';
1
+ export { useWidgetBootstrap } from './useWidgetBootstrap';
2
+ export { useWidgetServices } from './useWidgetServices';
@@ -1,3 +1,5 @@
1
+ import { useMemo } from 'react';
2
+
1
3
  export const getClientVersion = (): string => {
2
4
  try {
3
5
  const DeviceInfo = require("react-native-device-info");
@@ -13,3 +15,7 @@ export const getClientVersion = (): string => {
13
15
 
14
16
  return "unknown";
15
17
  };
18
+
19
+ export const useClientVersion = (): string => {
20
+ return useMemo(() => getClientVersion(), []);
21
+ };
@@ -1,5 +1,4 @@
1
- // hooks/useHeightAnimation.ts
2
- import { useRef, useCallback } from 'react';
1
+ import { useRef, useCallback, useEffect } from 'react';
3
2
  import { Animated } from 'react-native';
4
3
 
5
4
  export function useHeightAnimation(initialHeight = 0, duration = 300) {
@@ -16,7 +15,11 @@ export function useHeightAnimation(initialHeight = 0, duration = 300) {
16
15
  [height, duration],
17
16
  );
18
17
 
18
+ useEffect(() => {
19
+ updateHeight(initialHeight);
20
+ }, [initialHeight, updateHeight]);
21
+
19
22
  const animatedHeightStyle = { height };
20
23
 
21
- return { animatedHeightStyle, updateHeight, height };
24
+ return { animatedHeightStyle, height };
22
25
  }