@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/LICENSE +21 -0
- package/README.md +204 -0
- package/dist/index.d.mts +409 -0
- package/dist/index.d.ts +409 -0
- package/dist/index.js +1910 -0
- package/dist/index.mjs +1906 -0
- package/package.json +71 -0
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
|
+
}
|