@solucx/react-native-solucx-widget 0.2.1 → 0.2.2

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 (109) hide show
  1. package/lib/SoluCXWidget.d.ts +12 -0
  2. package/lib/SoluCXWidget.d.ts.map +1 -0
  3. package/lib/SoluCXWidget.js +110 -0
  4. package/lib/SoluCXWidget.js.map +1 -0
  5. package/lib/components/CloseButton.d.ts +8 -0
  6. package/lib/components/CloseButton.d.ts.map +1 -0
  7. package/lib/components/CloseButton.js +31 -0
  8. package/lib/components/CloseButton.js.map +1 -0
  9. package/lib/components/InlineWidget.d.ts +10 -0
  10. package/lib/components/InlineWidget.d.ts.map +1 -0
  11. package/lib/components/InlineWidget.js +19 -0
  12. package/lib/components/InlineWidget.js.map +1 -0
  13. package/lib/components/ModalWidget.d.ts +10 -0
  14. package/lib/components/ModalWidget.d.ts.map +1 -0
  15. package/lib/components/ModalWidget.js +27 -0
  16. package/lib/components/ModalWidget.js.map +1 -0
  17. package/lib/components/OverlayWidget.d.ts +12 -0
  18. package/lib/components/OverlayWidget.d.ts.map +1 -0
  19. package/lib/components/OverlayWidget.js +55 -0
  20. package/lib/components/OverlayWidget.js.map +1 -0
  21. package/lib/constants/Constants.d.ts +3 -0
  22. package/lib/constants/Constants.d.ts.map +1 -0
  23. package/lib/constants/Constants.js +10 -0
  24. package/lib/constants/Constants.js.map +1 -0
  25. package/lib/constants/webViewConstants.d.ts +12 -0
  26. package/lib/constants/webViewConstants.d.ts.map +1 -0
  27. package/lib/constants/webViewConstants.js +19 -0
  28. package/lib/constants/webViewConstants.js.map +1 -0
  29. package/lib/hooks/index.d.ts +3 -0
  30. package/lib/hooks/index.d.ts.map +1 -0
  31. package/lib/hooks/index.js +8 -0
  32. package/lib/hooks/index.js.map +1 -0
  33. package/lib/hooks/useDeviceInfoCollector.d.ts +14 -0
  34. package/lib/hooks/useDeviceInfoCollector.d.ts.map +1 -0
  35. package/lib/hooks/useDeviceInfoCollector.js +54 -0
  36. package/lib/hooks/useDeviceInfoCollector.js.map +1 -0
  37. package/lib/hooks/useHeightAnimation.d.ts +9 -0
  38. package/lib/hooks/useHeightAnimation.d.ts.map +1 -0
  39. package/lib/hooks/useHeightAnimation.js +19 -0
  40. package/lib/hooks/useHeightAnimation.js.map +1 -0
  41. package/lib/hooks/useWidgetHeight.d.ts +13 -0
  42. package/lib/hooks/useWidgetHeight.d.ts.map +1 -0
  43. package/lib/hooks/useWidgetHeight.js +21 -0
  44. package/lib/hooks/useWidgetHeight.js.map +1 -0
  45. package/lib/hooks/useWidgetState.d.ts +15 -0
  46. package/lib/hooks/useWidgetState.d.ts.map +1 -0
  47. package/lib/hooks/useWidgetState.js +79 -0
  48. package/lib/hooks/useWidgetState.js.map +1 -0
  49. package/lib/index.d.ts +13 -0
  50. package/lib/index.d.ts.map +1 -0
  51. package/lib/index.js +43 -0
  52. package/lib/index.js.map +1 -0
  53. package/lib/interfaces/WidgetCallbacks.d.ts +14 -0
  54. package/lib/interfaces/WidgetCallbacks.d.ts.map +1 -0
  55. package/lib/interfaces/WidgetCallbacks.js +3 -0
  56. package/lib/interfaces/WidgetCallbacks.js.map +1 -0
  57. package/lib/interfaces/WidgetData.d.ts +21 -0
  58. package/lib/interfaces/WidgetData.d.ts.map +1 -0
  59. package/lib/interfaces/WidgetData.js +3 -0
  60. package/lib/interfaces/WidgetData.js.map +1 -0
  61. package/lib/interfaces/WidgetOptions.d.ts +9 -0
  62. package/lib/interfaces/WidgetOptions.d.ts.map +1 -0
  63. package/lib/interfaces/WidgetOptions.js +3 -0
  64. package/lib/interfaces/WidgetOptions.js.map +1 -0
  65. package/lib/interfaces/WidgetResponse.d.ts +10 -0
  66. package/lib/interfaces/WidgetResponse.d.ts.map +1 -0
  67. package/lib/interfaces/WidgetResponse.js +12 -0
  68. package/lib/interfaces/WidgetResponse.js.map +1 -0
  69. package/lib/interfaces/WidgetSamplerLog.d.ts +7 -0
  70. package/lib/interfaces/WidgetSamplerLog.d.ts.map +1 -0
  71. package/lib/interfaces/WidgetSamplerLog.js +3 -0
  72. package/lib/interfaces/WidgetSamplerLog.js.map +1 -0
  73. package/lib/interfaces/index.d.ts +12 -0
  74. package/lib/interfaces/index.d.ts.map +1 -0
  75. package/lib/interfaces/index.js +3 -0
  76. package/lib/interfaces/index.js.map +1 -0
  77. package/lib/services/ClientVersionCollector.d.ts +2 -0
  78. package/lib/services/ClientVersionCollector.d.ts.map +1 -0
  79. package/lib/services/ClientVersionCollector.js +20 -0
  80. package/lib/services/ClientVersionCollector.js.map +1 -0
  81. package/lib/services/storage.d.ts +8 -0
  82. package/lib/services/storage.d.ts.map +1 -0
  83. package/lib/services/storage.js +23 -0
  84. package/lib/services/storage.js.map +1 -0
  85. package/lib/services/widgetBootstrapService.d.ts +5 -0
  86. package/lib/services/widgetBootstrapService.d.ts.map +1 -0
  87. package/lib/services/widgetBootstrapService.js +60 -0
  88. package/lib/services/widgetBootstrapService.js.map +1 -0
  89. package/lib/services/widgetEventService.d.ts +19 -0
  90. package/lib/services/widgetEventService.d.ts.map +1 -0
  91. package/lib/services/widgetEventService.js +79 -0
  92. package/lib/services/widgetEventService.js.map +1 -0
  93. package/lib/services/widgetValidationService.d.ts +18 -0
  94. package/lib/services/widgetValidationService.d.ts.map +1 -0
  95. package/lib/services/widgetValidationService.js +71 -0
  96. package/lib/services/widgetValidationService.js.map +1 -0
  97. package/lib/styles/widgetStyles.d.ts +87 -0
  98. package/lib/styles/widgetStyles.d.ts.map +1 -0
  99. package/lib/styles/widgetStyles.js +59 -0
  100. package/lib/styles/widgetStyles.js.map +1 -0
  101. package/lib/utils/urlUtils.d.ts +3 -0
  102. package/lib/utils/urlUtils.d.ts.map +1 -0
  103. package/lib/utils/urlUtils.js +13 -0
  104. package/lib/utils/urlUtils.js.map +1 -0
  105. package/package.json +5 -3
  106. package/src/SoluCXWidget.tsx +15 -11
  107. package/src/__tests__/SoluCXWidget.rendering.test.tsx +150 -0
  108. package/src/__tests__/widgetBootstrapService.test.ts +45 -10
  109. package/src/services/widgetBootstrapService.ts +13 -4
