@encatch/web-sdk 0.0.35 → 1.0.0-beta.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 (33) hide show
  1. package/README.md +140 -638
  2. package/dist/encatch.es.js +2 -0
  3. package/dist/encatch.es.js.map +1 -0
  4. package/dist/encatch.iife.js +2 -0
  5. package/dist/encatch.iife.js.map +1 -0
  6. package/dist/index.d.ts +191 -0
  7. package/package.json +32 -50
  8. package/dist-sdk/plugin/sdk/core-wrapper-BMvOyc0u.js +0 -22926
  9. package/dist-sdk/plugin/sdk/module-DC2Edddk.js +0 -481
  10. package/dist-sdk/plugin/sdk/module.js +0 -5
  11. package/dist-sdk/plugin/sdk/preview-sdk.html +0 -1182
  12. package/dist-sdk/plugin/sdk/vite.svg +0 -15
  13. package/dist-sdk/plugin/sdk/web-form-engine-core.css +0 -1
  14. package/index.d.ts +0 -207
  15. package/src/@types/encatch-type.ts +0 -111
  16. package/src/encatch-instance.ts +0 -161
  17. package/src/feedback-api-types.ts +0 -18
  18. package/src/hooks/useDevice.ts +0 -30
  19. package/src/hooks/useFeedbackInterval.ts +0 -71
  20. package/src/hooks/useFeedbackTriggers.ts +0 -342
  21. package/src/hooks/useFetchElligibleFeedbackConfiguration.ts +0 -363
  22. package/src/hooks/useFetchFeedbackConfigurationDetails.ts +0 -92
  23. package/src/hooks/usePageChangeTracker.ts +0 -88
  24. package/src/hooks/usePrepopulatedAnswers.ts +0 -123
  25. package/src/hooks/useRefineTextForm.ts +0 -55
  26. package/src/hooks/useSubmitFeedbackForm.ts +0 -134
  27. package/src/hooks/useUserSession.ts +0 -53
  28. package/src/module.tsx +0 -428
  29. package/src/store/formResponses.ts +0 -211
  30. package/src/utils/browser-details.ts +0 -36
  31. package/src/utils/duration-utils.ts +0 -158
  32. package/src/utils/feedback-frequency-storage.ts +0 -214
  33. package/src/utils/feedback-storage.ts +0 -166
