@encatch/react-native-sdk 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.
package/dist/index.js ADDED
@@ -0,0 +1,1910 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
9
+ var __getProtoOf = Object.getPrototypeOf;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
12
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
13
+ var __spreadValues = (a, b) => {
14
+ for (var prop in b || (b = {}))
15
+ if (__hasOwnProp.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ if (__getOwnPropSymbols)
18
+ for (var prop of __getOwnPropSymbols(b)) {
19
+ if (__propIsEnum.call(b, prop))
20
+ __defNormalProp(a, prop, b[prop]);
21
+ }
22
+ return a;
23
+ };
24
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
25
+ var __export = (target, all) => {
26
+ for (var name in all)
27
+ __defProp(target, name, { get: all[name], enumerable: true });
28
+ };
29
+ var __copyProps = (to, from, except, desc) => {
30
+ if (from && typeof from === "object" || typeof from === "function") {
31
+ for (let key of __getOwnPropNames(from))
32
+ if (!__hasOwnProp.call(to, key) && key !== except)
33
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
34
+ }
35
+ return to;
36
+ };
37
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
38
+ // If the importer is in node compatibility mode or this is not an ESM
39
+ // file that has been converted to a CommonJS file using a Babel-
40
+ // compatible transform (i.e. "__esModule" has not been set), then set
41
+ // "default" to the CommonJS "module.exports" for node compatibility.
42
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
43
+ mod
44
+ ));
45
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
46
+
47
+ // src/index.ts
48
+ var index_exports = {};
49
+ __export(index_exports, {
50
+ Encatch: () => Encatch,
51
+ EncatchProvider: () => EncatchProvider,
52
+ EncatchWebView: () => EncatchWebView,
53
+ buildSubmitRequest: () => buildSubmitRequest,
54
+ useEncatch: () => useEncatch
55
+ });
56
+ module.exports = __toCommonJS(index_exports);
57
+
58
+ // src/emitter.ts
59
+ var TypedEmitter = class {
60
+ constructor() {
61
+ this._listeners = {};
62
+ }
63
+ on(event, listener) {
64
+ if (!this._listeners[event]) {
65
+ this._listeners[event] = [];
66
+ }
67
+ this._listeners[event].push(listener);
68
+ }
69
+ off(event, listener) {
70
+ const listeners = this._listeners[event];
71
+ if (!listeners) return;
72
+ const idx = listeners.indexOf(listener);
73
+ if (idx !== -1) listeners.splice(idx, 1);
74
+ }
75
+ emit(event, payload) {
76
+ const listeners = this._listeners[event];
77
+ if (!listeners) return;
78
+ for (const listener of [...listeners]) {
79
+ try {
80
+ listener(payload);
81
+ } catch (e) {
82
+ }
83
+ }
84
+ }
85
+ removeAllListeners(event) {
86
+ if (event) {
87
+ this._listeners[event] = [];
88
+ } else {
89
+ this._listeners = {};
90
+ }
91
+ }
92
+ };
93
+
94
+ // src/storage.ts
95
+ var import_async_storage = __toESM(require("@react-native-async-storage/async-storage"));
96
+ var import_uuidv7 = require("uuidv7");
97
+ var KEY_DEVICE_ID = "@encatch/device_id";
98
+ var KEY_USER_NAME = "@encatch/user_name";
99
+ var KEY_USER_ID_PREFIX = "@encatch/user_id_";
100
+ var KEY_FT_PREFIX = "@encatch/ft_";
101
+ var KEY_PREFERENCES = "@encatch/preferences";
102
+ async function getOrCreateDeviceId() {
103
+ try {
104
+ const stored = await import_async_storage.default.getItem(KEY_DEVICE_ID);
105
+ if (stored) return stored;
106
+ const id = (0, import_uuidv7.uuidv7)();
107
+ await import_async_storage.default.setItem(KEY_DEVICE_ID, id);
108
+ return id;
109
+ } catch (e) {
110
+ return (0, import_uuidv7.uuidv7)();
111
+ }
112
+ }
113
+ var inMemorySessionId = null;
114
+ async function getOrCreateSessionId() {
115
+ if (inMemorySessionId) return inMemorySessionId;
116
+ inMemorySessionId = (0, import_uuidv7.uuidv7)();
117
+ return inMemorySessionId;
118
+ }
119
+ async function clearSession() {
120
+ inMemorySessionId = null;
121
+ }
122
+ async function getUserName() {
123
+ try {
124
+ return await import_async_storage.default.getItem(KEY_USER_NAME);
125
+ } catch (e) {
126
+ return null;
127
+ }
128
+ }
129
+ async function setUserName(name) {
130
+ try {
131
+ await import_async_storage.default.setItem(KEY_USER_NAME, name);
132
+ } catch (e) {
133
+ }
134
+ }
135
+ async function clearUserName() {
136
+ try {
137
+ await import_async_storage.default.removeItem(KEY_USER_NAME);
138
+ } catch (e) {
139
+ }
140
+ }
141
+ async function getUserId(userName) {
142
+ try {
143
+ return await import_async_storage.default.getItem(`${KEY_USER_ID_PREFIX}${userName}`);
144
+ } catch (e) {
145
+ return null;
146
+ }
147
+ }
148
+ async function setUserId(userName, userId) {
149
+ try {
150
+ await import_async_storage.default.setItem(`${KEY_USER_ID_PREFIX}${userName}`, userId);
151
+ } catch (e) {
152
+ }
153
+ }
154
+ async function clearUserId(userName) {
155
+ try {
156
+ await import_async_storage.default.removeItem(`${KEY_USER_ID_PREFIX}${userName}`);
157
+ } catch (e) {
158
+ }
159
+ }
160
+ function ftKey(identityKey) {
161
+ return `${KEY_FT_PREFIX}${identityKey}`;
162
+ }
163
+ async function getFeedbackTransactions(identityKey) {
164
+ try {
165
+ return await import_async_storage.default.getItem(ftKey(identityKey));
166
+ } catch (e) {
167
+ return null;
168
+ }
169
+ }
170
+ async function setFeedbackTransactions(identityKey, value) {
171
+ try {
172
+ await import_async_storage.default.setItem(ftKey(identityKey), value);
173
+ } catch (e) {
174
+ }
175
+ }
176
+ async function clearFeedbackTransactions(identityKey) {
177
+ try {
178
+ await import_async_storage.default.removeItem(ftKey(identityKey));
179
+ } catch (e) {
180
+ }
181
+ }
182
+ async function getPreferences() {
183
+ try {
184
+ const raw = await import_async_storage.default.getItem(KEY_PREFERENCES);
185
+ return raw ? JSON.parse(raw) : {};
186
+ } catch (e) {
187
+ return {};
188
+ }
189
+ }
190
+ async function setPreferences(updates) {
191
+ try {
192
+ const current = await getPreferences();
193
+ await import_async_storage.default.setItem(KEY_PREFERENCES, JSON.stringify(__spreadValues(__spreadValues({}, current), updates)));
194
+ } catch (e) {
195
+ }
196
+ }
197
+ async function clearPreferences() {
198
+ try {
199
+ await import_async_storage.default.removeItem(KEY_PREFERENCES);
200
+ } catch (e) {
201
+ }
202
+ }
203
+
204
+ // src/device-info.ts
205
+ var import_react_native = require("react-native");
206
+ async function getDeviceLocale() {
207
+ var _a, _b, _c, _d;
208
+ try {
209
+ const Localization = require("expo-localization");
210
+ const locales = (_a = Localization.getLocales) == null ? void 0 : _a.call(Localization);
211
+ if (Array.isArray(locales) && locales.length > 0 && ((_b = locales[0]) == null ? void 0 : _b.languageTag)) {
212
+ return locales[0].languageTag;
213
+ }
214
+ } catch (e) {
215
+ }
216
+ try {
217
+ const RNLocalize = require("react-native-localize");
218
+ const locales = (_c = RNLocalize.getLocales) == null ? void 0 : _c.call(RNLocalize);
219
+ if (Array.isArray(locales) && locales.length > 0 && ((_d = locales[0]) == null ? void 0 : _d.languageTag)) {
220
+ return locales[0].languageTag;
221
+ }
222
+ } catch (e) {
223
+ }
224
+ return "en";
225
+ }
226
+ function getOsVersion() {
227
+ const v = import_react_native.Platform.Version;
228
+ if (typeof v === "number") return String(v);
229
+ return v != null ? v : "unknown";
230
+ }
231
+ async function getAppVersion() {
232
+ var _a;
233
+ try {
234
+ const Application = require("expo-application");
235
+ const v = Application.nativeApplicationVersion;
236
+ if (v && typeof v === "string") return v;
237
+ } catch (e) {
238
+ }
239
+ try {
240
+ const DeviceInfo = require("react-native-device-info");
241
+ const v = (_a = DeviceInfo.getVersion) == null ? void 0 : _a.call(DeviceInfo);
242
+ if (v && typeof v === "string") return v;
243
+ } catch (e) {
244
+ }
245
+ return null;
246
+ }
247
+ async function getAppPackageId() {
248
+ var _a, _b, _c;
249
+ let isExpo = false;
250
+ try {
251
+ const Device = require("expo-device");
252
+ isExpo = typeof Device.getDeviceTypeAsync === "function";
253
+ } catch (e) {
254
+ }
255
+ if (isExpo) {
256
+ try {
257
+ const Application = require("expo-application");
258
+ let id = Application.applicationId;
259
+ if (!id && typeof Application.getApplicationIdAsync === "function") {
260
+ id = await Application.getApplicationIdAsync();
261
+ }
262
+ if (id && typeof id === "string") return id;
263
+ } catch (e) {
264
+ }
265
+ try {
266
+ const Constants = require("expo-constants");
267
+ const env = (_b = (_a = Constants == null ? void 0 : Constants.default) == null ? void 0 : _a.executionEnvironment) != null ? _b : Constants == null ? void 0 : Constants.executionEnvironment;
268
+ if (env === "storeClient") return "expo-go";
269
+ } catch (e) {
270
+ }
271
+ return "expo-go";
272
+ }
273
+ try {
274
+ const DeviceInfo = require("react-native-device-info");
275
+ const id = (_c = DeviceInfo.getBundleId) == null ? void 0 : _c.call(DeviceInfo);
276
+ if (id && typeof id === "string") return id;
277
+ } catch (e) {
278
+ }
279
+ return null;
280
+ }
281
+ function getTimezone() {
282
+ try {
283
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
284
+ if (tz && typeof tz === "string") return tz;
285
+ } catch (e) {
286
+ }
287
+ return null;
288
+ }
289
+ function getPlatform() {
290
+ if (import_react_native.Platform.OS === "ios") return "ios";
291
+ if (import_react_native.Platform.OS === "android") return "android";
292
+ return "web";
293
+ }
294
+ function getDeviceTypeEnv() {
295
+ return getPlatform() === "web" ? "web" : "native";
296
+ }
297
+ function getDeviceSize() {
298
+ const g = globalThis;
299
+ if (typeof g.window === "undefined" || typeof g.window.innerWidth !== "number") {
300
+ return void 0;
301
+ }
302
+ const w = g.window.innerWidth;
303
+ if (w < 768) return "mobile";
304
+ if (w < 1024) return "tablet";
305
+ return "desktop";
306
+ }
307
+
308
+ // src/retry-queue.ts
309
+ var import_react_native2 = require("react-native");
310
+ var import_async_storage2 = __toESM(require("@react-native-async-storage/async-storage"));
311
+ var QUEUE_KEY = "@encatch/retry_queue";
312
+ var MAX_RETRIES = 3;
313
+ var BASE_BACKOFF_MS = 1e3;
314
+ var queue = [];
315
+ function isClientError(err) {
316
+ const msg = err instanceof Error ? err.message : String(err);
317
+ const match = msg.match(/status (\d+)/);
318
+ if (!match) return false;
319
+ const status = parseInt(match[1], 10);
320
+ return status >= 400 && status < 500;
321
+ }
322
+ function backoffMs(retries) {
323
+ return BASE_BACKOFF_MS * Math.pow(2, retries);
324
+ }
325
+ async function persistQueue() {
326
+ try {
327
+ const serializable = queue.map((item) => ({
328
+ id: item.id,
329
+ retries: item.retries,
330
+ maxRetries: item.maxRetries,
331
+ createdAt: item.createdAt,
332
+ label: item.label
333
+ }));
334
+ await import_async_storage2.default.setItem(QUEUE_KEY, JSON.stringify(serializable));
335
+ } catch (e) {
336
+ }
337
+ }
338
+ async function removeFromPersisted(id) {
339
+ try {
340
+ const raw = await import_async_storage2.default.getItem(QUEUE_KEY);
341
+ if (!raw) return;
342
+ const items = JSON.parse(raw);
343
+ const filtered = items.filter((i) => i.id !== id);
344
+ await import_async_storage2.default.setItem(QUEUE_KEY, JSON.stringify(filtered));
345
+ } catch (e) {
346
+ }
347
+ }
348
+ function enqueue(label, fn, maxRetries = MAX_RETRIES) {
349
+ const item = {
350
+ id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
351
+ fn,
352
+ retries: 0,
353
+ maxRetries,
354
+ createdAt: Date.now(),
355
+ label
356
+ };
357
+ queue.push(item);
358
+ persistQueue();
359
+ }
360
+ async function flush() {
361
+ if (queue.length === 0) return;
362
+ const snapshot = [...queue];
363
+ for (const item of snapshot) {
364
+ try {
365
+ await item.fn();
366
+ const idx = queue.findIndex((q) => q.id === item.id);
367
+ if (idx !== -1) queue.splice(idx, 1);
368
+ await removeFromPersisted(item.id);
369
+ } catch (err) {
370
+ if (isClientError(err)) {
371
+ const idx = queue.findIndex((q) => q.id === item.id);
372
+ if (idx !== -1) queue.splice(idx, 1);
373
+ await removeFromPersisted(item.id);
374
+ console.warn(`[Encatch] Retry queue: dropping "${item.label}" (client error, no retry)`, err);
375
+ return;
376
+ }
377
+ item.retries += 1;
378
+ if (item.retries >= item.maxRetries) {
379
+ const idx = queue.findIndex((q) => q.id === item.id);
380
+ if (idx !== -1) queue.splice(idx, 1);
381
+ await removeFromPersisted(item.id);
382
+ console.warn(`[Encatch] Retry queue: dropping "${item.label}" after ${item.maxRetries} retries`, err);
383
+ } else {
384
+ const delay = backoffMs(item.retries);
385
+ setTimeout(() => flushSingle(item.id), delay);
386
+ await persistQueue();
387
+ }
388
+ }
389
+ }
390
+ }
391
+ async function flushSingle(id) {
392
+ const item = queue.find((q) => q.id === id);
393
+ if (!item) return;
394
+ try {
395
+ await item.fn();
396
+ const idx = queue.findIndex((q) => q.id === id);
397
+ if (idx !== -1) queue.splice(idx, 1);
398
+ await removeFromPersisted(id);
399
+ } catch (err) {
400
+ if (isClientError(err)) {
401
+ const idx = queue.findIndex((q) => q.id === id);
402
+ if (idx !== -1) queue.splice(idx, 1);
403
+ await removeFromPersisted(id);
404
+ console.warn(`[Encatch] Retry queue: dropping "${item.label}" (client error, no retry)`, err);
405
+ return;
406
+ }
407
+ item.retries += 1;
408
+ if (item.retries >= item.maxRetries) {
409
+ const idx = queue.findIndex((q) => q.id === id);
410
+ if (idx !== -1) queue.splice(idx, 1);
411
+ await removeFromPersisted(id);
412
+ console.warn(`[Encatch] Retry queue: dropping "${item.label}" after ${item.maxRetries} retries`, err);
413
+ } else {
414
+ const delay = backoffMs(item.retries);
415
+ setTimeout(() => flushSingle(id), delay);
416
+ await persistQueue();
417
+ }
418
+ }
419
+ }
420
+ var appStateSubscription = null;
421
+ function startAppStateListener() {
422
+ if (appStateSubscription) return;
423
+ const handleAppStateChange = (nextState) => {
424
+ if (nextState === "active") {
425
+ flush().catch(() => {
426
+ });
427
+ }
428
+ };
429
+ appStateSubscription = import_react_native2.AppState.addEventListener("change", handleAppStateChange);
430
+ }
431
+ function stopAppStateListener() {
432
+ if (appStateSubscription) {
433
+ appStateSubscription.remove();
434
+ appStateSubscription = null;
435
+ }
436
+ }
437
+
438
+ // src/logger.ts
439
+ var noop = () => {
440
+ };
441
+ function createFallbackLogger(debugMode) {
442
+ return {
443
+ debug: debugMode ? (...args) => console.log("[Encatch]", ...args) : noop,
444
+ warn: (...args) => console.warn("[Encatch]", ...args)
445
+ };
446
+ }
447
+ function prettyStringify(msg) {
448
+ if (msg === null) return "null";
449
+ if (msg === void 0) return "undefined";
450
+ if (typeof msg === "string") {
451
+ try {
452
+ const parsed = JSON.parse(msg);
453
+ return JSON.stringify(parsed, null, 2);
454
+ } catch (e) {
455
+ return msg;
456
+ }
457
+ }
458
+ if (typeof msg === "object") {
459
+ return JSON.stringify(msg, null, 2);
460
+ }
461
+ return String(msg);
462
+ }
463
+ function createEncatchLogger(debugMode) {
464
+ try {
465
+ const { logger, consoleTransport } = require("react-native-logs");
466
+ const encatchLog = logger.createLogger({
467
+ levels: { debug: 0, info: 1, warn: 2, error: 3 },
468
+ severity: debugMode ? "debug" : "warn",
469
+ transport: consoleTransport,
470
+ transportOptions: {
471
+ colors: {
472
+ debug: "cyan",
473
+ info: "blueBright",
474
+ warn: "yellowBright",
475
+ error: "redBright"
476
+ }
477
+ },
478
+ stringifyFunc: (msg) => {
479
+ if (Array.isArray(msg)) {
480
+ return msg.map((m) => prettyStringify(m)).join("\n");
481
+ }
482
+ return prettyStringify(msg);
483
+ },
484
+ dateFormat: "time",
485
+ printLevel: true,
486
+ printDate: true,
487
+ enabled: true
488
+ });
489
+ const ext = encatchLog.extend("Encatch");
490
+ return {
491
+ debug: (...args) => ext.debug(...args),
492
+ warn: (...args) => ext.warn(...args)
493
+ };
494
+ } catch (e) {
495
+ return createFallbackLogger(debugMode);
496
+ }
497
+ }
498
+
499
+ // src/encatch.ts
500
+ var SDK_VERSION = "2.0.0";
501
+ var ENDPOINTS = {
502
+ IDENTIFY_USER: "engage-product/encatch/api/v2/encatch/identify-user",
503
+ TRACK_EVENT: "engage-product/encatch/api/v2/encatch/track-event",
504
+ TRACK_SCREEN: "engage-product/encatch/api/v2/encatch/track-screen",
505
+ SHOW_FORM: "engage-product/encatch/api/v2/encatch/show-form",
506
+ DISMISS_FORM: "engage-product/encatch/api/v2/encatch/dismiss-form",
507
+ PING: "engage-product/encatch/api/v2/encatch/ping",
508
+ REFINE_TEXT: "engage-product/encatch/api/v2/encatch/refine-text",
509
+ SUBMIT_FORM: "engage-product/encatch/api/v2/encatch/submit-form"
510
+ };
511
+ var _internalEmitter = new TypedEmitter();
512
+ var EncatchSDK = class {
513
+ constructor() {
514
+ // Initialisation state
515
+ this._initialized = false;
516
+ this._debugMode = false;
517
+ // Config
518
+ this._apiKey = null;
519
+ this._apiBaseUrl = "https://app.encatch.com";
520
+ this._webHost = "https://app.encatch.com";
521
+ this._isFullScreen = false;
522
+ // Identity
523
+ this._userName = null;
524
+ this._userId = null;
525
+ this._userSignature = null;
526
+ // Preferences
527
+ this._locale = null;
528
+ this._country = null;
529
+ this._theme = "system";
530
+ // Current screen (updated by EncatchProvider / trackScreen)
531
+ this._currentScreen = null;
532
+ // Async-loaded ids (populated after init)
533
+ this._deviceId = null;
534
+ this._sessionId = null;
535
+ // Feedback transactions (persisted opaque string returned by API)
536
+ this._feedbackTransactions = null;
537
+ // Ping interval
538
+ this._pingIntervalId = null;
539
+ this._pingTimeoutId = null;
540
+ this._pingIntervalMs = 3e4;
541
+ this._isPingActive = false;
542
+ // Whether a form is currently visible (suppresses ping)
543
+ this._isFormVisible = false;
544
+ // App version (can be set by consumer)
545
+ this._appVersion = "1.0.0";
546
+ this._appPackageName = null;
547
+ // Event callbacks (external SDK consumers via Encatch.on())
548
+ this._eventCallbacks = [];
549
+ // Interceptor (optional — called before showing any form)
550
+ this._onBeforeShowForm = void 0;
551
+ // Logger (uses react-native-logs when debugMode and package installed)
552
+ this._logger = createEncatchLogger(false);
553
+ // ============================================================================
554
+ // Form response helpers
555
+ // ============================================================================
556
+ // Pre-filled responses stored by addToResponse, sent with sdk:prefillResponses
557
+ this._pendingResponses = {};
558
+ }
559
+ // ============================================================================
560
+ // Initialisation
561
+ // ============================================================================
562
+ async init(apiKey, config) {
563
+ var _a, _b, _c, _d, _e, _f;
564
+ this._debugMode = (_a = config == null ? void 0 : config.debugMode) != null ? _a : false;
565
+ this._logger = createEncatchLogger(this._debugMode);
566
+ if (this._initialized) {
567
+ this._logger.debug("SDK already initialized");
568
+ return;
569
+ }
570
+ this._apiKey = apiKey;
571
+ const defaultHost = "https://app.encatch.com";
572
+ this._apiBaseUrl = ((_b = config == null ? void 0 : config.apiBaseUrl) != null ? _b : defaultHost).replace(/\/+$/, "");
573
+ this._webHost = ((_c = config == null ? void 0 : config.webHost) != null ? _c : this._apiBaseUrl).replace(/\/+$/, "");
574
+ this._isFullScreen = (_d = config == null ? void 0 : config.isFullScreen) != null ? _d : false;
575
+ if (config == null ? void 0 : config.theme) {
576
+ this._theme = config.theme;
577
+ }
578
+ this._onBeforeShowForm = config == null ? void 0 : config.onBeforeShowForm;
579
+ this._logger.debug("Initializing SDK...");
580
+ const [storedName, deviceId, sessionId, prefs, appPackageId, appVersion] = await Promise.all([
581
+ getUserName(),
582
+ getOrCreateDeviceId(),
583
+ getOrCreateSessionId(),
584
+ getPreferences(),
585
+ getAppPackageId(),
586
+ getAppVersion()
587
+ ]);
588
+ this._deviceId = deviceId;
589
+ this._sessionId = sessionId;
590
+ this._appPackageName = appPackageId != null ? appPackageId : null;
591
+ this._appVersion = (_f = (_e = config == null ? void 0 : config.appVersion) != null ? _e : appVersion) != null ? _f : "1.0.0";
592
+ if (prefs.locale != null) this._locale = prefs.locale;
593
+ if (prefs.country != null) this._country = prefs.country;
594
+ if (storedName) {
595
+ this._userName = storedName;
596
+ this._userId = await getUserId(storedName);
597
+ this._feedbackTransactions = await getFeedbackTransactions(storedName);
598
+ } else {
599
+ this._feedbackTransactions = await getFeedbackTransactions("anonymous");
600
+ }
601
+ this._initialized = true;
602
+ startAppStateListener();
603
+ flush().catch(() => {
604
+ });
605
+ this._logger.debug("SDK initialized. deviceId:", deviceId);
606
+ }
607
+ // ============================================================================
608
+ // Identity
609
+ // ============================================================================
610
+ async identifyUser(userName, traits, options) {
611
+ var _a, _b, _c, _d, _e;
612
+ if (!this._initialized) return;
613
+ this._userName = userName;
614
+ await setUserName(userName);
615
+ if ((options == null ? void 0 : options.locale) != null) {
616
+ this._locale = options.locale;
617
+ setPreferences({ locale: options.locale }).catch(() => {
618
+ });
619
+ }
620
+ if ((options == null ? void 0 : options.country) != null) {
621
+ this._country = options.country;
622
+ setPreferences({ country: options.country }).catch(() => {
623
+ });
624
+ }
625
+ const convertDates = (obj) => {
626
+ if (!obj) return void 0;
627
+ return Object.keys(obj).reduce((acc, k) => {
628
+ acc[k] = obj[k] instanceof Date ? obj[k].toISOString() : obj[k];
629
+ return acc;
630
+ }, {});
631
+ };
632
+ const userAttributes = traits ? __spreadProps(__spreadValues({}, traits), {
633
+ $set: convertDates(traits.$set),
634
+ $setOnce: convertDates(traits.$setOnce)
635
+ }) : void 0;
636
+ const deviceInfo = await this._buildDeviceInfo();
637
+ const req = {
638
+ userName,
639
+ userId: (_a = this._userId) != null ? _a : void 0,
640
+ userSignature: (_d = (_c = (_b = options == null ? void 0 : options.secure) == null ? void 0 : _b.signature) != null ? _c : this._userSignature) != null ? _d : void 0,
641
+ $deviceInfo: deviceInfo,
642
+ userAttributes,
643
+ $feedbackTransactions: (_e = this._feedbackTransactions) != null ? _e : void 0
644
+ };
645
+ enqueue("identifyUser", async () => {
646
+ var _a2;
647
+ const res = await this._post(ENDPOINTS.IDENTIFY_USER, req, {
648
+ signatureTime: (_a2 = options == null ? void 0 : options.secure) == null ? void 0 : _a2.generatedDateTimeinUTC
649
+ });
650
+ if (res.userId) {
651
+ this._userId = res.userId;
652
+ await setUserId(userName, res.userId);
653
+ }
654
+ if (res.$feedbackTransactions) {
655
+ this._feedbackTransactions = res.$feedbackTransactions;
656
+ await setFeedbackTransactions(userName, res.$feedbackTransactions);
657
+ }
658
+ this._handleResponseMeta(res);
659
+ _internalEmitter.emit("userIdentified", {
660
+ userName: this._userName,
661
+ userId: this._userId
662
+ });
663
+ await this.startSession({ skipImmediatePing: true, skipImmediateTrackScreen: true });
664
+ if (typeof res.pingAgainIn === "number" && res.pingAgainIn > 0) {
665
+ this._scheduleNextPing(res.pingAgainIn * 1e3);
666
+ }
667
+ if (res.formConfigurationId) {
668
+ this._showFormById(res.formConfigurationId, { triggerType: "automatic" });
669
+ }
670
+ });
671
+ flush().catch(() => {
672
+ });
673
+ }
674
+ // ============================================================================
675
+ // Preferences
676
+ // ============================================================================
677
+ setLocale(locale) {
678
+ this._locale = locale;
679
+ setPreferences({ locale }).catch(() => {
680
+ });
681
+ }
682
+ setCountry(country) {
683
+ this._country = country;
684
+ setPreferences({ country }).catch(() => {
685
+ });
686
+ }
687
+ setTheme(theme) {
688
+ this._theme = theme;
689
+ }
690
+ // ============================================================================
691
+ // Event tracking
692
+ // ============================================================================
693
+ async trackEvent(eventName) {
694
+ var _a;
695
+ if (!this._initialized) return;
696
+ if (this._isFullScreen) return;
697
+ const deviceInfo = await this._buildDeviceInfo();
698
+ const req = {
699
+ eventName,
700
+ $deviceInfo: deviceInfo,
701
+ $feedbackTransactions: (_a = this._feedbackTransactions) != null ? _a : void 0
702
+ };
703
+ enqueue("trackEvent", async () => {
704
+ var _a2;
705
+ const res = await this._post(ENDPOINTS.TRACK_EVENT, req);
706
+ if (res.$feedbackTransactions) {
707
+ this._feedbackTransactions = res.$feedbackTransactions;
708
+ const key = (_a2 = this._userName) != null ? _a2 : "anonymous";
709
+ await setFeedbackTransactions(key, res.$feedbackTransactions);
710
+ }
711
+ this._handleResponseMeta(res);
712
+ if (res.formConfigurationId) {
713
+ this._showFormById(res.formConfigurationId, { triggerType: "automatic" });
714
+ }
715
+ });
716
+ flush().catch(() => {
717
+ });
718
+ }
719
+ /**
720
+ * Best-effort server call for form lifecycle events (form:show, form:started, etc.).
721
+ * Not enqueued — matches web SDK behaviour where form events are fire-and-forget.
722
+ */
723
+ async _trackFormEvent(eventName, feedbackConfigurationId) {
724
+ var _a, _b;
725
+ if (!this._initialized) return;
726
+ const deviceInfo = await this._buildDeviceInfo();
727
+ const req = {
728
+ eventName,
729
+ feedbackConfigurationId,
730
+ $deviceInfo: deviceInfo,
731
+ $feedbackTransactions: (_a = this._feedbackTransactions) != null ? _a : void 0
732
+ };
733
+ try {
734
+ const res = await this._post(ENDPOINTS.TRACK_EVENT, req);
735
+ if (res.$feedbackTransactions) {
736
+ this._feedbackTransactions = res.$feedbackTransactions;
737
+ const key = (_b = this._userName) != null ? _b : "anonymous";
738
+ await setFeedbackTransactions(key, res.$feedbackTransactions);
739
+ }
740
+ this._handleResponseMeta(res);
741
+ if (res.formConfigurationId) {
742
+ this._showFormById(res.formConfigurationId, { triggerType: "automatic" });
743
+ }
744
+ } catch (e) {
745
+ }
746
+ }
747
+ async trackScreen(screenName) {
748
+ var _a;
749
+ if (!this._initialized) return;
750
+ if (this._isFullScreen) return;
751
+ this._currentScreen = screenName;
752
+ const deviceInfo = await this._buildDeviceInfo(screenName);
753
+ const req = {
754
+ $deviceInfo: deviceInfo,
755
+ $feedbackTransactions: (_a = this._feedbackTransactions) != null ? _a : void 0
756
+ };
757
+ enqueue("trackScreen", async () => {
758
+ var _a2;
759
+ const res = await this._post(ENDPOINTS.TRACK_SCREEN, req);
760
+ if (res.$feedbackTransactions) {
761
+ this._feedbackTransactions = res.$feedbackTransactions;
762
+ const key = (_a2 = this._userName) != null ? _a2 : "anonymous";
763
+ await setFeedbackTransactions(key, res.$feedbackTransactions);
764
+ }
765
+ this._handleResponseMeta(res);
766
+ if (res.formConfigurationId) {
767
+ this._showFormById(res.formConfigurationId, { triggerType: "automatic" });
768
+ }
769
+ if (res.nextFeedbackId) {
770
+ const delay = typeof res.onPageDelay === "number" ? res.onPageDelay : 0;
771
+ setTimeout(() => {
772
+ this._showFormById(res.nextFeedbackId, { triggerType: "automatic", reset: "always" });
773
+ }, delay * 1e3);
774
+ }
775
+ });
776
+ flush().catch(() => {
777
+ });
778
+ }
779
+ // ============================================================================
780
+ // Form display
781
+ // ============================================================================
782
+ async showForm(formId, options) {
783
+ if (!this._initialized) return;
784
+ await this._showFormInternal(formId, __spreadProps(__spreadValues({}, options), { triggerType: "manual" }));
785
+ }
786
+ async _showFormInternal(formId, options) {
787
+ var _a, _b, _c, _d, _e, _f, _g;
788
+ const resetMode = (_a = options == null ? void 0 : options.reset) != null ? _a : "always";
789
+ const triggerType = (_b = options == null ? void 0 : options.triggerType) != null ? _b : "manual";
790
+ const deviceInfo = await this._buildDeviceInfo();
791
+ const req = {
792
+ formSlugOrId: formId,
793
+ triggerType,
794
+ language: (_c = this._locale) != null ? _c : void 0,
795
+ $deviceInfo: deviceInfo,
796
+ $feedbackTransactions: (_d = this._feedbackTransactions) != null ? _d : void 0
797
+ };
798
+ try {
799
+ const res = await this._post(ENDPOINTS.SHOW_FORM, req);
800
+ if (res.$feedbackTransactions) {
801
+ this._feedbackTransactions = res.$feedbackTransactions;
802
+ const key = (_e = this._userName) != null ? _e : "anonymous";
803
+ await setFeedbackTransactions(key, res.$feedbackTransactions);
804
+ }
805
+ this._handleResponseMeta(res);
806
+ const prefillResponses = this.getPendingResponses();
807
+ const payload = {
808
+ formId,
809
+ formConfig: res,
810
+ resetMode,
811
+ triggerType,
812
+ prefillResponses,
813
+ locale: (_f = this._locale) != null ? _f : void 0,
814
+ theme: this._theme
815
+ };
816
+ if (this._onBeforeShowForm) {
817
+ const allow = await Promise.resolve(this._onBeforeShowForm(payload));
818
+ if (!allow) {
819
+ this.clearPendingResponses();
820
+ return;
821
+ }
822
+ }
823
+ _internalEmitter.emit("showForm", {
824
+ formId,
825
+ formConfig: res,
826
+ resetMode,
827
+ triggerType,
828
+ prefillResponses: Object.keys(prefillResponses).length > 0 ? prefillResponses : void 0,
829
+ locale: (_g = this._locale) != null ? _g : void 0,
830
+ theme: this._theme
831
+ });
832
+ this._isFormVisible = true;
833
+ } catch (err) {
834
+ this._logger.warn("showForm API error:", err);
835
+ }
836
+ }
837
+ /** Used internally when server returns a formConfigurationId auto-trigger */
838
+ _showFormById(formConfigurationId, options) {
839
+ this._showFormInternal(formConfigurationId, options).catch(() => {
840
+ });
841
+ }
842
+ async dismissForm(formConfigurationId) {
843
+ var _a, _b;
844
+ if (!this._initialized) return;
845
+ _internalEmitter.emit("dismissForm", { formConfigurationId });
846
+ this._isFormVisible = false;
847
+ this._trackFormEvent("form:dismissed", formConfigurationId).catch(() => {
848
+ });
849
+ const deviceInfo = await this._buildDeviceInfo();
850
+ const req = {
851
+ formConfigurationId,
852
+ $deviceInfo: deviceInfo,
853
+ $feedbackTransactions: (_a = this._feedbackTransactions) != null ? _a : void 0
854
+ };
855
+ try {
856
+ const res = await this._post(ENDPOINTS.DISMISS_FORM, req);
857
+ if (res.$feedbackTransactions) {
858
+ this._feedbackTransactions = res.$feedbackTransactions;
859
+ const key = (_b = this._userName) != null ? _b : "anonymous";
860
+ await setFeedbackTransactions(key, res.$feedbackTransactions);
861
+ }
862
+ this._handleResponseMeta(res);
863
+ } catch (e) {
864
+ }
865
+ this.emitEvent("form:dismissed", { formId: formConfigurationId != null ? formConfigurationId : "" });
866
+ }
867
+ addToResponse(questionId, value) {
868
+ this._pendingResponses[questionId] = value;
869
+ }
870
+ getPendingResponses() {
871
+ return __spreadValues({}, this._pendingResponses);
872
+ }
873
+ clearPendingResponses() {
874
+ this._pendingResponses = {};
875
+ }
876
+ // ============================================================================
877
+ // Submit form (called by EncatchWebView after form:submit)
878
+ // ============================================================================
879
+ async submitForm(params) {
880
+ var _a, _b;
881
+ if (!this._initialized) return;
882
+ const deviceInfo = await this._buildDeviceInfo();
883
+ const req = __spreadProps(__spreadValues({}, params), {
884
+ $deviceInfo: deviceInfo,
885
+ $feedbackTransactions: (_a = this._feedbackTransactions) != null ? _a : void 0
886
+ });
887
+ try {
888
+ const res = await this._post(ENDPOINTS.SUBMIT_FORM, req);
889
+ if (res.$feedbackTransactions) {
890
+ this._feedbackTransactions = res.$feedbackTransactions;
891
+ const key = (_b = this._userName) != null ? _b : "anonymous";
892
+ await setFeedbackTransactions(key, res.$feedbackTransactions);
893
+ }
894
+ this._handleResponseMeta(res);
895
+ } catch (err) {
896
+ this._logger.warn("submitForm API error:", err);
897
+ }
898
+ }
899
+ // ============================================================================
900
+ // Refine text (called by EncatchWebView after form:refineTextRequest)
901
+ // ============================================================================
902
+ async refineText(params) {
903
+ var _a;
904
+ if (!this._initialized) throw new Error("[Encatch] SDK not initialized");
905
+ const deviceInfo = await this._buildDeviceInfo();
906
+ const req = __spreadProps(__spreadValues({}, params), {
907
+ $deviceInfo: deviceInfo,
908
+ $feedbackTransactions: (_a = this._feedbackTransactions) != null ? _a : void 0
909
+ });
910
+ return this._post(ENDPOINTS.REFINE_TEXT, req);
911
+ }
912
+ // ============================================================================
913
+ // Session management
914
+ // ============================================================================
915
+ async startSession(options) {
916
+ if (!this._initialized) return;
917
+ if (this._isFullScreen) {
918
+ this._logger.debug("Skipping session tracking - fullscreen mode");
919
+ return;
920
+ }
921
+ this._sessionId = await getOrCreateSessionId();
922
+ this._startPingInterval();
923
+ if (!(options == null ? void 0 : options.skipImmediatePing)) {
924
+ this._doPing().catch(() => {
925
+ });
926
+ }
927
+ if (!(options == null ? void 0 : options.skipImmediateTrackScreen) && this._currentScreen) {
928
+ this.trackScreen(this._currentScreen).catch(() => {
929
+ });
930
+ }
931
+ }
932
+ async resetUser() {
933
+ if (this._userName) {
934
+ await Promise.all([
935
+ clearUserId(this._userName),
936
+ clearFeedbackTransactions(this._userName)
937
+ ]);
938
+ }
939
+ await clearFeedbackTransactions("anonymous");
940
+ await clearUserName();
941
+ await clearSession();
942
+ await clearPreferences();
943
+ this._userName = null;
944
+ this._userId = null;
945
+ this._userSignature = null;
946
+ this._feedbackTransactions = null;
947
+ this._locale = null;
948
+ this._country = null;
949
+ this._sessionId = await getOrCreateSessionId();
950
+ this._stopPingInterval();
951
+ _internalEmitter.emit("userIdentified", { userName: null, userId: null });
952
+ }
953
+ // ============================================================================
954
+ // Ping mechanism (mirrors web SDK)
955
+ // ============================================================================
956
+ _startPingInterval() {
957
+ this._stopPingInterval();
958
+ this._isPingActive = true;
959
+ this._pingIntervalId = setInterval(() => {
960
+ if (this._isFormVisible) return;
961
+ this._doPing().catch(() => {
962
+ });
963
+ }, this._pingIntervalMs);
964
+ }
965
+ _stopPingInterval() {
966
+ this._isPingActive = false;
967
+ if (this._pingIntervalId) {
968
+ clearInterval(this._pingIntervalId);
969
+ this._pingIntervalId = null;
970
+ }
971
+ if (this._pingTimeoutId) {
972
+ clearTimeout(this._pingTimeoutId);
973
+ this._pingTimeoutId = null;
974
+ }
975
+ }
976
+ _scheduleNextPing(delayMs) {
977
+ this._stopPingInterval();
978
+ this._pingTimeoutId = setTimeout(async () => {
979
+ if (!this._isFormVisible) {
980
+ try {
981
+ await this._doPing();
982
+ } catch (e) {
983
+ }
984
+ }
985
+ this._startPingInterval();
986
+ }, delayMs);
987
+ }
988
+ async _doPing() {
989
+ var _a, _b, _c;
990
+ const deviceInfo = await this._buildDeviceInfo((_a = this._currentScreen) != null ? _a : void 0);
991
+ const req = {
992
+ $deviceInfo: deviceInfo,
993
+ $feedbackTransactions: (_b = this._feedbackTransactions) != null ? _b : void 0
994
+ };
995
+ const res = await this._post(ENDPOINTS.PING, req);
996
+ if (res.$feedbackTransactions) {
997
+ this._feedbackTransactions = res.$feedbackTransactions;
998
+ const key = (_c = this._userName) != null ? _c : "anonymous";
999
+ await setFeedbackTransactions(key, res.$feedbackTransactions);
1000
+ }
1001
+ this._handleResponseMeta(res);
1002
+ if (res.formConfigurationId) {
1003
+ this._showFormById(res.formConfigurationId, { triggerType: "automatic" });
1004
+ }
1005
+ }
1006
+ // ============================================================================
1007
+ // Form visibility state (used by EncatchWebView)
1008
+ // ============================================================================
1009
+ setFormVisible(visible) {
1010
+ this._isFormVisible = visible;
1011
+ }
1012
+ // ============================================================================
1013
+ // API response meta handler
1014
+ // ============================================================================
1015
+ _handleResponseMeta(res) {
1016
+ if (typeof res.pingAgainIn === "number" && res.pingAgainIn > 0 && this._isPingActive) {
1017
+ this._scheduleNextPing(res.pingAgainIn * 1e3);
1018
+ }
1019
+ }
1020
+ // ============================================================================
1021
+ // SDK events (external callbacks)
1022
+ // ============================================================================
1023
+ on(callback) {
1024
+ this._eventCallbacks.push(callback);
1025
+ return () => this.off(callback);
1026
+ }
1027
+ off(callback) {
1028
+ const idx = this._eventCallbacks.indexOf(callback);
1029
+ if (idx !== -1) this._eventCallbacks.splice(idx, 1);
1030
+ }
1031
+ emitEvent(eventType, payload) {
1032
+ const full = __spreadProps(__spreadValues({}, payload), { timestamp: Date.now() });
1033
+ for (const cb of this._eventCallbacks) {
1034
+ try {
1035
+ cb(eventType, full);
1036
+ } catch (e) {
1037
+ }
1038
+ }
1039
+ }
1040
+ // ============================================================================
1041
+ // Device info builder
1042
+ // ============================================================================
1043
+ async _buildDeviceInfo(screenName) {
1044
+ var _a, _b, _c, _d;
1045
+ const locale = await getDeviceLocale();
1046
+ const osVersion = getOsVersion();
1047
+ const platform = getPlatform();
1048
+ const timezone = getTimezone();
1049
+ const deviceType = getDeviceTypeEnv();
1050
+ const deviceSize = deviceType === "web" ? getDeviceSize() : void 0;
1051
+ return {
1052
+ $deviceOs: platform,
1053
+ $deviceVersion: osVersion,
1054
+ $deviceOsVersion: osVersion,
1055
+ $deviceType: deviceType,
1056
+ $deviceSize: deviceSize,
1057
+ $sdkVersion: SDK_VERSION,
1058
+ $appVersion: this._appVersion,
1059
+ $app: (_a = this._appPackageName) != null ? _a : void 0,
1060
+ $deviceLanguage: locale,
1061
+ $userLanguage: (_b = this._locale) != null ? _b : locale,
1062
+ $countryCode: (_c = this._country) != null ? _c : void 0,
1063
+ $preferredTheme: this._theme,
1064
+ $timezone: timezone != null ? timezone : void 0,
1065
+ $urlOrScreenName: (_d = screenName != null ? screenName : this._currentScreen) != null ? _d : void 0
1066
+ };
1067
+ }
1068
+ // ============================================================================
1069
+ // HTTP client (plain fetch, no external dependency)
1070
+ // ============================================================================
1071
+ async _post(endpoint, body, opts) {
1072
+ if (!this._apiKey) throw new Error("[Encatch] SDK not initialized");
1073
+ const url = `${this._apiBaseUrl}/${endpoint}`;
1074
+ const headers = {
1075
+ "Content-Type": "application/json",
1076
+ "X-Api-Key": this._apiKey
1077
+ };
1078
+ if (this._sessionId) headers["X-Session-Id"] = this._sessionId;
1079
+ if (this._userName) headers["X-User-Name"] = this._userName;
1080
+ if (this._userId) headers["X-User-Id"] = this._userId;
1081
+ if (this._userSignature) headers["X-User-Signature"] = this._userSignature;
1082
+ if (this._deviceId) headers["X-Device-Id"] = this._deviceId;
1083
+ if (opts == null ? void 0 : opts.signatureTime) headers["X-User-Signature-Time"] = opts.signatureTime;
1084
+ if (this._appPackageName) headers["Referer"] = this._appPackageName;
1085
+ const bodyStr = JSON.stringify(body);
1086
+ if (this._debugMode) {
1087
+ const headersForLog = __spreadValues({}, headers);
1088
+ if (headersForLog["X-Api-Key"]) headersForLog["X-Api-Key"] = "***";
1089
+ this._logger.debug(`POST ${endpoint} -> ${url}`);
1090
+ this._logger.debug("Request headers:\n" + JSON.stringify(headersForLog, null, 2));
1091
+ this._logger.debug("Request body:\n" + JSON.stringify(body, null, 2));
1092
+ }
1093
+ const res = await fetch(url, {
1094
+ method: "POST",
1095
+ headers,
1096
+ body: bodyStr
1097
+ });
1098
+ const responseText = await res.text().catch(() => "");
1099
+ if (this._debugMode) {
1100
+ const resHeaders = {};
1101
+ res.headers.forEach((v, k) => {
1102
+ resHeaders[k] = v;
1103
+ });
1104
+ this._logger.debug(`POST ${endpoint} <- ${res.status}`);
1105
+ this._logger.debug("Response headers:\n" + JSON.stringify(resHeaders, null, 2));
1106
+ try {
1107
+ const resBody = JSON.parse(responseText);
1108
+ this._logger.debug("Response body:\n" + JSON.stringify(resBody, null, 2));
1109
+ } catch (e) {
1110
+ this._logger.debug("Response body:\n" + responseText);
1111
+ }
1112
+ }
1113
+ try {
1114
+ const parsedForCheck = JSON.parse(responseText);
1115
+ if (parsedForCheck && typeof parsedForCheck === "object" && parsedForCheck.user_pending_retry_exhausted === true) {
1116
+ console.log("USER identification timeout for encatch SDK");
1117
+ this._stopPingInterval();
1118
+ this.resetUser().catch(() => {
1119
+ });
1120
+ }
1121
+ } catch (e) {
1122
+ }
1123
+ if (!res.ok) {
1124
+ const errMsg = `[Encatch API] ${endpoint} failed with status ${res.status}: ${responseText}`;
1125
+ this._logger.warn(errMsg);
1126
+ throw new Error(errMsg);
1127
+ }
1128
+ return JSON.parse(responseText);
1129
+ }
1130
+ // ============================================================================
1131
+ // Getters (read-only, used by EncatchWebView / EncatchProvider)
1132
+ // ============================================================================
1133
+ get isInitialized() {
1134
+ return this._initialized;
1135
+ }
1136
+ get apiKey() {
1137
+ return this._apiKey;
1138
+ }
1139
+ get baseUrl() {
1140
+ return this._apiBaseUrl;
1141
+ }
1142
+ get webHost() {
1143
+ return this._webHost;
1144
+ }
1145
+ get isFullScreen() {
1146
+ return this._isFullScreen;
1147
+ }
1148
+ get theme() {
1149
+ return this._theme;
1150
+ }
1151
+ get locale() {
1152
+ return this._locale;
1153
+ }
1154
+ get deviceId() {
1155
+ return this._deviceId;
1156
+ }
1157
+ get sessionId() {
1158
+ return this._sessionId;
1159
+ }
1160
+ get userName() {
1161
+ return this._userName;
1162
+ }
1163
+ get userId() {
1164
+ return this._userId;
1165
+ }
1166
+ get debugMode() {
1167
+ return this._debugMode;
1168
+ }
1169
+ // ============================================================================
1170
+ // Teardown
1171
+ // ============================================================================
1172
+ stop() {
1173
+ this._stopPingInterval();
1174
+ stopAppStateListener();
1175
+ }
1176
+ };
1177
+ var Encatch = new EncatchSDK();
1178
+
1179
+ // src/EncatchProvider.tsx
1180
+ var import_react = require("react");
1181
+ var useNavigationState = null;
1182
+ var useSegments = null;
1183
+ var usePathname = null;
1184
+ var useGlobalSearchParams = null;
1185
+ try {
1186
+ const reactNavigation = require("@react-navigation/native");
1187
+ useNavigationState = reactNavigation.useNavigationState;
1188
+ } catch (e) {
1189
+ }
1190
+ try {
1191
+ const expoRouter = require("expo-router");
1192
+ useSegments = expoRouter.useSegments;
1193
+ usePathname = expoRouter.usePathname;
1194
+ useGlobalSearchParams = expoRouter.useGlobalSearchParams;
1195
+ } catch (e) {
1196
+ }
1197
+ function isRouteSkipped(path, skippedRoutes) {
1198
+ return skippedRoutes.some((skip) => skip.toLowerCase() === path.toLowerCase());
1199
+ }
1200
+ var ExpoRouterTracker = ({ skippedRoutes }) => {
1201
+ var _a, _b, _c;
1202
+ const segments = (_a = useSegments == null ? void 0 : useSegments()) != null ? _a : [];
1203
+ const pathname = (_b = usePathname == null ? void 0 : usePathname()) != null ? _b : "";
1204
+ const params = (_c = useGlobalSearchParams == null ? void 0 : useGlobalSearchParams()) != null ? _c : {};
1205
+ (0, import_react.useEffect)(() => {
1206
+ if (!pathname) return;
1207
+ const pathParamKeys = segments.filter((s) => s.startsWith("[") && s.endsWith("]")).map((s) => s.slice(1, -1));
1208
+ const queryParams = {};
1209
+ for (const key of Object.keys(params)) {
1210
+ if (!pathParamKeys.includes(key) && key !== "#") {
1211
+ queryParams[key] = params[key];
1212
+ }
1213
+ }
1214
+ let fullPath = pathname;
1215
+ if (params["#"]) fullPath += `#${params["#"]}`;
1216
+ if (isRouteSkipped(fullPath, skippedRoutes)) return;
1217
+ Encatch.trackScreen(fullPath);
1218
+ }, [pathname, segments, params]);
1219
+ return null;
1220
+ };
1221
+ var ReactNavigationTracker = ({ skippedRoutes }) => {
1222
+ const navigationState = useNavigationState == null ? void 0 : useNavigationState((state) => state);
1223
+ (0, import_react.useEffect)(() => {
1224
+ if (!navigationState) return;
1225
+ const getActiveRoute = (state) => {
1226
+ var _a;
1227
+ if (!(state == null ? void 0 : state.routes)) return { path: "/", params: {} };
1228
+ const route = state.routes[(_a = state.index) != null ? _a : state.routes.length - 1];
1229
+ if (route.state) return getActiveRoute(route.state);
1230
+ const buildPath = (s, path2 = "", allParams = {}) => {
1231
+ var _a2, _b;
1232
+ if (!(s == null ? void 0 : s.routes)) return { path: path2, params: allParams };
1233
+ const r = s.routes[(_a2 = s.index) != null ? _a2 : s.routes.length - 1];
1234
+ const merged = __spreadValues(__spreadValues({}, allParams), (_b = r.params) != null ? _b : {});
1235
+ if (r.state) return buildPath(r.state, `${path2}/${r.name}`, merged);
1236
+ return { path: `${path2}/${r.name}`, params: merged };
1237
+ };
1238
+ return buildPath(state);
1239
+ };
1240
+ const { path, params } = getActiveRoute(navigationState);
1241
+ if (!path || isRouteSkipped(path, skippedRoutes)) return;
1242
+ Encatch.trackScreen(path);
1243
+ }, [navigationState]);
1244
+ return null;
1245
+ };
1246
+ var EncatchContext = (0, import_react.createContext)(null);
1247
+ var EncatchProvider = ({
1248
+ children,
1249
+ apiKey,
1250
+ config,
1251
+ navigationType = null,
1252
+ skippedRoutes = []
1253
+ }) => {
1254
+ const [isInitialized, setIsInitialized] = (0, import_react.useState)(false);
1255
+ const [isIdentified, setIsIdentified] = (0, import_react.useState)(false);
1256
+ const [userName, setUserName2] = (0, import_react.useState)(null);
1257
+ const initCalled = (0, import_react.useRef)(false);
1258
+ (0, import_react.useEffect)(() => {
1259
+ if (initCalled.current) return;
1260
+ initCalled.current = true;
1261
+ Encatch.init(apiKey, config).then(async () => {
1262
+ setIsInitialized(true);
1263
+ setIsIdentified(!!Encatch.userName && !!Encatch.userId);
1264
+ setUserName2(Encatch.userName);
1265
+ await Encatch.startSession();
1266
+ });
1267
+ return () => {
1268
+ Encatch.stop();
1269
+ };
1270
+ }, []);
1271
+ (0, import_react.useEffect)(() => {
1272
+ const onUserIdentified = ({ userName: name, userId }) => {
1273
+ setIsIdentified(!!name && !!userId);
1274
+ setUserName2(name);
1275
+ };
1276
+ _internalEmitter.on("userIdentified", onUserIdentified);
1277
+ return () => {
1278
+ _internalEmitter.off("userIdentified", onUserIdentified);
1279
+ };
1280
+ }, []);
1281
+ const identifyUser = (0, import_react.useCallback)(
1282
+ (userName2, traits, opts) => {
1283
+ Encatch.identifyUser(userName2, traits, opts).catch(() => {
1284
+ });
1285
+ },
1286
+ []
1287
+ );
1288
+ const setLocale = (0, import_react.useCallback)((locale) => Encatch.setLocale(locale), []);
1289
+ const setCountry = (0, import_react.useCallback)((country) => Encatch.setCountry(country), []);
1290
+ const setTheme = (0, import_react.useCallback)((theme) => Encatch.setTheme(theme), []);
1291
+ const trackEvent = (0, import_react.useCallback)(
1292
+ (eventName) => {
1293
+ Encatch.trackEvent(eventName).catch(() => {
1294
+ });
1295
+ },
1296
+ []
1297
+ );
1298
+ const trackScreen = (0, import_react.useCallback)(
1299
+ (screenName) => {
1300
+ Encatch.trackScreen(screenName).catch(() => {
1301
+ });
1302
+ },
1303
+ []
1304
+ );
1305
+ const showForm = (0, import_react.useCallback)(
1306
+ (formId, opts) => {
1307
+ Encatch.showForm(formId, opts).catch(() => {
1308
+ });
1309
+ },
1310
+ []
1311
+ );
1312
+ const dismissForm = (0, import_react.useCallback)(
1313
+ (formConfigurationId) => {
1314
+ Encatch.dismissForm(formConfigurationId).catch(() => {
1315
+ });
1316
+ },
1317
+ []
1318
+ );
1319
+ const addToResponse = (0, import_react.useCallback)(
1320
+ (questionId, value) => Encatch.addToResponse(questionId, value),
1321
+ []
1322
+ );
1323
+ const resetUser = (0, import_react.useCallback)(() => {
1324
+ Encatch.resetUser().catch(() => {
1325
+ });
1326
+ }, []);
1327
+ const on = (0, import_react.useCallback)((callback) => Encatch.on(callback), []);
1328
+ const off = (0, import_react.useCallback)((callback) => Encatch.off(callback), []);
1329
+ const submitForm = (0, import_react.useCallback)(
1330
+ (params) => {
1331
+ Encatch.submitForm(params).catch(() => {
1332
+ });
1333
+ },
1334
+ []
1335
+ );
1336
+ const emitEvent = (0, import_react.useCallback)(
1337
+ (eventType, payload) => {
1338
+ Encatch.emitEvent(eventType, payload);
1339
+ },
1340
+ []
1341
+ );
1342
+ const refineText = (0, import_react.useCallback)((params) => {
1343
+ return Encatch.refineText(params);
1344
+ }, []);
1345
+ const contextValue = {
1346
+ isInitialized,
1347
+ isIdentified,
1348
+ userName,
1349
+ identifyUser,
1350
+ setLocale,
1351
+ setCountry,
1352
+ setTheme,
1353
+ trackEvent,
1354
+ trackScreen,
1355
+ showForm,
1356
+ dismissForm,
1357
+ addToResponse,
1358
+ resetUser,
1359
+ on,
1360
+ off,
1361
+ submitForm,
1362
+ emitEvent,
1363
+ refineText
1364
+ };
1365
+ return <EncatchContext.Provider value={contextValue}>
1366
+ {navigationType === "expo-router" && useSegments && <ExpoRouterTracker skippedRoutes={skippedRoutes} />}
1367
+ {navigationType === "react-navigation" && useNavigationState && <ReactNavigationTracker skippedRoutes={skippedRoutes} />}
1368
+ {children}
1369
+ </EncatchContext.Provider>;
1370
+ };
1371
+ function useEncatch() {
1372
+ const context = (0, import_react.useContext)(EncatchContext);
1373
+ if (!context) {
1374
+ throw new Error("[Encatch] useEncatch must be used within an <EncatchProvider>");
1375
+ }
1376
+ return context;
1377
+ }
1378
+
1379
+ // src/EncatchWebView.tsx
1380
+ var import_react2 = require("react");
1381
+ var import_react_native3 = require("react-native");
1382
+ var import_react_native_webview = require("react-native-webview");
1383
+ function hexWithAlpha(hex, alphaHex = "4D") {
1384
+ let h = hex.replace("#", "");
1385
+ if (h.length === 3) h = h.split("").map((c) => c + c).join("");
1386
+ if (h.length === 6) return `#${h}${alphaHex}`;
1387
+ if (h.length === 8) return `#${h}`;
1388
+ return `#000000${alphaHex}`;
1389
+ }
1390
+ function getPositionLayout(position) {
1391
+ let justifyContent = "center";
1392
+ let alignItems = "center";
1393
+ if (position.startsWith("top")) justifyContent = "flex-start";
1394
+ else if (position.startsWith("bottom")) justifyContent = "flex-end";
1395
+ if (position.endsWith("left")) alignItems = "flex-start";
1396
+ else if (position.endsWith("right")) alignItems = "flex-end";
1397
+ return { justifyContent, alignItems };
1398
+ }
1399
+ function getBorderRadii(position) {
1400
+ const hasTop = position.includes("top");
1401
+ const hasBottom = position.includes("bottom");
1402
+ return {
1403
+ borderTopLeftRadius: hasTop ? 0 : 20,
1404
+ borderTopRightRadius: hasTop ? 0 : 20,
1405
+ borderBottomLeftRadius: hasBottom ? 0 : 20,
1406
+ borderBottomRightRadius: hasBottom ? 0 : 20
1407
+ };
1408
+ }
1409
+ function getAnimationConfig(position) {
1410
+ if (position.startsWith("top")) {
1411
+ return { type: "slide", tx: 0, ty: -100 };
1412
+ }
1413
+ if (position.startsWith("bottom")) {
1414
+ return { type: "slide", tx: 0, ty: 100 };
1415
+ }
1416
+ if (position.endsWith("left")) {
1417
+ return { type: "slide", tx: -100, ty: 0 };
1418
+ }
1419
+ if (position.endsWith("right")) {
1420
+ return { type: "slide", tx: 100, ty: 0 };
1421
+ }
1422
+ return { type: "scale", tx: 0, ty: 0 };
1423
+ }
1424
+ function calcMaxWidth(screenWidth) {
1425
+ if (screenWidth < 600) return screenWidth;
1426
+ if (screenWidth < 1200) return screenWidth * 0.5;
1427
+ return screenWidth * 0.4;
1428
+ }
1429
+ function getPopoverColor(themeJson, fallback) {
1430
+ if (!themeJson || themeJson === "{}") return fallback;
1431
+ try {
1432
+ const vars = JSON.parse(themeJson);
1433
+ const value = vars["--popover"];
1434
+ return typeof value === "string" && value.length > 0 ? value : fallback;
1435
+ } catch (e) {
1436
+ return fallback;
1437
+ }
1438
+ }
1439
+ function resolveActiveMode(shareableMode) {
1440
+ if (shareableMode === "light") return "light";
1441
+ if (shareableMode === "dark") return "dark";
1442
+ return import_react_native3.Appearance.getColorScheme() === "dark" ? "dark" : "light";
1443
+ }
1444
+ var EncatchWebView = () => {
1445
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1446
+ const webViewRef = (0, import_react2.useRef)(null);
1447
+ const [visible, setVisible] = (0, import_react2.useState)(false);
1448
+ const [webViewReady, setWebViewReady] = (0, import_react2.useState)(false);
1449
+ const [showCloseButton, setShowCloseButton] = (0, import_react2.useState)(true);
1450
+ const [isClosing, setIsClosing] = (0, import_react2.useState)(false);
1451
+ const [formPayload, setFormPayload] = (0, import_react2.useState)(null);
1452
+ const [screenWidth, setScreenWidth] = (0, import_react2.useState)(import_react_native3.Dimensions.get("window").width);
1453
+ const [screenHeight, setScreenHeight] = (0, import_react2.useState)(import_react_native3.Dimensions.get("window").height);
1454
+ const animatedHeight = (0, import_react2.useRef)(new import_react_native3.Animated.Value(300)).current;
1455
+ const heightTimeoutRef = (0, import_react2.useRef)(null);
1456
+ const fadeAnim = (0, import_react2.useRef)(new import_react_native3.Animated.Value(0)).current;
1457
+ const scaleAnim = (0, import_react2.useRef)(new import_react_native3.Animated.Value(0.8)).current;
1458
+ const translateXAnim = (0, import_react2.useRef)(new import_react_native3.Animated.Value(0)).current;
1459
+ const translateYAnim = (0, import_react2.useRef)(new import_react_native3.Animated.Value(0)).current;
1460
+ const formAnsweredTracked = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
1461
+ const pendingDismissResolverRef = (0, import_react2.useRef)(null);
1462
+ const position = (_c = (_b = (_a = formPayload == null ? void 0 : formPayload.formConfig) == null ? void 0 : _a.appearanceProperties) == null ? void 0 : _b.selectedPosition) != null ? _c : "center";
1463
+ const overlayColor = (_h = (_g = (_f = (_e = (_d = formPayload == null ? void 0 : formPayload.formConfig) == null ? void 0 : _d.appearanceProperties) == null ? void 0 : _e.themes) == null ? void 0 : _f.dark) == null ? void 0 : _g.overlayColor) != null ? _h : "#000000";
1464
+ const popupBgColor = (0, import_react2.useMemo)(() => {
1465
+ var _a2, _b2, _c2, _d2;
1466
+ const appearanceProperties = (_a2 = formPayload == null ? void 0 : formPayload.formConfig) == null ? void 0 : _a2.appearanceProperties;
1467
+ const shareableMode = (_b2 = appearanceProperties == null ? void 0 : appearanceProperties.featureSettings) == null ? void 0 : _b2.shareableMode;
1468
+ const activeMode = resolveActiveMode(shareableMode);
1469
+ const themeJson = (_d2 = (_c2 = appearanceProperties == null ? void 0 : appearanceProperties.themes) == null ? void 0 : _c2[activeMode]) == null ? void 0 : _d2.theme;
1470
+ const fallback = activeMode === "dark" ? "#1a1a1a" : "#ffffff";
1471
+ return getPopoverColor(themeJson, fallback);
1472
+ }, [formPayload]);
1473
+ (0, import_react2.useEffect)(() => {
1474
+ const sub = import_react_native3.Dimensions.addEventListener("change", ({ window }) => {
1475
+ setScreenWidth(window.width);
1476
+ setScreenHeight(window.height);
1477
+ });
1478
+ return () => sub.remove();
1479
+ }, []);
1480
+ const maxWidth = (0, import_react2.useMemo)(() => calcMaxWidth(screenWidth), [screenWidth]);
1481
+ const runEntranceAnimation = (0, import_react2.useCallback)(
1482
+ (pos) => {
1483
+ const cfg = getAnimationConfig(pos);
1484
+ translateXAnim.setValue(cfg.tx);
1485
+ translateYAnim.setValue(cfg.ty);
1486
+ scaleAnim.setValue(cfg.type === "scale" ? 0.8 : 1);
1487
+ fadeAnim.setValue(0);
1488
+ const anims = [
1489
+ import_react_native3.Animated.timing(fadeAnim, { toValue: 1, duration: 300, useNativeDriver: false })
1490
+ ];
1491
+ if (cfg.type === "slide") {
1492
+ anims.push(
1493
+ import_react_native3.Animated.spring(translateXAnim, { toValue: 0, tension: 50, friction: 8, useNativeDriver: false }),
1494
+ import_react_native3.Animated.spring(translateYAnim, { toValue: 0, tension: 50, friction: 8, useNativeDriver: false })
1495
+ );
1496
+ } else {
1497
+ anims.push(
1498
+ import_react_native3.Animated.spring(scaleAnim, { toValue: 1, tension: 50, friction: 8, useNativeDriver: false })
1499
+ );
1500
+ }
1501
+ import_react_native3.Animated.parallel(anims).start();
1502
+ },
1503
+ [fadeAnim, scaleAnim, translateXAnim, translateYAnim]
1504
+ );
1505
+ const runExitAnimation = (0, import_react2.useCallback)(
1506
+ (onDone, pos) => {
1507
+ const cfg = getAnimationConfig(pos);
1508
+ const anims = [
1509
+ import_react_native3.Animated.timing(fadeAnim, { toValue: 0, duration: 250, useNativeDriver: false })
1510
+ ];
1511
+ if (cfg.type === "slide") {
1512
+ anims.push(
1513
+ import_react_native3.Animated.spring(translateXAnim, { toValue: cfg.tx, tension: 50, friction: 8, useNativeDriver: false }),
1514
+ import_react_native3.Animated.spring(translateYAnim, { toValue: cfg.ty, tension: 50, friction: 8, useNativeDriver: false })
1515
+ );
1516
+ } else {
1517
+ anims.push(
1518
+ import_react_native3.Animated.spring(scaleAnim, { toValue: 0.8, tension: 50, friction: 8, useNativeDriver: false })
1519
+ );
1520
+ }
1521
+ import_react_native3.Animated.parallel(anims).start(() => onDone());
1522
+ },
1523
+ [fadeAnim, scaleAnim, translateXAnim, translateYAnim]
1524
+ );
1525
+ (0, import_react2.useEffect)(() => {
1526
+ const onShowForm = (payload) => {
1527
+ setFormPayload(payload);
1528
+ setShowCloseButton(true);
1529
+ setIsClosing(false);
1530
+ setWebViewReady(false);
1531
+ fadeAnim.setValue(0);
1532
+ setVisible(true);
1533
+ animatedHeight.setValue(300);
1534
+ Encatch.setFormVisible(true);
1535
+ };
1536
+ const onDismissForm = () => {
1537
+ var _a2, _b2, _c2;
1538
+ if (!visible) return;
1539
+ const pos = (_c2 = (_b2 = (_a2 = formPayload == null ? void 0 : formPayload.formConfig) == null ? void 0 : _a2.appearanceProperties) == null ? void 0 : _b2.selectedPosition) != null ? _c2 : "center";
1540
+ runExitAnimation(() => {
1541
+ setVisible(false);
1542
+ setWebViewReady(false);
1543
+ setFormPayload(null);
1544
+ Encatch.setFormVisible(false);
1545
+ }, pos);
1546
+ };
1547
+ _internalEmitter.on("showForm", onShowForm);
1548
+ _internalEmitter.on("dismissForm", onDismissForm);
1549
+ return () => {
1550
+ _internalEmitter.off("showForm", onShowForm);
1551
+ _internalEmitter.off("dismissForm", onDismissForm);
1552
+ };
1553
+ }, [visible, runExitAnimation, animatedHeight, formPayload]);
1554
+ const handleClose = (0, import_react2.useCallback)(() => {
1555
+ if (isClosing) return;
1556
+ setIsClosing(true);
1557
+ runExitAnimation(() => {
1558
+ setVisible(false);
1559
+ setWebViewReady(false);
1560
+ setFormPayload(null);
1561
+ Encatch.setFormVisible(false);
1562
+ }, position);
1563
+ }, [isClosing, runExitAnimation, position]);
1564
+ const updateHeight = (0, import_react2.useCallback)(
1565
+ (newHeight) => {
1566
+ if (heightTimeoutRef.current) clearTimeout(heightTimeoutRef.current);
1567
+ heightTimeoutRef.current = setTimeout(() => {
1568
+ const capped = Math.min(newHeight, screenHeight * 0.8);
1569
+ import_react_native3.Animated.timing(animatedHeight, {
1570
+ toValue: capped,
1571
+ duration: 150,
1572
+ useNativeDriver: false
1573
+ }).start();
1574
+ }, 10);
1575
+ },
1576
+ [animatedHeight, screenHeight]
1577
+ );
1578
+ (0, import_react2.useEffect)(() => {
1579
+ return () => {
1580
+ if (heightTimeoutRef.current) clearTimeout(heightTimeoutRef.current);
1581
+ };
1582
+ }, []);
1583
+ const injectSDKMessage = (0, import_react2.useCallback)((msg) => {
1584
+ if (!webViewRef.current) return;
1585
+ const js = `
1586
+ window.dispatchEvent(new MessageEvent('message', {
1587
+ data: ${JSON.stringify(msg)}
1588
+ }));
1589
+ true;
1590
+ `;
1591
+ webViewRef.current.injectJavaScript(js);
1592
+ }, []);
1593
+ const DISMISS_READY_TIMEOUT_MS = 3e3;
1594
+ const handleDismissWithPartialSubmit = (0, import_react2.useCallback)(() => {
1595
+ var _a2;
1596
+ if (isClosing) return;
1597
+ const partialResponseEnabled = ((_a2 = formPayload == null ? void 0 : formPayload.formConfig) == null ? void 0 : _a2.partialResponseEnabled) === true;
1598
+ if (!partialResponseEnabled) {
1599
+ handleClose();
1600
+ return;
1601
+ }
1602
+ injectSDKMessage({ type: "sdk:submitPartialBeforeDismiss", data: {} });
1603
+ const timeout = setTimeout(() => {
1604
+ if (pendingDismissResolverRef.current) {
1605
+ pendingDismissResolverRef.current = null;
1606
+ handleClose();
1607
+ }
1608
+ }, DISMISS_READY_TIMEOUT_MS);
1609
+ pendingDismissResolverRef.current = () => {
1610
+ clearTimeout(timeout);
1611
+ pendingDismissResolverRef.current = null;
1612
+ handleClose();
1613
+ };
1614
+ }, [isClosing, formPayload, injectSDKMessage, handleClose]);
1615
+ const handleWebViewMessage = (0, import_react2.useCallback)(
1616
+ async (event) => {
1617
+ var _a2, _b2, _c2, _d2, _e2, _f2, _g2, _h2, _i;
1618
+ let parsed;
1619
+ try {
1620
+ parsed = JSON.parse(event.nativeEvent.data);
1621
+ } catch (e) {
1622
+ console.warn("[EncatchWebView] Failed to parse WebView message:", event.nativeEvent.data);
1623
+ return;
1624
+ }
1625
+ const { type, data } = parsed;
1626
+ switch (type) {
1627
+ case "form:ready": {
1628
+ if (!formPayload) return;
1629
+ const { formConfig, resetMode, triggerType, prefillResponses, locale, theme } = formPayload;
1630
+ injectSDKMessage({
1631
+ type: "sdk:formConfig",
1632
+ data: __spreadProps(__spreadValues({}, formConfig), {
1633
+ triggerType
1634
+ })
1635
+ });
1636
+ if (resetMode === "always") {
1637
+ injectSDKMessage({ type: "sdk:resetData" });
1638
+ }
1639
+ if (prefillResponses && Object.keys(prefillResponses).length > 0) {
1640
+ injectSDKMessage({ type: "sdk:prefillResponses", data: { responses: prefillResponses } });
1641
+ } else {
1642
+ const pending = Encatch.getPendingResponses();
1643
+ if (Object.keys(pending).length > 0) {
1644
+ injectSDKMessage({ type: "sdk:prefillResponses", data: { responses: pending } });
1645
+ Encatch.clearPendingResponses();
1646
+ }
1647
+ }
1648
+ if (theme) {
1649
+ injectSDKMessage({ type: "sdk:theme", data: { theme } });
1650
+ }
1651
+ if (locale) {
1652
+ injectSDKMessage({ type: "sdk:locale", data: { locale } });
1653
+ }
1654
+ setWebViewReady(true);
1655
+ runEntranceAnimation(
1656
+ (_b2 = (_a2 = formConfig == null ? void 0 : formConfig.appearanceProperties) == null ? void 0 : _a2.selectedPosition) != null ? _b2 : "center"
1657
+ );
1658
+ break;
1659
+ }
1660
+ case "form:resize": {
1661
+ const h = data == null ? void 0 : data.height;
1662
+ if (typeof h === "number" && h > 0) {
1663
+ updateHeight(h);
1664
+ }
1665
+ break;
1666
+ }
1667
+ case "form:closeButton": {
1668
+ setShowCloseButton((data == null ? void 0 : data.show) !== false);
1669
+ break;
1670
+ }
1671
+ case "form:themeData": {
1672
+ break;
1673
+ }
1674
+ case "form:submit": {
1675
+ if (!data) break;
1676
+ const submitReq = {
1677
+ triggerType: (_c2 = data.triggerType) != null ? _c2 : "manual",
1678
+ formDetails: {
1679
+ formConfigurationId: data.feedbackConfigurationId,
1680
+ isPartialSubmit: (_d2 = data.isPartialSubmit) != null ? _d2 : false,
1681
+ feedbackIdentifier: data.feedbackIdentifier,
1682
+ responseLanguageCode: data.responseLanguageCode,
1683
+ response: data.response,
1684
+ completionTimeInSeconds: data.completionTimeInSeconds
1685
+ },
1686
+ $feedbackTransactions: void 0
1687
+ };
1688
+ Encatch.submitForm(submitReq).catch(() => {
1689
+ });
1690
+ Encatch.emitEvent("form:submit", { formId: parsed.formId, data });
1691
+ break;
1692
+ }
1693
+ case "form:complete": {
1694
+ Encatch.emitEvent("form:complete", { formId: parsed.formId, data });
1695
+ Encatch._trackFormEvent("form:complete", data == null ? void 0 : data.feedbackConfigurationId).catch(() => {
1696
+ });
1697
+ formAnsweredTracked.current.delete((_e2 = parsed.formId) != null ? _e2 : "");
1698
+ handleClose();
1699
+ break;
1700
+ }
1701
+ case "form:close": {
1702
+ Encatch.emitEvent("form:close", { formId: parsed.formId, data });
1703
+ formAnsweredTracked.current.delete((_g2 = (_f2 = data == null ? void 0 : data.feedbackConfigurationId) != null ? _f2 : parsed.formId) != null ? _g2 : "");
1704
+ handleClose();
1705
+ break;
1706
+ }
1707
+ case "form:started": {
1708
+ Encatch.emitEvent("form:started", { formId: parsed.formId, data });
1709
+ Encatch._trackFormEvent("form:started", data == null ? void 0 : data.feedbackConfigurationId).catch(() => {
1710
+ });
1711
+ break;
1712
+ }
1713
+ case "form:answered": {
1714
+ Encatch.emitEvent("form:answered", { formId: parsed.formId, data });
1715
+ const answeredKey = (_i = (_h2 = data == null ? void 0 : data.feedbackConfigurationId) != null ? _h2 : parsed.formId) != null ? _i : "";
1716
+ if (answeredKey && !formAnsweredTracked.current.has(answeredKey)) {
1717
+ formAnsweredTracked.current.add(answeredKey);
1718
+ Encatch._trackFormEvent("form:answered", data == null ? void 0 : data.feedbackConfigurationId).catch(() => {
1719
+ });
1720
+ }
1721
+ break;
1722
+ }
1723
+ case "form:section:change": {
1724
+ Encatch.emitEvent("form:section:change", { formId: parsed.formId, data });
1725
+ break;
1726
+ }
1727
+ case "form:show": {
1728
+ Encatch.emitEvent("form:show", { formId: parsed.formId, data });
1729
+ Encatch._trackFormEvent("form:show", data == null ? void 0 : data.feedbackConfigurationId).catch(() => {
1730
+ });
1731
+ break;
1732
+ }
1733
+ case "form:refineTextRequest": {
1734
+ if (!data) break;
1735
+ const refineParams = {
1736
+ questionId: data.questionId,
1737
+ feedbackConfigurationId: data.feedbackConfigurationId,
1738
+ userText: data.userText
1739
+ };
1740
+ try {
1741
+ const res = await Encatch.refineText(refineParams);
1742
+ injectSDKMessage({
1743
+ type: "sdk:refineTextResponse",
1744
+ data: __spreadValues({
1745
+ requestId: data.requestId
1746
+ }, res)
1747
+ });
1748
+ } catch (e) {
1749
+ injectSDKMessage({
1750
+ type: "sdk:refineTextResponse",
1751
+ data: {
1752
+ requestId: data.requestId,
1753
+ error: "Refine text request failed"
1754
+ }
1755
+ });
1756
+ }
1757
+ break;
1758
+ }
1759
+ case "form:error": {
1760
+ console.warn("[EncatchWebView] form:error received:", data);
1761
+ Encatch.emitEvent("form:error", { formId: parsed.formId, data });
1762
+ break;
1763
+ }
1764
+ case "form:readyToDismiss": {
1765
+ if (pendingDismissResolverRef.current) {
1766
+ pendingDismissResolverRef.current();
1767
+ }
1768
+ break;
1769
+ }
1770
+ default:
1771
+ break;
1772
+ }
1773
+ },
1774
+ [formPayload, injectSDKMessage, updateHeight, handleClose, runEntranceAnimation]
1775
+ );
1776
+ const webViewUrl = (0, import_react2.useMemo)(() => {
1777
+ if (!formPayload) return "";
1778
+ const base = Encatch.webHost;
1779
+ const formId = formPayload.formId;
1780
+ const params = { formId };
1781
+ if (Encatch.debugMode) params.debug = "true";
1782
+ return `${base}/s/react-native-sdk-form?${new URLSearchParams(params).toString()}`;
1783
+ }, [formPayload]);
1784
+ const { justifyContent, alignItems } = (0, import_react2.useMemo)(() => getPositionLayout(position), [position]);
1785
+ const borderRadii = (0, import_react2.useMemo)(() => getBorderRadii(position), [position]);
1786
+ const styles = (0, import_react2.useMemo)(
1787
+ () => import_react_native3.StyleSheet.create({
1788
+ overlay: {
1789
+ position: "absolute",
1790
+ top: 0,
1791
+ left: 0,
1792
+ width: screenWidth,
1793
+ height: screenHeight,
1794
+ backgroundColor: hexWithAlpha(overlayColor),
1795
+ justifyContent,
1796
+ alignItems,
1797
+ zIndex: 9999,
1798
+ elevation: 9999
1799
+ },
1800
+ popupContainer: __spreadProps(__spreadValues({}, borderRadii), {
1801
+ overflow: "hidden",
1802
+ maxHeight: screenHeight * 0.8,
1803
+ backgroundColor: popupBgColor
1804
+ }),
1805
+ closeButton: {
1806
+ position: "absolute",
1807
+ top: 8,
1808
+ right: 8,
1809
+ width: 30,
1810
+ height: 30,
1811
+ justifyContent: "center",
1812
+ alignItems: "center",
1813
+ zIndex: 2
1814
+ },
1815
+ closeText: {
1816
+ fontSize: 14,
1817
+ color: "#000",
1818
+ fontWeight: "900"
1819
+ }
1820
+ }),
1821
+ [screenWidth, screenHeight, overlayColor, popupBgColor, justifyContent, alignItems, borderRadii]
1822
+ );
1823
+ if (!formPayload) return null;
1824
+ return <import_react_native3.Animated.View
1825
+ style={[styles.overlay, { opacity: fadeAnim }]}
1826
+ pointerEvents={webViewReady ? "box-none" : "none"}
1827
+ >
1828
+ <import_react_native3.Animated.View
1829
+ style={[
1830
+ styles.popupContainer,
1831
+ {
1832
+ height: animatedHeight,
1833
+ width: maxWidth,
1834
+ transform: [
1835
+ { scaleX: scaleAnim },
1836
+ { scaleY: scaleAnim },
1837
+ { translateX: translateXAnim },
1838
+ { translateY: translateYAnim }
1839
+ ]
1840
+ }
1841
+ ]}
1842
+ >
1843
+ {showCloseButton && <import_react_native3.TouchableOpacity onPress={handleDismissWithPartialSubmit} style={styles.closeButton}>
1844
+ <import_react_native3.Text style={styles.closeText}>✕</import_react_native3.Text>
1845
+ </import_react_native3.TouchableOpacity>}
1846
+
1847
+ <import_react_native_webview.WebView
1848
+ ref={webViewRef}
1849
+ source={{ uri: webViewUrl }}
1850
+ onMessage={handleWebViewMessage}
1851
+ javaScriptEnabled
1852
+ domStorageEnabled
1853
+ startInLoadingState
1854
+ mixedContentMode="compatibility"
1855
+ scrollEnabled
1856
+ bounces={false}
1857
+ showsHorizontalScrollIndicator={false}
1858
+ showsVerticalScrollIndicator={false}
1859
+ allowsInlineMediaPlayback
1860
+ mediaPlaybackRequiresUserAction={false}
1861
+ style={{ flex: 1, backgroundColor: popupBgColor }}
1862
+ containerStyle={{ flex: 1, backgroundColor: popupBgColor }}
1863
+ onError={(e) => console.warn("[EncatchWebView] Load error:", e.nativeEvent)}
1864
+ onHttpError={(e) => console.warn("[EncatchWebView] HTTP error:", e.nativeEvent.statusCode, e.nativeEvent.url)}
1865
+ />
1866
+ </import_react_native3.Animated.View>
1867
+ </import_react_native3.Animated.View>;
1868
+ };
1869
+
1870
+ // src/form-helpers.ts
1871
+ function toQuestionAnswer(type, value) {
1872
+ switch (type) {
1873
+ case "rating":
1874
+ return { rating: typeof value === "number" ? value : parseInt(String(value), 10) };
1875
+ case "nps":
1876
+ return { nps: typeof value === "number" ? value : parseInt(String(value), 10) };
1877
+ case "short_answer":
1878
+ return { shortAnswer: String(value) };
1879
+ case "long_text":
1880
+ return { longText: String(value) };
1881
+ case "single_choice":
1882
+ return { singleChoice: String(value) };
1883
+ case "multiple_choice":
1884
+ return {
1885
+ multipleChoiceMultiple: Array.isArray(value) ? value.map(String) : [String(value)]
1886
+ };
1887
+ default:
1888
+ return { shortAnswer: String(value) };
1889
+ }
1890
+ }
1891
+ function buildSubmitRequest(options, responses) {
1892
+ var _a;
1893
+ const questions = responses.map((r) => ({
1894
+ questionId: r.questionId,
1895
+ type: r.type,
1896
+ answer: toQuestionAnswer(r.type, r.value)
1897
+ }));
1898
+ const formDetails = {
1899
+ formConfigurationId: options.formConfigurationId,
1900
+ responseLanguageCode: options.responseLanguageCode,
1901
+ completionTimeInSeconds: options.completionTimeInSeconds,
1902
+ isPartialSubmit: options.isPartialSubmit,
1903
+ feedbackIdentifier: options.feedbackIdentifier,
1904
+ response: { questions }
1905
+ };
1906
+ return {
1907
+ triggerType: (_a = options.triggerType) != null ? _a : "manual",
1908
+ formDetails
1909
+ };
1910
+ }