@@ -0,0 +1,18 @@
1
+ import type { WidgetOptions } from "../interfaces";
2
+ export type BlockReason = "BLOCKED_BY_RATING_INTERVAL" | "BLOCKED_BY_PARTIAL_INTERVAL" | "BLOCKED_BY_MAX_RETRY_ATTEMPTS";
3
+ export interface WidgetDisplayResult {
4
+ canDisplay: boolean;
5
+ blockReason?: BlockReason;
6
+ }
7
+ export declare class WidgetValidationService {
8
+ private storageService;
9
+ constructor(userId: string);
10
+ shouldDisplayWidget(widgetOptions: WidgetOptions): Promise<WidgetDisplayResult>;
11
+ private getLog;
12
+ private setLog;
13
+ private isWithinCollectInterval;
14
+ private isWithinCollectPartialInterval;
15
+ private isWithinRetryInterval;
16
+ private resetAttemptsIfNeeded;
17
+ }
18
+ //# sourceMappingURL=widgetValidationService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widgetValidationService.d.ts","sourceRoot":"","sources":["../../src/services/widgetValidationService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAoB,MAAM,eAAe,CAAC;AAGrE,MAAM,MAAM,WAAW,GACjB,4BAA4B,GAC5B,6BAA6B,GAC7B,+BAA+B,CAAC;AAEtC,MAAM,WAAW,mBAAmB;IAChC,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,WAAW,CAAC;CAC7B;AAED,qBAAa,uBAAuB;IAChC,OAAO,CAAC,cAAc,CAAiB;gBAE3B,MAAM,EAAE,MAAM;IAIpB,mBAAmB,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,mBAAmB,CAAC;YAsBvE,MAAM;YAcN,MAAM;IAQpB,OAAO,CAAC,uBAAuB;IAU/B,OAAO,CAAC,8BAA8B;IAUtC,OAAO,CAAC,qBAAqB;YAYf,qBAAqB;CAKtC"}
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WidgetValidationService = void 0;
4
+ const storage_1 = require("./storage");
5
+ class WidgetValidationService {
6
+ constructor(userId) {
7
+ this.storageService = new storage_1.StorageService(userId);
8
+ }
9
+ async shouldDisplayWidget(widgetOptions) {
10
+ const { retry, waitDelayAfterRating = 60 } = widgetOptions;
11
+ const { attempts = 5, interval = 1 } = retry || {};
12
+ const userLog = await this.getLog();
13
+ const now = Date.now();
14
+ const dayInMilliseconds = 86400000;
15
+ if (this.isWithinCollectInterval(userLog, waitDelayAfterRating, now, dayInMilliseconds)) {
16
+ return { canDisplay: false, blockReason: "BLOCKED_BY_RATING_INTERVAL" };
17
+ }
18
+ if (this.isWithinCollectPartialInterval(userLog, waitDelayAfterRating, now, dayInMilliseconds)) {
19
+ return { canDisplay: false, blockReason: "BLOCKED_BY_PARTIAL_INTERVAL" };
20
+ }
21
+ if (this.isWithinRetryInterval(userLog, interval, attempts, now, dayInMilliseconds)) {
22
+ return { canDisplay: false, blockReason: "BLOCKED_BY_MAX_RETRY_ATTEMPTS" };
23
+ }
24
+ await this.resetAttemptsIfNeeded(userLog, attempts);
25
+ await this.setLog(userLog);
26
+ return { canDisplay: true };
27
+ }
28
+ async getLog() {
29
+ try {
30
+ return await this.storageService.read();
31
+ }
32
+ catch (error) {
33
+ console.error("Error reading widget log:", error);
34
+ return {
35
+ attempts: 0,
36
+ lastAttempt: 0,
37
+ lastRating: 0,
38
+ lastParcial: 0,
39
+ };
40
+ }
41
+ }
42
+ async setLog(userLog) {
43
+ try {
44
+ await this.storageService.write(userLog);
45
+ }
46
+ catch (error) {
47
+ console.error("Error writing widget log:", error);
48
+ }
49
+ }
50
+ isWithinCollectInterval(userLog, waitDelayAfterRating, now, dayInMilliseconds) {
51
+ const timeSinceLastRating = now - userLog.lastRating;
52
+ return userLog.lastRating > 0 && timeSinceLastRating < waitDelayAfterRating * dayInMilliseconds;
53
+ }
54
+ isWithinCollectPartialInterval(userLog, waitDelayAfterRating, now, dayInMilliseconds) {
55
+ const timeSinceLastPartial = now - userLog.lastParcial;
56
+ return userLog.lastParcial > 0 && timeSinceLastPartial < waitDelayAfterRating * dayInMilliseconds;
57
+ }
58
+ isWithinRetryInterval(userLog, interval, attempts, now, dayInMilliseconds) {
59
+ if (userLog.attempts < attempts)
60
+ return false;
61
+ const timeSinceLastAttempt = now - userLog.lastAttempt;
62
+ return timeSinceLastAttempt < interval * dayInMilliseconds;
63
+ }
64
+ async resetAttemptsIfNeeded(userLog, maxAttempts) {
65
+ if (userLog.attempts >= maxAttempts) {
66
+ userLog.attempts = 0;
67
+ }
68
+ }
69
+ }
70
+ exports.WidgetValidationService = WidgetValidationService;
71
+ //# sourceMappingURL=widgetValidationService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widgetValidationService.js","sourceRoot":"","sources":["../../src/services/widgetValidationService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ import type { WidgetType } from '../interfaces';
2
+ export declare const styles: {
3
+ wrapper: {
4
+ flex: number;
5
+ justifyContent: "center";
6
+ alignItems: "center";
7
+ };
8
+ inlineWrapper: {
9
+ justifyContent: "center";
10
+ alignItems: "center";
11
+ };
12
+ bottom: {
13
+ position: "absolute";
14
+ bottom: number;
15
+ justifyContent: "center";
16
+ alignItems: "center";
17
+ };
18
+ top: {
19
+ position: "absolute";
20
+ top: number;
21
+ justifyContent: "center";
22
+ alignItems: "center";
23
+ };
24
+ inline: {
25
+ justifyContent: "center";
26
+ alignItems: "center";
27
+ };
28
+ modalOverlay: {
29
+ flex: number;
30
+ justifyContent: "center";
31
+ backgroundColor: string;
32
+ alignItems: "center";
33
+ };
34
+ modalContent: {
35
+ backgroundColor: string;
36
+ borderRadius: number;
37
+ };
38
+ };
39
+ export declare const getWidgetVisibility: (visibility: boolean) => {
40
+ opacity: number;
41
+ pointerEvents: "none" | "auto";
42
+ };
43
+ export declare const getWidgetStyles: (type: WidgetType) => {
44
+ container: {
45
+ flex: number;
46
+ justifyContent: "center";
47
+ alignItems: "center";
48
+ };
49
+ content: {
50
+ position: "absolute";
51
+ bottom: number;
52
+ justifyContent: "center";
53
+ alignItems: "center";
54
+ };
55
+ } | {
56
+ container: {
57
+ flex: number;
58
+ justifyContent: "center";
59
+ alignItems: "center";
60
+ };
61
+ content: {
62
+ position: "absolute";
63
+ top: number;
64
+ justifyContent: "center";
65
+ alignItems: "center";
66
+ };
67
+ } | {
68
+ container: {
69
+ justifyContent: "center";
70
+ alignItems: "center";
71
+ };
72
+ content: {
73
+ justifyContent: "center";
74
+ alignItems: "center";
75
+ };
76
+ } | {
77
+ container: {
78
+ flex: number;
79
+ justifyContent: "center";
80
+ alignItems: "center";
81
+ };
82
+ content: {
83
+ justifyContent: "center";
84
+ alignItems: "center";
85
+ };
86
+ };
87
+ //# sourceMappingURL=widgetStyles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widgetStyles.d.ts","sourceRoot":"","sources":["../../src/styles/widgetStyles.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCjB,CAAC;AAEH,eAAO,MAAM,mBAAmB,GAAI,YAAY,OAAO;;;CAKtD,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAS/C,CAAC"}
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getWidgetStyles = exports.getWidgetVisibility = exports.styles = void 0;
4
+ const react_native_1 = require("react-native");
5
+ exports.styles = react_native_1.StyleSheet.create({
6
+ wrapper: {
7
+ flex: 1,
8
+ justifyContent: 'center',
9
+ alignItems: 'center',
10
+ },
11
+ inlineWrapper: {
12
+ justifyContent: 'center',
13
+ alignItems: 'center',
14
+ },
15
+ bottom: {
16
+ position: 'absolute',
17
+ bottom: 0,
18
+ justifyContent: 'center',
19
+ alignItems: 'center',
20
+ },
21
+ top: {
22
+ position: 'absolute',
23
+ top: 0,
24
+ justifyContent: 'center',
25
+ alignItems: 'center',
26
+ },
27
+ inline: {
28
+ justifyContent: 'center',
29
+ alignItems: 'center',
30
+ },
31
+ modalOverlay: {
32
+ flex: 1,
33
+ justifyContent: 'center',
34
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
35
+ alignItems: 'center',
36
+ },
37
+ modalContent: {
38
+ backgroundColor: 'white',
39
+ borderRadius: 10,
40
+ },
41
+ });
42
+ const getWidgetVisibility = (visibility) => {
43
+ return {
44
+ opacity: visibility ? 1 : 0,
45
+ pointerEvents: visibility ? 'auto' : 'none',
46
+ };
47
+ };
48
+ exports.getWidgetVisibility = getWidgetVisibility;
49
+ const getWidgetStyles = (type) => {
50
+ const styleMap = {
51
+ 'bottom': { container: exports.styles.wrapper, content: exports.styles.bottom },
52
+ 'top': { container: exports.styles.wrapper, content: exports.styles.top },
53
+ 'inline': { container: exports.styles.inlineWrapper, content: exports.styles.inline },
54
+ 'modal': { container: exports.styles.wrapper, content: exports.styles.inline }
55
+ };
56
+ return styleMap[type] || styleMap.bottom;
57
+ };
58
+ exports.getWidgetStyles = getWidgetStyles;
59
+ //# sourceMappingURL=widgetStyles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widgetStyles.js","sourceRoot":"","sources":["../../src/styles/widgetStyles.ts"],"names":[],"mappings":";;;AAAA,+CAA0C;AAG7B,QAAA,MAAM,GAAG,yBAAU,CAAC,MAAM,CAAC;IACpC,OAAO,EAAE;QACL,IAAI,EAAE,CAAC;QACP,cAAc,EAAE,QAAQ;QACxB,UAAU,EAAE,QAAQ;KACvB;IACD,aAAa,EAAE;QACX,cAAc,EAAE,QAAQ;QACxB,UAAU,EAAE,QAAQ;KACvB;IACD,MAAM,EAAE;QACJ,QAAQ,EAAE,UAAU;QACpB,MAAM,EAAE,CAAC;QACT,cAAc,EAAE,QAAQ;QACxB,UAAU,EAAE,QAAQ;KACvB;IACD,GAAG,EAAE;QACD,QAAQ,EAAE,UAAU;QACpB,GAAG,EAAE,CAAC;QACN,cAAc,EAAE,QAAQ;QACxB,UAAU,EAAE,QAAQ;KACvB;IACD,MAAM,EAAE;QACJ,cAAc,EAAE,QAAQ;QACxB,UAAU,EAAE,QAAQ;KACvB;IACD,YAAY,EAAE;QACV,IAAI,EAAE,CAAC;QACP,cAAc,EAAE,QAAQ;QACxB,eAAe,EAAE,oBAAoB;QACrC,UAAU,EAAE,QAAQ;KACvB;IACD,YAAY,EAAE;QACV,eAAe,EAAE,OAAO;QACxB,YAAY,EAAE,EAAE;KACnB;CACJ,CAAC,CAAC;AAEI,MAAM,mBAAmB,GAAG,CAAC,UAAmB,EAAE,EAAE;IACvD,OAAO;QACH,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3B,aAAa,EAAE,UAAU,CAAC,CAAC,CAAC,MAAe,CAAC,CAAC,CAAC,MAAe;KAChE,CAAC;AACN,CAAC,CAAC;AALW,QAAA,mBAAmB,uBAK9B;AAEK,MAAM,eAAe,GAAG,CAAC,IAAgB,EAAE,EAAE;IAChD,MAAM,QAAQ,GAAG;QACb,QAAQ,EAAE,EAAE,SAAS,EAAE,cAAM,CAAC,OAAO,EAAE,OAAO,EAAE,cAAM,CAAC,MAAM,EAAE;QAC/D,KAAK,EAAE,EAAE,SAAS,EAAE,cAAM,CAAC,OAAO,EAAE,OAAO,EAAE,cAAM,CAAC,GAAG,EAAE;QACzD,QAAQ,EAAE,EAAE,SAAS,EAAE,cAAM,CAAC,aAAa,EAAE,OAAO,EAAE,cAAM,CAAC,MAAM,EAAE;QACrE,OAAO,EAAE,EAAE,SAAS,EAAE,cAAM,CAAC,OAAO,EAAE,OAAO,EAAE,cAAM,CAAC,MAAM,EAAE;KACjE,CAAC;IAEF,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC;AAC7C,CAAC,CAAC;AATW,QAAA,eAAe,mBAS1B"}
@@ -0,0 +1,3 @@
1
+ import type { WidgetData, SoluCXKey } from "../interfaces";
2
+ export declare function buildWidgetURL(key: SoluCXKey, data: WidgetData): string;
3
+ //# sourceMappingURL=urlUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"urlUtils.d.ts","sourceRoot":"","sources":["../../src/utils/urlUtils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE3D,wBAAgB,cAAc,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CASvE"}
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildWidgetURL = buildWidgetURL;
4
+ const webViewConstants_1 = require("../constants/webViewConstants");
5
+ function buildWidgetURL(key, data) {
6
+ const params = new URLSearchParams(data);
7
+ const baseURL = `${webViewConstants_1.BASE_URL}/${key}/?mode=widget`;
8
+ if (data.transaction_id) {
9
+ return `${baseURL}&${params.toString()}`;
10
+ }
11
+ return `${baseURL}&transaction_id=&${params.toString()}`;
12
+ }
13
+ //# sourceMappingURL=urlUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"urlUtils.js","sourceRoot":"","sources":["../../src/utils/urlUtils.ts"],"names":[],"mappings":";;AAGA,wCASC;AAZD,oEAAyD;AAGzD,SAAgB,cAAc,CAAC,GAAc,EAAE,IAAgB;IAC3D,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,IAA8B,CAAC,CAAC;IACnE,MAAM,OAAO,GAAG,GAAG,2BAAQ,IAAI,GAAG,eAAe,CAAC;IAElD,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,OAAO,GAAG,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;IAC7C,CAAC;IAED,OAAO,GAAG,OAAO,oBAAoB,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;AAC7D,CAAC"}
package/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "@solucx/react-native-solucx-widget",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "The React Native SDK for Solucx Widget",
5
- "main": "src/index",
5
+ "main": "lib/index.js",
6
+ "types": "lib/index.d.ts",
6
7
  "author": " <> ()",