@@ -1,18 +0,0 @@
1
- import { Answer } from "@encatch/schema";
2
-
3
- interface Duration {
4
- from: string;
5
- to: string;
6
- }
7
-
8
- interface TriggerProperties {
9
- screens: string[];
10
- }
11
-
12
- interface PrepopulatedAnswer {
13
- sectionIndex: number;
14
- questionIndex: number;
15
- value: Answer;
16
- }
17
-
18
- export type { Duration, TriggerProperties,PrepopulatedAnswer };
@@ -1,30 +0,0 @@
1
- import { generateUUID, getDeviceInfo } from "../utils/browser-details";
2
- import { DeviceInfo } from "@encatch/schema";
3
- import { useState, useEffect } from "preact/hooks";
4
-
5
- const DEVICE_STORAGE_KEY = "app_device";
6
-
7
- export const useDevice = () => {
8
- const [deviceId, setDeviceId] = useState<string | null>(null);
9
- const [deviceInfo, setDeviceInfo] = useState<DeviceInfo | null>(null);
10
-
11
- useEffect(() => {
12
- const storedId = localStorage.getItem(DEVICE_STORAGE_KEY);
13
- let currentDeviceId: string;
14
-
15
- if (storedId) {
16
- setDeviceId(storedId);
17
- currentDeviceId = storedId;
18
- } else {
19
- const newId = generateUUID();
20
- localStorage.setItem(DEVICE_STORAGE_KEY, newId);
21
- setDeviceId(newId);
22
- currentDeviceId = newId;
23
- }
24
- const userAgent = navigator.userAgent;
25
- const deviceInfo = getDeviceInfo(userAgent, currentDeviceId);
26
- setDeviceInfo(deviceInfo);
27
- }, []);
28
-
29
- return { deviceId, deviceInfo };
30
- };
@@ -1,71 +0,0 @@
1
- import { useState, useEffect } from "preact/hooks";
2
-
3
- const FEEDBACK_VISIBLE_KEY = "encatch_feedback_visible";
4
- const FEEDBACK_INTERVAL_KEY = "encatch_feedback_interval";
5
-
6
- export const useFeedbackInterval = () => {
7
- const [feedbackInterval, setFeedbackInterval] = useState<number>(0);
8
- const [isFeedbackVisible, setIsFeedbackVisible] = useState<boolean>(false);
9
- const [lastVisibleTime, setLastVisibleTime] = useState<number | null>(null);
10
-
11
- const canShowFeedbackModal = () => {
12
- const currentInterval = feedbackInterval;
13
- const lastVisible = lastVisibleTime;
14
-
15
- if (!lastVisible || !currentInterval) {
16
- return true;
17
- }
18
-
19
- const timeSinceLastVisible = Date.now() - lastVisible;
20
- const canShow = timeSinceLastVisible >= currentInterval;
21
-
22
- return canShow;
23
- };
24
-
25
- const setFeedbackVisible = (visible: boolean) => {
26
- setIsFeedbackVisible(visible);
27
- if (visible) {
28
- const timestamp = Date.now();
29
- setLastVisibleTime(timestamp);
30
- localStorage.setItem(FEEDBACK_VISIBLE_KEY, timestamp.toString());
31
- } else {
32
- setLastVisibleTime(null);
33
- localStorage.removeItem(FEEDBACK_VISIBLE_KEY);
34
- }
35
- };
36
-
37
- const updateFeedbackInterval = (interval: number) => {
38
- // Convert minutes to milliseconds (interval * 60 * 1000)
39
- const intervalInMs = interval * 60 * 1000;
40
- setFeedbackInterval(intervalInMs);
41
- localStorage.setItem(FEEDBACK_INTERVAL_KEY, intervalInMs.toString());
42
- };
43
-
44
- useEffect(() => {
45
- // Load stored interval
46
- const storedInterval = localStorage.getItem(FEEDBACK_INTERVAL_KEY);
47
- if (storedInterval) {
48
- const interval = parseInt(storedInterval);
49
- const intervalInMs = interval * 60 * 1000;
50
- setFeedbackInterval(intervalInMs);
51
- }
52
-
53
- // Check if feedback was recently shown
54
- const storedLastVisible = localStorage.getItem(FEEDBACK_VISIBLE_KEY);
55
- if (storedLastVisible) {
56
- const timestamp = parseInt(storedLastVisible);
57
- setLastVisibleTime(timestamp);
58
- const timeSinceLastVisible = Date.now() - timestamp;
59
- const currentInterval = feedbackInterval;
60
- const isVisible = timeSinceLastVisible < currentInterval;
61
- setIsFeedbackVisible(isVisible);
62
- }
63
- }, []);
64
-
65
- return {
66
- canShowFeedbackModal,
67
- setFeedbackVisible,
68
- setFeedbackInterval: updateFeedbackInterval,
69
- isFeedbackVisible
70
- };
71
- };
@@ -1,342 +0,0 @@
1
- import { useState, useRef, useCallback } from "preact/hooks";
2
- import {
3
- TriggerProperties as BaseTriggerProperties,
4
- } from "../feedback-api-types";
5
-
6
- import jsonLogic from "json-logic-js";
7
- import { match as pathMatch } from "path-to-regexp";
8
- import { FeedbackConfigurationItem, QueryOutput, TriggerAction } from "@encatch/schema";
9
-
10
- jsonLogic.add_operation("startsWith", (a: any, b: any) => {
11
- if (typeof a !== "string" || typeof b !== "string") return false;
12
- return a.startsWith(b);
13
- });
14
-
15
- jsonLogic.add_operation("regex", (a: any, b: any) => {
16
- if (typeof a !== "string" || typeof b !== "string") {
17
- return true;
18
- }
19
- return matchesDynamicRoute(a, b);
20
- });
21
- function matchesDynamicRoute(inputPath: string, routePattern: string): boolean {
22
- try {
23
- const matcher = pathMatch(routePattern, { decode: decodeURIComponent });
24
- const result = matcher(inputPath);
25
- return !!result;
26
- } catch (error) {
27
- console.error("[swat] pathMatch error", { inputPath, routePattern, error });
28
- return false;
29
- }
30
- }
31
- type TriggerConfig = {
32
- feedbackConfigurationId: string;
33
- eventName: string;
34
- jsonLogicField: any;
35
- thenAction: any;
36
- isImportant: boolean;
37
- };
38
-
39
- type WhenCondition = {
40
- if: {
41
- id: string;
42
- event: string;
43
- queryOutput: QueryOutput;
44
- };
45
- then: TriggerAction;
46
- };
47
-
48
- type Segment = {
49
- when: WhenCondition[];
50
- isImportant?: boolean;
51
- };
52
-
53
- interface ExtendedTriggerProperties extends BaseTriggerProperties {
54
- isAuto: boolean;
55
- segments: Segment[];
56
- }
57
-
58
- type QueuedEvent = {
59
- eventName: string;
60
- properties?: Record<string, any>;
61
- };
62
-
63
- export const useFeedbackTriggers = (
64
- onTriggerMatch?: (configId: string, event: string) => void
65
- ) => {
66
-
67
- const [pageLoadTriggers, setPageLoadTriggers] = useState<TriggerConfig[] | null>(null);
68
- const [feedbackConfig, setFeedbackConfig] = useState<FeedbackConfigurationItem[] | null>(null);
69
- const [customEventTriggers, setCustomEventTriggers] = useState<TriggerConfig[] | null>(null);
70
- const [eventQueue, setEventQueue] = useState<QueuedEvent[]>([]);
71
- const [canShowFeedbackModal, setCanShowFeedbackModal] = useState(false);
72
-
73
- const [activeFeedbackConfig, setActiveFeedbackConfig] = useState<{
74
- configId: string;
75
- isImportant: boolean;
76
- eventName: string;
77
- } | null>(null);
78
-
79
- const currentTimeoutIdRef = useRef<number | null>(null);
80
-
81
- // Store triggers in refs to avoid stale closure issues
82
- const pageLoadTriggersRef = useRef<TriggerConfig[] | null>(null);
83
- const customEventTriggersRef = useRef<TriggerConfig[] | null>(null);
84
-
85
- const showFeedbackByConfigId = (configId: string, event: string) => {
86
- if (onTriggerMatch) {
87
- onTriggerMatch(configId, event);
88
- }
89
- };
90
-
91
- /**
92
- * Exposes a function to manually trigger feedback modal based on scroll percentage.
93
- * Instead of calculating scroll, uses the scroll value from WhenCondition.
94
- * When the scroll percentage matches or exceeds, displays the modal.
95
- */
96
- const triggeredFeedbacksRef = useRef(new Set<string>());
97
-
98
- const capturePageScrollEvent = useCallback((scrollPercent: string) => {
99
- let percentNum: number;
100
-
101
- if (typeof scrollPercent === 'string') {
102
- percentNum = parseFloat(scrollPercent.replace('%', ''));
103
- } else {
104
- percentNum = scrollPercent;
105
- }
106
-
107
- const configs = feedbackConfig || [];
108
-
109
- for (const config of configs) {
110
- if (triggeredFeedbacksRef.current.has(config.feedbackConfigurationId)) continue;
111
-
112
- if (config.triggerProperties) {
113
- const triggerProps = config.triggerProperties ;
114
- for (const segment of triggerProps.segments) {
115
- for (const whenItem of segment.when) {
116
- let scrollValue = 0;
117
- if (typeof whenItem.then.scroll === 'string') {
118
- scrollValue = parseFloat(whenItem.then.scroll.replace('%', ''));
119
- } else {
120
- scrollValue = Number(whenItem.then.scroll);
121
- }
122
-
123
- if (
124
- whenItem.then.triggerType === "scroll" &&
125
- !isNaN(scrollValue) &&
126
- percentNum >= scrollValue
127
- ) {
128
- triggeredFeedbacksRef.current.add(config.feedbackConfigurationId);
129
- onTriggerMatch?.(config.feedbackConfigurationId, whenItem.if.event);
130
- return;
131
- }
132
- }
133
- }
134
- }
135
- }
136
- }, [feedbackConfig, onTriggerMatch]);
137
-
138
- const updateFeedbackConfigRef = useRef<(configs: FeedbackConfigurationItem[]) => void>();
139
-
140
- updateFeedbackConfigRef.current = (configs: FeedbackConfigurationItem[]) => {
141
- const pageLoadTriggersList: TriggerConfig[] = [];
142
- const customEventTriggersList: TriggerConfig[] = [];
143
-
144
- configs.forEach((config) => {
145
- if (config.triggerProperties) {
146
- const triggerProps =
147
- config.triggerProperties as unknown as ExtendedTriggerProperties;
148
- const { isAuto, segments } = triggerProps;
149
-
150
- if (isAuto && segments) {
151
- segments.forEach((seg: Segment) => {
152
- if (seg.when) {
153
- seg.when.forEach((whenItem: WhenCondition) => {
154
- if (whenItem.if) {
155
- const { event, queryOutput } = whenItem.if;
156
- let jsonLogicField: any = queryOutput.jsonLogic;
157
-
158
- // Parse jsonLogic if it's a string
159
- if (typeof jsonLogicField === "string") {
160
- if (jsonLogicField.trim() === "") {
161
- console.warn("Empty jsonLogic string received");
162
- jsonLogicField = null;
163
- } else {
164
- try {
165
- jsonLogicField = JSON.parse(jsonLogicField);
166
- } catch (e) {
167
- console.warn("Failed to parse jsonLogic string:", jsonLogicField, e);
168
- jsonLogicField = null;
169
- }
170
- }
171
- }
172
-
173
- // Skip if jsonLogic is null, undefined, or an empty object
174
- if (!jsonLogicField || (typeof jsonLogicField === 'object' && Object.keys(jsonLogicField).length === 0)) {
175
- return;
176
- }
177
-
178
- const triggerConfig: TriggerConfig = {
179
- feedbackConfigurationId: config.feedbackConfigurationId,
180
- eventName: event,
181
- jsonLogicField,
182
- thenAction: whenItem.then,
183
- isImportant: seg.isImportant ?? false,
184
- };
185
-
186
- if (event === "pageLoad") {
187
- pageLoadTriggersList.push(triggerConfig);
188
- } else {
189
- customEventTriggersList.push(triggerConfig);
190
- }
191
- }
192
- });
193
- }
194
- });
195
- }
196
- }
197
- });
198
-
199
- // Update refs immediately so processTrigger can access fresh values
200
- pageLoadTriggersRef.current = pageLoadTriggersList;
201
- customEventTriggersRef.current = customEventTriggersList;
202
-
203
- setPageLoadTriggers(pageLoadTriggersList);
204
- setCustomEventTriggers(customEventTriggersList);
205
- setFeedbackConfig(configs);
206
-
207
- // Process any queued events now that we have the configuration
208
- const queue = eventQueue;
209
- if (queue.length > 0) {
210
- queue.forEach(event => {
211
- processTriggerRef.current?.(event.eventName, event.properties);
212
- });
213
- setEventQueue([]); // Clear the queue after processing
214
- }
215
- };
216
-
217
- const processTriggerRef = useRef<(eventName: string, properties?: Record<string, any>) => void>();
218
-
219
- processTriggerRef.current = (
220
- eventName: string,
221
- properties?: Record<string, any>
222
- ) => {
223
- let identifiedTrigger: TriggerConfig | undefined;
224
-
225
- if (eventName === "pageLoad") {
226
- const pageTriggers = pageLoadTriggersRef.current;
227
- if (pageTriggers) {
228
- identifiedTrigger = pageTriggers.find((trigger) => {
229
- let jsonLogicResult = false;
230
- try {
231
- const props = properties || {};
232
- jsonLogicResult = jsonLogic.apply(trigger.jsonLogicField, props);
233
- } catch (e) {
234
- console.error('[swat] Error applying jsonLogic for pageLoad trigger:', {
235
- configId: trigger.feedbackConfigurationId,
236
- jsonLogicField: trigger.jsonLogicField,
237
- error: e
238
- });
239
- jsonLogicResult = false;
240
- }
241
- const match = trigger.eventName === eventName && jsonLogicResult;
242
- return match;
243
- });
244
- }
245
- } else {
246
- const customTriggers = customEventTriggersRef.current;
247
- if (customTriggers) {
248
- identifiedTrigger = customTriggers.find((trigger) => {
249
- const eventMatches = trigger.eventName === eventName;
250
- let jsonLogicResult = false;
251
- try {
252
- jsonLogicResult = jsonLogic.apply(trigger.jsonLogicField, properties || {});
253
- } catch (e) {
254
- console.error('[swat] Error applying jsonLogic for custom event trigger:', {
255
- configId: trigger.feedbackConfigurationId,
256
- eventName: trigger.eventName,
257
- jsonLogicField: trigger.jsonLogicField,
258
- error: e
259
- });
260
- jsonLogicResult = false;
261
- }
262
- const match = eventMatches && jsonLogicResult;
263
- return match;
264
- });
265
- }
266
- }
267
-
268
- if (identifiedTrigger) {
269
- // Allow delay triggers to bypass modal restrictions, but block other non-important triggers
270
- const isDelayTrigger = identifiedTrigger.thenAction.triggerType === "delay";
271
- if (!identifiedTrigger.isImportant && !canShowFeedbackModal && !isDelayTrigger) {
272
- return;
273
- }
274
-
275
- const { triggerType, delay, scroll } = identifiedTrigger.thenAction;
276
- if (activeFeedbackConfig && activeFeedbackConfig?.isImportant && identifiedTrigger.isImportant) {
277
- setActiveFeedbackConfig({
278
- configId: identifiedTrigger.feedbackConfigurationId,
279
- isImportant: identifiedTrigger.isImportant,
280
- eventName: identifiedTrigger.eventName
281
- });
282
- } else if (
283
- activeFeedbackConfig &&
284
- activeFeedbackConfig?.isImportant &&
285
- !identifiedTrigger.isImportant
286
- ) {
287
- return;
288
- }
289
-
290
- if (currentTimeoutIdRef.current !== null) {
291
- clearTimeout(currentTimeoutIdRef.current);
292
- currentTimeoutIdRef.current = null;
293
- }
294
-
295
- if (triggerType === "immediately") {
296
- showFeedbackByConfigId(identifiedTrigger.feedbackConfigurationId, identifiedTrigger.eventName);
297
- setActiveFeedbackConfig(null);
298
- } else if (triggerType === "delay") {
299
- const delayInMs = delay * 1000; // Convert seconds to milliseconds
300
- setActiveFeedbackConfig({
301
- configId: identifiedTrigger.feedbackConfigurationId,
302
- isImportant: identifiedTrigger.isImportant,
303
- eventName: identifiedTrigger.eventName
304
- });
305
- currentTimeoutIdRef.current = window.setTimeout(() => {
306
- showFeedbackByConfigId(identifiedTrigger.feedbackConfigurationId, identifiedTrigger.eventName);
307
- setActiveFeedbackConfig(null);
308
- currentTimeoutIdRef.current = null;
309
- }, delayInMs);
310
- }
311
- }
312
- };
313
-
314
- const invokeTrigger = (
315
- eventName: string,
316
- properties?: Record<string, any>,
317
- canShowFeedbackModal?: boolean
318
- ) => {
319
- setCanShowFeedbackModal(canShowFeedbackModal ?? true);
320
-
321
- // If feedback configuration is not loaded yet, queue the event
322
- if (!feedbackConfig) {
323
- setEventQueue([...eventQueue, { eventName, properties }]);
324
- return;
325
- }
326
-
327
- // If configuration is loaded, process the trigger
328
- processTriggerRef.current?.(eventName, properties);
329
- };
330
-
331
- const updateFeedbackConfig = (configs: FeedbackConfigurationItem[]) => {
332
- updateFeedbackConfigRef.current?.(configs);
333
- };
334
-
335
- return {
336
- updateFeedbackConfig,
337
- invokeTrigger,
338
- pageLoadTriggers,
339
- customEventTriggers,
340
- capturePageScrollEvent,
341
- };
342
- };