7
8
  "homepage": "#readme",
8
9
  "scripts": {
9
- "build": "tsc",
10
+ "build": "tsc --project tsconfig.build.json",
10
11
  "prepublishOnly": "npm run build",
11
12
  "test": "jest"
12
13
  },
13
14
  "files": [
15
+ "lib/",
14
16
  "src/",
15
17
  "README.md"
16
18
  ],
@@ -24,10 +24,8 @@ export const SoluCXWidget: React.FC<SoluCXWidgetProps> = ({ soluCXKey, type, dat
24
24
  const webviewRef = useRef<WebView>(null);
25
25
  const { width } = Dimensions.get("window");
26
26
 
27
- if (!data) {
28
- callbacks?.onError?.("Widget data is required but was not provided");
29
- return;
30
- }
27
+ const callbacksRef = useRef(callbacks);
28
+ callbacksRef.current = callbacks;
31
29
 
32
30
  const serializedData = JSON.stringify(data);
33
31
  const normalizedData = useMemo(() => data, [serializedData]);
@@ -36,12 +34,12 @@ export const SoluCXWidget: React.FC<SoluCXWidgetProps> = ({ soluCXKey, type, dat
36
34
  useWidgetState(normalizedData, options, type);
37
35
 
38
36
  const eventService = useMemo(
39
- () => new WidgetEventService(setIsWidgetVisible, resize, userId, callbacks),
37
+ () => new WidgetEventService(setIsWidgetVisible, resize, userId, callbacksRef.current),
40
38
  [setIsWidgetVisible, resize],
41
39
  );
42
40
 
43
41
  const validationService = useMemo(() => new WidgetValidationService(userId), [userId]);
44
- const isForm = Boolean(normalizedData.form_id);
42
+ const isForm = Boolean(normalizedData?.form_id);
45
43
  const [widgetUri, setWidgetUri] = useState<string | null>(null);
46
44
 
47
45
  useEffect(() => {
@@ -52,6 +50,7 @@ export const SoluCXWidget: React.FC<SoluCXWidgetProps> = ({ soluCXKey, type, dat
52
50
  let isActive = true;
53
51
 
54
52
  const prepareWidgetURL = async () => {
53
+ if (!data) return;
55
54
  setWidgetUri(null);
56
55
 
57
56
  try {
@@ -63,15 +62,15 @@ export const SoluCXWidget: React.FC<SoluCXWidgetProps> = ({ soluCXKey, type, dat
63
62
 
64
63
  if (!result.canDisplay) {
65
64
  const blockReason = result?.blockReason;
66
- callbacks?.onBlock?.(blockReason);
65
+ callbacksRef.current?.onBlock?.(blockReason);
67
66
  setIsWidgetVisible(false);
68
67
  return;
69
68
  }
70
69
 
71
- callbacks?.onPreOpen?.(userId);
70
+ callbacksRef.current?.onPreOpen?.(userId);
72
71
  open();
73
72
  setWidgetUri(widgetUrl);
74
- callbacks?.onOpened?.(userId);
73
+ callbacksRef.current?.onOpened?.(userId);
75
74
  } catch (error) {
76
75
  if (isActive) {
77
76
  let errorMessage = "Unknown error";
@@ -80,7 +79,7 @@ export const SoluCXWidget: React.FC<SoluCXWidgetProps> = ({ soluCXKey, type, dat
80
79
  else if (typeof error === "string") errorMessage = error;
81
80
  else if (error !== null && typeof error === "object") errorMessage = JSON.stringify(error);
82
81
 
83
- callbacks?.onError?.(errorMessage);
82
+ callbacksRef.current?.onError?.(errorMessage);
84
83
  setIsWidgetVisible(false);
85
84
  }
86
85
  }
@@ -91,7 +90,7 @@ export const SoluCXWidget: React.FC<SoluCXWidgetProps> = ({ soluCXKey, type, dat
91
90
  return () => {
92
91
  isActive = false;
93
92
  };
94
- }, [validationService, soluCXKey, normalizedData, isForm, setIsWidgetVisible, options, open, userId, callbacks]);
93
+ }, [validationService, soluCXKey, normalizedData, isForm, setIsWidgetVisible, options, open, userId]);
95
94
 
96
95
  const handleWebViewMessage = useCallback(
97
96
  async (message: string) => {
@@ -119,6 +118,11 @@ export const SoluCXWidget: React.FC<SoluCXWidgetProps> = ({ soluCXKey, type, dat
119
118
 
120
119
  const webViewStyle = [{ height: widgetHeight }, { width }];
121
120
 
121
+ if (!data) {
122
+ callbacksRef.current?.onError?.("Widget data is required but was not provided");
123
+ return null;
124
+ }
125
+
122
126
  if (!widgetUri) {
123
127
  return null;
124
128
  }
@@ -502,3 +502,153 @@ describe("SoluCXWidget callbacks", () => {
502
502
  });
503
503
  });
504
504
  });
505
+
506
+ describe("SoluCXWidget error handling from requestWidgetUrl", () => {
507
+ it("calls onError when requestWidgetUrl throws error for non-ok response (404)", async () => {
508
+ mockRequestWidgetUrl.mockRejectedValue(new Error("Failed to fetch widget URL: 404 Not Found"));
509
+ const onError = jest.fn();
510
+
511
+ const props = {
512
+ ...baseProps,
513
+ type: "modal" as const,
514
+ callbacks: { onError },
515
+ };
516
+
517
+ const { queryByTestId } = render(<SoluCXWidget {...props} />);
518
+
519
+ await waitFor(() => {
520
+ expect(mockRequestWidgetUrl).toHaveBeenCalledTimes(1);
521
+ expect(onError).toHaveBeenCalledTimes(1);
522
+ expect(onError).toHaveBeenCalledWith("Failed to fetch widget URL: 404 Not Found");
523
+ expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(false);
524
+ expect(queryByTestId("webview")).toBeNull();
525
+ });
526
+ });
527
+
528
+ it("calls onError when requestWidgetUrl throws error for non-ok response (500)", async () => {
529
+ mockRequestWidgetUrl.mockRejectedValue(new Error("Failed to fetch widget URL: 500 Internal Server Error"));
530
+ const onError = jest.fn();
531
+
532
+ const props = {
533
+ ...baseProps,
534
+ type: "inline" as const,
535
+ callbacks: { onError },
536
+ };
537
+
538
+ const { queryByTestId } = render(<SoluCXWidget {...props} />);
539
+
540
+ await waitFor(() => {
541
+ expect(mockRequestWidgetUrl).toHaveBeenCalledTimes(1);
542
+ expect(onError).toHaveBeenCalledTimes(1);
543
+ expect(onError).toHaveBeenCalledWith("Failed to fetch widget URL: 500 Internal Server Error");
544
+ expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(false);
545
+ expect(queryByTestId("webview")).toBeNull();
546
+ });
547
+ });
548
+
549
+ it("calls onError when requestWidgetUrl throws error for fetch not available", async () => {
550
+ mockRequestWidgetUrl.mockRejectedValue(new Error("Fetch is not available"));
551
+ const onError = jest.fn();
552
+
553
+ const props = {
554
+ ...baseProps,
555
+ type: "bottom" as const,
556
+ callbacks: { onError },
557
+ };
558
+
559
+ const { queryByTestId } = render(<SoluCXWidget {...props} />);
560
+
561
+ await waitFor(() => {
562
+ expect(mockRequestWidgetUrl).toHaveBeenCalledTimes(1);
563
+ expect(onError).toHaveBeenCalledTimes(1);
564
+ expect(onError).toHaveBeenCalledWith("Fetch is not available");
565
+ expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(false);
566
+ expect(queryByTestId("webview")).toBeNull();
567
+ });
568
+ });
569
+
570
+ it("calls onError when requestWidgetUrl throws error for missing URL in response", async () => {
571
+ mockRequestWidgetUrl.mockRejectedValue(new Error("Widget URL not found in response"));
572
+ const onError = jest.fn();
573
+
574
+ const props = {
575
+ ...baseProps,
576
+ type: "top" as const,
577
+ callbacks: { onError },
578
+ };
579
+
580
+ const { queryByTestId } = render(<SoluCXWidget {...props} />);
581
+
582
+ await waitFor(() => {
583
+ expect(mockRequestWidgetUrl).toHaveBeenCalledTimes(1);
584
+ expect(onError).toHaveBeenCalledTimes(1);
585
+ expect(onError).toHaveBeenCalledWith("Widget URL not found in response");
586
+ expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(false);
587
+ expect(queryByTestId("webview")).toBeNull();
588
+ });
589
+ });
590
+
591
+ it("does not call shouldDisplayWidget when requestWidgetUrl fails", async () => {
592
+ mockRequestWidgetUrl.mockRejectedValue(new Error("Failed to fetch widget URL: 403 Forbidden"));
593
+ const onError = jest.fn();
594
+
595
+ const props = {
596
+ ...baseProps,
597
+ type: "modal" as const,
598
+ callbacks: { onError },
599
+ };
600
+
601
+ render(<SoluCXWidget {...props} />);
602
+
603
+ await waitFor(() => {
604
+ expect(mockRequestWidgetUrl).toHaveBeenCalledTimes(1);
605
+ expect(mockShouldDisplayWidget).not.toHaveBeenCalled();
606
+ expect(mockOpen).not.toHaveBeenCalled();
607
+ expect(onError).toHaveBeenCalledWith("Failed to fetch widget URL: 403 Forbidden");
608
+ });
609
+ });
610
+
611
+ it("does not call onPreOpen or onOpened when requestWidgetUrl fails", async () => {
612
+ mockRequestWidgetUrl.mockRejectedValue(new Error("Failed to fetch widget URL: 401 Unauthorized"));
613
+ const onPreOpen = jest.fn();
614
+ const onOpened = jest.fn();
615
+ const onError = jest.fn();
616
+
617
+ const props = {
618
+ ...baseProps,
619
+ type: "inline" as const,
620
+ callbacks: { onPreOpen, onOpened, onError },
621
+ };
622
+
623
+ render(<SoluCXWidget {...props} />);
624
+
625
+ await waitFor(() => {
626
+ expect(mockRequestWidgetUrl).toHaveBeenCalledTimes(1);
627
+ expect(onPreOpen).not.toHaveBeenCalled();
628
+ expect(onOpened).not.toHaveBeenCalled();
629
+ expect(onError).toHaveBeenCalledWith("Failed to fetch widget URL: 401 Unauthorized");
630
+ });
631
+ });
632
+
633
+ it("widget remains hidden when requestWidgetUrl fails with 404", async () => {
634
+ mockRequestWidgetUrl.mockRejectedValue(new Error("Failed to fetch widget URL: 404 Not Found"));
635
+
636
+ const props = {
637
+ ...baseProps,
638
+ type: "bottom" as const,
639
+ };
640
+
641
+ const { queryByTestId } = render(<SoluCXWidget {...props} />);
642
+
643
+ // Initially null
644
+ expect(queryByTestId("webview")).toBeNull();
645
+
646
+ await waitFor(() => {
647
+ expect(mockRequestWidgetUrl).toHaveBeenCalledTimes(1);
648
+ });
649
+
650
+ // Should remain null after failure
651
+ expect(queryByTestId("webview")).toBeNull();
652
+ expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(false);
653
+ });
654
+ });
@@ -156,27 +156,62 @@ describe('widgetBootstrapService', () => {
156
156
  expect(result).toBe('https://widget.url/test');
157
157
  });
158
158
 
159
- it('should return undefined when response is not ok', async () => {
159
+ it('should throw error when response is not ok', async () => {
160
160
  const mockResponse = {
161
161
  ok: false,
162
+ status: 404,
163
+ statusText: 'Not Found',
162
164
  };
163
165
  (global.fetch as jest.Mock).mockResolvedValue(mockResponse);
164
166
 
165
- const result = await requestWidgetUrl(
166
- 'api-key',
167
- { customer_id: 'cust' },
168
- 'user-id'
169
- );
167
+ await expect(
168
+ requestWidgetUrl('api-key', { customer_id: 'cust' }, 'user-id')
169
+ ).rejects.toThrow('Failed to fetch widget URL: 404 Not Found');
170
+ });
170
171
 
171
- expect(result).toBeUndefined();
172
+ it('should throw error with status 500', async () => {
173
+ const mockResponse = {
174
+ ok: false,
175
+ status: 500,
176
+ statusText: 'Internal Server Error',
177
+ };
178
+ (global.fetch as jest.Mock).mockResolvedValue(mockResponse);
179
+
180
+ await expect(
181
+ requestWidgetUrl('api-key', { customer_id: 'cust' }, 'user-id')
182
+ ).rejects.toThrow('Failed to fetch widget URL: 500 Internal Server Error');
172
183
  });
173
184
 
174
- it('should return undefined when fetch is not available', async () => {
185
+ it('should throw error when fetch is not available', async () => {
175
186
  (global as any).fetch = undefined;
176
187
 
177
- const result = await requestWidgetUrl('api-key', { customer_id: 'cust' }, 'user-id');
188
+ await expect(
189
+ requestWidgetUrl('api-key', { customer_id: 'cust' }, 'user-id')
190
+ ).rejects.toThrow('Fetch is not available');
191
+ });
192
+
193
+ it('should throw error when response payload does not contain url', async () => {
194
+ const mockResponse = {
195
+ ok: true,
196
+ json: jest.fn().mockResolvedValue({}),
197
+ };
198
+ (global.fetch as jest.Mock).mockResolvedValue(mockResponse);
199
+
200
+ await expect(
201
+ requestWidgetUrl('api-key', { customer_id: 'cust' }, 'user-id')
202
+ ).rejects.toThrow('Widget URL not found in response');
203
+ });
204
+
205
+ it('should throw error when response payload url is null', async () => {
206
+ const mockResponse = {
207
+ ok: true,
208
+ json: jest.fn().mockResolvedValue({ url: null }),
209
+ };
210
+ (global.fetch as jest.Mock).mockResolvedValue(mockResponse);
178
211
 
179
- expect(result).toBeUndefined();
212
+ await expect(
213
+ requestWidgetUrl('api-key', { customer_id: 'cust' }, 'user-id')
214
+ ).rejects.toThrow('Widget URL not found in response');
180
215
  });
181
216
  });
182
217
  });
@@ -50,18 +50,27 @@ export async function requestWidgetUrl(
50
50
  instanceKey: SoluCXKey,
51
51
  requestParams: WidgetData,
52
52
  userId: string,
53
- ): Promise<string | undefined> {
54
- if (typeof fetch !== "function") return;
53
+ ): Promise<string> {
54
+ if (typeof fetch !== "function") {
55
+ throw new Error("Fetch is not available");
56
+ }
55
57
 
56
58
  const params = buildRequestParams(requestParams);
57
59
  const url = `${RATING_FORM_ENDPOINT}?${params.toString()}`;
58
60
  const headers = buildRequestHeaders(instanceKey, userId);
59
61
  const response = await fetch(url, { method: "GET", headers });
60
62
 
61
- if (!response.ok) return;
63
+ if (!response.ok) {
64
+ throw new Error(`Failed to fetch widget URL: ${response.status} ${response.statusText}`);
65
+ }
62
66
 
63
67
  const payload: Payload = await response.json();
64
- return payload?.url;
68
+
69
+ if (!payload?.url) {
70
+ throw new Error("Widget URL not found in response");
71
+ }
72
+
73
+ return payload.url;
65
74
  }
66
75
 
67
76
  export { buildRequestParams };