@layers/client 1.4.11 → 2.0.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-C6l4kIMP.d.ts +698 -0
- package/dist/index-C6l4kIMP.d.ts.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/src-DnOWq7k2.js +1305 -0
- package/dist/src-DnOWq7k2.js.map +1 -0
- package/package.json +2 -2
- package/dist/index-xkdOC7s3.d.ts +0 -307
- package/dist/index-xkdOC7s3.d.ts.map +0 -1
- package/dist/src-DwRmWyYw.js +0 -403
- package/dist/src-DwRmWyYw.js.map +0 -1
|
@@ -0,0 +1,1305 @@
|
|
|
1
|
+
import { FetchHttpClient, LayersCore, LayersError as LayersError$1, createDefaultPersistence } from "@layers/core-wasm";
|
|
2
|
+
|
|
3
|
+
//#region src/attribution.ts
|
|
4
|
+
const CLICK_ID_PARAMS = [
|
|
5
|
+
"fbclid",
|
|
6
|
+
"gclid",
|
|
7
|
+
"gbraid",
|
|
8
|
+
"wbraid",
|
|
9
|
+
"ttclid",
|
|
10
|
+
"msclkid",
|
|
11
|
+
"rclid"
|
|
12
|
+
];
|
|
13
|
+
const UTM_PARAMS = [
|
|
14
|
+
"utm_source",
|
|
15
|
+
"utm_medium",
|
|
16
|
+
"utm_campaign",
|
|
17
|
+
"utm_content",
|
|
18
|
+
"utm_term"
|
|
19
|
+
];
|
|
20
|
+
const STORAGE_KEY = "layers_attribution";
|
|
21
|
+
const TTL_MS = 720 * 60 * 60 * 1e3;
|
|
22
|
+
/**
|
|
23
|
+
* Capture attribution signals from the current page URL and referrer.
|
|
24
|
+
* Persists to localStorage with a 30-day TTL. Click IDs take priority:
|
|
25
|
+
* if a new click ID is present, the entire record is overwritten.
|
|
26
|
+
*/
|
|
27
|
+
function captureAttribution() {
|
|
28
|
+
if (typeof window === "undefined" || typeof localStorage === "undefined") return;
|
|
29
|
+
let params;
|
|
30
|
+
try {
|
|
31
|
+
params = new URLSearchParams(window.location.search);
|
|
32
|
+
} catch {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
let clickIdParam;
|
|
36
|
+
let clickIdValue;
|
|
37
|
+
for (const param of CLICK_ID_PARAMS) {
|
|
38
|
+
const value = params.get(param);
|
|
39
|
+
if (value) {
|
|
40
|
+
clickIdParam = param;
|
|
41
|
+
clickIdValue = value;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const utms = {};
|
|
46
|
+
let hasUtm = false;
|
|
47
|
+
for (const param of UTM_PARAMS) {
|
|
48
|
+
const value = params.get(param);
|
|
49
|
+
if (value) {
|
|
50
|
+
utms[param] = value;
|
|
51
|
+
hasUtm = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const referrer = typeof document !== "undefined" && document.referrer ? document.referrer : void 0;
|
|
55
|
+
if (!clickIdParam && !hasUtm && !referrer) return;
|
|
56
|
+
const existing = getAttribution();
|
|
57
|
+
if (clickIdParam) {
|
|
58
|
+
writeAttribution({
|
|
59
|
+
click_id_param: clickIdParam,
|
|
60
|
+
...clickIdValue != null && { click_id_value: clickIdValue },
|
|
61
|
+
...utms,
|
|
62
|
+
...referrer != null && { referrer_url: referrer },
|
|
63
|
+
captured_at: Date.now()
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (hasUtm) {
|
|
68
|
+
writeAttribution({
|
|
69
|
+
...existing?.click_id_param != null && { click_id_param: existing.click_id_param },
|
|
70
|
+
...existing?.click_id_value != null && { click_id_value: existing.click_id_value },
|
|
71
|
+
...utms,
|
|
72
|
+
...referrer != null && { referrer_url: referrer },
|
|
73
|
+
captured_at: Date.now()
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (!existing) writeAttribution({
|
|
78
|
+
...referrer != null && { referrer_url: referrer },
|
|
79
|
+
captured_at: Date.now()
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Read stored attribution data, returning null if missing or expired.
|
|
84
|
+
*/
|
|
85
|
+
function getAttribution() {
|
|
86
|
+
if (typeof localStorage === "undefined") return null;
|
|
87
|
+
try {
|
|
88
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
89
|
+
if (!raw) return null;
|
|
90
|
+
const data = JSON.parse(raw);
|
|
91
|
+
if (Date.now() - data.captured_at > TTL_MS) {
|
|
92
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return data;
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Return a flat property bag suitable for merging into event properties.
|
|
102
|
+
* Keys are prefixed with `$attribution_` to avoid collisions.
|
|
103
|
+
*/
|
|
104
|
+
function getAttributionProperties() {
|
|
105
|
+
const data = getAttribution();
|
|
106
|
+
if (!data) return {};
|
|
107
|
+
const props = {};
|
|
108
|
+
if (data.click_id_param) props["$attribution_click_id_param"] = data.click_id_param;
|
|
109
|
+
if (data.click_id_value) props["$attribution_click_id_value"] = data.click_id_value;
|
|
110
|
+
if (data.utm_source) props["$attribution_utm_source"] = data.utm_source;
|
|
111
|
+
if (data.utm_medium) props["$attribution_utm_medium"] = data.utm_medium;
|
|
112
|
+
if (data.utm_campaign) props["$attribution_utm_campaign"] = data.utm_campaign;
|
|
113
|
+
if (data.utm_content) props["$attribution_utm_content"] = data.utm_content;
|
|
114
|
+
if (data.utm_term) props["$attribution_utm_term"] = data.utm_term;
|
|
115
|
+
if (data.referrer_url) props["$attribution_referrer_url"] = data.referrer_url;
|
|
116
|
+
return props;
|
|
117
|
+
}
|
|
118
|
+
function writeAttribution(data) {
|
|
119
|
+
try {
|
|
120
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
|
121
|
+
} catch {}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/capi.ts
|
|
126
|
+
/**
|
|
127
|
+
* Read a named cookie from document.cookie.
|
|
128
|
+
* Returns the cookie value or undefined if not found / unavailable.
|
|
129
|
+
*/
|
|
130
|
+
function getCookie(name) {
|
|
131
|
+
if (typeof document === "undefined" || !document.cookie) return void 0;
|
|
132
|
+
const prefix = `${name}=`;
|
|
133
|
+
const cookies = document.cookie.split("; ");
|
|
134
|
+
for (const cookie of cookies) if (cookie.startsWith(prefix)) return decodeURIComponent(cookie.slice(prefix.length));
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Read Meta's _fbp cookie.
|
|
138
|
+
* Format set by Meta Pixel: fb.1.{timestamp}.{random}
|
|
139
|
+
*/
|
|
140
|
+
function getFbpCookie() {
|
|
141
|
+
return getCookie("_fbp");
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Read TikTok's _ttp cookie.
|
|
145
|
+
*/
|
|
146
|
+
function getTtpCookie() {
|
|
147
|
+
return getCookie("_ttp");
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Capture the current page URL.
|
|
151
|
+
* Required by Meta CAPI for every web event (event_source_url).
|
|
152
|
+
*/
|
|
153
|
+
function getPageUrl() {
|
|
154
|
+
if (typeof window === "undefined") return void 0;
|
|
155
|
+
try {
|
|
156
|
+
return window.location.href;
|
|
157
|
+
} catch {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Format an fbclid value into Meta's fbc cookie format.
|
|
163
|
+
* Format: fb.1.{timestamp_ms}.{fbclid}
|
|
164
|
+
*
|
|
165
|
+
* @param fbclid - The raw fbclid parameter value from the URL
|
|
166
|
+
* @param timestampMs - Optional timestamp in milliseconds (defaults to Date.now())
|
|
167
|
+
*/
|
|
168
|
+
function formatFbc(fbclid, timestampMs) {
|
|
169
|
+
return `fb.1.${timestampMs ?? Date.now()}.${fbclid}`;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get the fbc value. Checks in order:
|
|
173
|
+
* 1. If an fbclid URL parameter is present, format it as fbc
|
|
174
|
+
* 2. If an _fbc cookie exists, return it
|
|
175
|
+
* 3. Return undefined
|
|
176
|
+
*/
|
|
177
|
+
function getFbc() {
|
|
178
|
+
if (typeof window !== "undefined") try {
|
|
179
|
+
const fbclid = new URLSearchParams(window.location.search).get("fbclid");
|
|
180
|
+
if (fbclid) return formatFbc(fbclid);
|
|
181
|
+
} catch {}
|
|
182
|
+
return getCookie("_fbc");
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Build the full set of CAPI properties for an event.
|
|
186
|
+
* All values are optional — only present keys are included.
|
|
187
|
+
*/
|
|
188
|
+
function getCapiProperties() {
|
|
189
|
+
const props = {};
|
|
190
|
+
const fbp = getFbpCookie();
|
|
191
|
+
if (fbp) props["$fbp"] = fbp;
|
|
192
|
+
const ttp = getTtpCookie();
|
|
193
|
+
if (ttp) props["$ttp"] = ttp;
|
|
194
|
+
const pageUrl = getPageUrl();
|
|
195
|
+
if (pageUrl) props["$page_url"] = pageUrl;
|
|
196
|
+
const fbc = getFbc();
|
|
197
|
+
if (fbc) props["$fbc"] = fbc;
|
|
198
|
+
return props;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/debug-overlay.ts
|
|
203
|
+
const OVERLAY_ID = "layers-debug-overlay";
|
|
204
|
+
const OVERLAY_STYLES = `
|
|
205
|
+
position: fixed;
|
|
206
|
+
bottom: 8px;
|
|
207
|
+
right: 8px;
|
|
208
|
+
width: 360px;
|
|
209
|
+
max-height: 400px;
|
|
210
|
+
background: rgba(0, 0, 0, 0.88);
|
|
211
|
+
color: #e0e0e0;
|
|
212
|
+
font-family: monospace;
|
|
213
|
+
font-size: 11px;
|
|
214
|
+
line-height: 1.5;
|
|
215
|
+
padding: 12px;
|
|
216
|
+
border-radius: 8px;
|
|
217
|
+
z-index: 999999;
|
|
218
|
+
overflow-y: auto;
|
|
219
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
|
220
|
+
pointer-events: auto;
|
|
221
|
+
`;
|
|
222
|
+
let overlayElement = null;
|
|
223
|
+
let updateInterval = null;
|
|
224
|
+
/**
|
|
225
|
+
* Create and display the debug overlay in the DOM.
|
|
226
|
+
* Updates every second with the latest state from the provider.
|
|
227
|
+
*/
|
|
228
|
+
function enableDebugOverlay(stateProvider) {
|
|
229
|
+
if (typeof document === "undefined") return;
|
|
230
|
+
if (overlayElement) disableDebugOverlay();
|
|
231
|
+
overlayElement = document.createElement("div");
|
|
232
|
+
overlayElement.id = OVERLAY_ID;
|
|
233
|
+
overlayElement.setAttribute("style", OVERLAY_STYLES);
|
|
234
|
+
document.body.appendChild(overlayElement);
|
|
235
|
+
const update = () => {
|
|
236
|
+
if (!overlayElement) return;
|
|
237
|
+
overlayElement.innerHTML = renderOverlay(stateProvider());
|
|
238
|
+
};
|
|
239
|
+
update();
|
|
240
|
+
updateInterval = setInterval(update, 1e3);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Remove the debug overlay from the DOM and stop updates.
|
|
244
|
+
*/
|
|
245
|
+
function disableDebugOverlay() {
|
|
246
|
+
if (updateInterval) {
|
|
247
|
+
clearInterval(updateInterval);
|
|
248
|
+
updateInterval = null;
|
|
249
|
+
}
|
|
250
|
+
if (overlayElement && overlayElement.parentNode) overlayElement.parentNode.removeChild(overlayElement);
|
|
251
|
+
overlayElement = null;
|
|
252
|
+
}
|
|
253
|
+
function renderOverlay(state) {
|
|
254
|
+
const lines = [
|
|
255
|
+
`<div style="font-size:13px;font-weight:bold;margin-bottom:8px;">Layers Debug ${state.isOnline ? "🟢" : "🔴"}</div>`,
|
|
256
|
+
`<div><b>SDK:</b> ${escapeHtml(state.sdkVersion)}</div>`,
|
|
257
|
+
`<div><b>App:</b> ${escapeHtml(state.appId)} (${escapeHtml(state.environment)})</div>`,
|
|
258
|
+
`<div><b>Session:</b> ${escapeHtml(truncate(state.sessionId, 20))}</div>`,
|
|
259
|
+
`<div><b>Install:</b> ${escapeHtml(truncate(state.installId ?? "N/A", 20))}</div>`,
|
|
260
|
+
`<div><b>Queue:</b> ${String(state.queueDepth)} events</div>`,
|
|
261
|
+
`<div><b>Network:</b> ${state.isOnline ? "Online" : "Offline"}</div>`
|
|
262
|
+
];
|
|
263
|
+
if (state.recentEvents.length > 0) {
|
|
264
|
+
lines.push(`<div style="margin-top:8px;font-weight:bold;border-top:1px solid #444;padding-top:6px;">Recent Events</div>`);
|
|
265
|
+
for (const event of state.recentEvents.slice(0, 10)) lines.push(`<div style="color:#aaa;">${escapeHtml(event)}</div>`);
|
|
266
|
+
} else lines.push(`<div style="margin-top:8px;color:#888;">No events tracked yet</div>`);
|
|
267
|
+
return lines.join("");
|
|
268
|
+
}
|
|
269
|
+
function escapeHtml(str) {
|
|
270
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
271
|
+
}
|
|
272
|
+
function truncate(str, maxLen) {
|
|
273
|
+
if (str.length <= maxLen) return str;
|
|
274
|
+
return `${str.slice(0, maxLen)}...`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region src/deep-links.ts
|
|
279
|
+
/**
|
|
280
|
+
* Parse a URL string into structured DeepLinkData.
|
|
281
|
+
* Returns null if the URL cannot be parsed.
|
|
282
|
+
*/
|
|
283
|
+
function parseDeepLink(url) {
|
|
284
|
+
try {
|
|
285
|
+
const parsed = new URL(url);
|
|
286
|
+
const params = {};
|
|
287
|
+
parsed.searchParams.forEach((value, key) => {
|
|
288
|
+
params[key] = value;
|
|
289
|
+
});
|
|
290
|
+
return {
|
|
291
|
+
url,
|
|
292
|
+
scheme: parsed.protocol.replace(":", ""),
|
|
293
|
+
host: parsed.hostname,
|
|
294
|
+
path: parsed.pathname,
|
|
295
|
+
queryParams: params,
|
|
296
|
+
timestamp: Date.now()
|
|
297
|
+
};
|
|
298
|
+
} catch {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Set up a listener for URL changes in a web browser context.
|
|
304
|
+
* Listens for `popstate` and `hashchange` events and calls the callback
|
|
305
|
+
* with parsed deep link data on each URL change.
|
|
306
|
+
*
|
|
307
|
+
* Returns an unsubscribe function to remove the listeners.
|
|
308
|
+
*/
|
|
309
|
+
function setupDeepLinkListener(onDeepLink) {
|
|
310
|
+
if (typeof window === "undefined" || !window.location) return () => {};
|
|
311
|
+
let lastUrl;
|
|
312
|
+
try {
|
|
313
|
+
lastUrl = window.location.href;
|
|
314
|
+
} catch {
|
|
315
|
+
return () => {};
|
|
316
|
+
}
|
|
317
|
+
const handleUrlChange = () => {
|
|
318
|
+
const currentUrl = window.location.href;
|
|
319
|
+
if (currentUrl === lastUrl) return;
|
|
320
|
+
lastUrl = currentUrl;
|
|
321
|
+
const parsed = parseDeepLink(currentUrl);
|
|
322
|
+
if (parsed) onDeepLink(parsed);
|
|
323
|
+
};
|
|
324
|
+
window.addEventListener("popstate", handleUrlChange);
|
|
325
|
+
window.addEventListener("hashchange", handleUrlChange);
|
|
326
|
+
const initialData = parseDeepLink(window.location.href);
|
|
327
|
+
if (initialData && Object.keys(initialData.queryParams).length > 0) onDeepLink(initialData);
|
|
328
|
+
return () => {
|
|
329
|
+
window.removeEventListener("popstate", handleUrlChange);
|
|
330
|
+
window.removeEventListener("hashchange", handleUrlChange);
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
//#endregion
|
|
335
|
+
//#region src/standard-events.ts
|
|
336
|
+
/**
|
|
337
|
+
* Predefined standard event name constants.
|
|
338
|
+
*
|
|
339
|
+
* Usage:
|
|
340
|
+
* ```ts
|
|
341
|
+
* import { StandardEvents } from '@layers/client';
|
|
342
|
+
* client.track(StandardEvents.PURCHASE, { amount: 9.99, currency: 'USD' });
|
|
343
|
+
* ```
|
|
344
|
+
*/
|
|
345
|
+
const StandardEvents = {
|
|
346
|
+
APP_OPEN: "app_open",
|
|
347
|
+
LOGIN: "login",
|
|
348
|
+
SIGN_UP: "sign_up",
|
|
349
|
+
REGISTER: "register",
|
|
350
|
+
PURCHASE: "purchase_success",
|
|
351
|
+
ADD_TO_CART: "add_to_cart",
|
|
352
|
+
ADD_TO_WISHLIST: "add_to_wishlist",
|
|
353
|
+
INITIATE_CHECKOUT: "initiate_checkout",
|
|
354
|
+
BEGIN_CHECKOUT: "begin_checkout",
|
|
355
|
+
START_TRIAL: "start_trial",
|
|
356
|
+
SUBSCRIBE: "subscribe",
|
|
357
|
+
LEVEL_START: "level_start",
|
|
358
|
+
LEVEL_COMPLETE: "level_complete",
|
|
359
|
+
TUTORIAL_COMPLETE: "tutorial_complete",
|
|
360
|
+
SEARCH: "search",
|
|
361
|
+
VIEW_ITEM: "view_item",
|
|
362
|
+
VIEW_CONTENT: "view_content",
|
|
363
|
+
SHARE: "share",
|
|
364
|
+
DEEP_LINK: "deep_link_opened",
|
|
365
|
+
SCREEN_VIEW: "screen_view"
|
|
366
|
+
};
|
|
367
|
+
/** Build a login event. */
|
|
368
|
+
function loginEvent(method) {
|
|
369
|
+
const properties = {};
|
|
370
|
+
if (method !== void 0) properties.method = method;
|
|
371
|
+
return {
|
|
372
|
+
event: StandardEvents.LOGIN,
|
|
373
|
+
properties
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
/** Build a sign-up event. */
|
|
377
|
+
function signUpEvent(method) {
|
|
378
|
+
const properties = {};
|
|
379
|
+
if (method !== void 0) properties.method = method;
|
|
380
|
+
return {
|
|
381
|
+
event: StandardEvents.SIGN_UP,
|
|
382
|
+
properties
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
/** Build a register event. */
|
|
386
|
+
function registerEvent(method) {
|
|
387
|
+
const properties = {};
|
|
388
|
+
if (method !== void 0) properties.method = method;
|
|
389
|
+
return {
|
|
390
|
+
event: StandardEvents.REGISTER,
|
|
391
|
+
properties
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
/** Build a purchase event. */
|
|
395
|
+
function purchaseEvent(amount, currency = "USD", itemId) {
|
|
396
|
+
const properties = {
|
|
397
|
+
amount,
|
|
398
|
+
currency
|
|
399
|
+
};
|
|
400
|
+
if (itemId !== void 0) properties.item_id = itemId;
|
|
401
|
+
return {
|
|
402
|
+
event: StandardEvents.PURCHASE,
|
|
403
|
+
properties
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
/** Build an add-to-cart event. */
|
|
407
|
+
function addToCartEvent(itemId, price, quantity = 1) {
|
|
408
|
+
return {
|
|
409
|
+
event: StandardEvents.ADD_TO_CART,
|
|
410
|
+
properties: {
|
|
411
|
+
item_id: itemId,
|
|
412
|
+
price,
|
|
413
|
+
quantity
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
/** Build an add-to-wishlist event. */
|
|
418
|
+
function addToWishlistEvent(itemId, name, price) {
|
|
419
|
+
const properties = { item_id: itemId };
|
|
420
|
+
if (name !== void 0) properties.name = name;
|
|
421
|
+
if (price !== void 0) properties.price = price;
|
|
422
|
+
return {
|
|
423
|
+
event: StandardEvents.ADD_TO_WISHLIST,
|
|
424
|
+
properties
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
/** Build an initiate-checkout event. */
|
|
428
|
+
function initiateCheckoutEvent(value, currency = "USD", itemCount) {
|
|
429
|
+
const properties = {
|
|
430
|
+
value,
|
|
431
|
+
currency
|
|
432
|
+
};
|
|
433
|
+
if (itemCount !== void 0) properties.item_count = itemCount;
|
|
434
|
+
return {
|
|
435
|
+
event: StandardEvents.INITIATE_CHECKOUT,
|
|
436
|
+
properties
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
/** Build a start-trial event. */
|
|
440
|
+
function startTrialEvent(plan, durationDays) {
|
|
441
|
+
const properties = {};
|
|
442
|
+
if (plan !== void 0) properties.plan = plan;
|
|
443
|
+
if (durationDays !== void 0) properties.duration_days = durationDays;
|
|
444
|
+
return {
|
|
445
|
+
event: StandardEvents.START_TRIAL,
|
|
446
|
+
properties
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
/** Build a subscribe event. */
|
|
450
|
+
function subscribeEvent(plan, amount, currency = "USD") {
|
|
451
|
+
return {
|
|
452
|
+
event: StandardEvents.SUBSCRIBE,
|
|
453
|
+
properties: {
|
|
454
|
+
plan,
|
|
455
|
+
amount,
|
|
456
|
+
currency
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
/** Build a level-start event. */
|
|
461
|
+
function levelStartEvent(level) {
|
|
462
|
+
return {
|
|
463
|
+
event: StandardEvents.LEVEL_START,
|
|
464
|
+
properties: { level }
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
/** Build a level-complete event. */
|
|
468
|
+
function levelCompleteEvent(level, score) {
|
|
469
|
+
const properties = { level };
|
|
470
|
+
if (score !== void 0) properties.score = score;
|
|
471
|
+
return {
|
|
472
|
+
event: StandardEvents.LEVEL_COMPLETE,
|
|
473
|
+
properties
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
/** Build a tutorial-complete event. */
|
|
477
|
+
function tutorialCompleteEvent(name) {
|
|
478
|
+
const properties = {};
|
|
479
|
+
if (name !== void 0) properties.name = name;
|
|
480
|
+
return {
|
|
481
|
+
event: StandardEvents.TUTORIAL_COMPLETE,
|
|
482
|
+
properties
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
/** Build a search event. */
|
|
486
|
+
function searchEvent(query, resultCount) {
|
|
487
|
+
const properties = { query };
|
|
488
|
+
if (resultCount !== void 0) properties.result_count = resultCount;
|
|
489
|
+
return {
|
|
490
|
+
event: StandardEvents.SEARCH,
|
|
491
|
+
properties
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
/** Build a view-item event. */
|
|
495
|
+
function viewItemEvent(itemId, name, category) {
|
|
496
|
+
const properties = { item_id: itemId };
|
|
497
|
+
if (name !== void 0) properties.name = name;
|
|
498
|
+
if (category !== void 0) properties.category = category;
|
|
499
|
+
return {
|
|
500
|
+
event: StandardEvents.VIEW_ITEM,
|
|
501
|
+
properties
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
/** Build a view-content event. */
|
|
505
|
+
function viewContentEvent(contentId, contentType, name) {
|
|
506
|
+
const properties = { content_id: contentId };
|
|
507
|
+
if (contentType !== void 0) properties.content_type = contentType;
|
|
508
|
+
if (name !== void 0) properties.name = name;
|
|
509
|
+
return {
|
|
510
|
+
event: StandardEvents.VIEW_CONTENT,
|
|
511
|
+
properties
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
/** Build a share event. */
|
|
515
|
+
function shareEvent(contentType, method, contentId) {
|
|
516
|
+
const properties = { content_type: contentType };
|
|
517
|
+
if (method !== void 0) properties.method = method;
|
|
518
|
+
if (contentId !== void 0) properties.content_id = contentId;
|
|
519
|
+
return {
|
|
520
|
+
event: StandardEvents.SHARE,
|
|
521
|
+
properties
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
/** Build a screen-view event. */
|
|
525
|
+
function screenViewEvent(name, screenClass) {
|
|
526
|
+
const properties = { screen_name: name };
|
|
527
|
+
if (screenClass !== void 0) properties.screen_class = screenClass;
|
|
528
|
+
return {
|
|
529
|
+
event: StandardEvents.SCREEN_VIEW,
|
|
530
|
+
properties
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
//#endregion
|
|
535
|
+
//#region src/commerce.ts
|
|
536
|
+
/**
|
|
537
|
+
* Track a successful purchase.
|
|
538
|
+
*
|
|
539
|
+
* ```ts
|
|
540
|
+
* import { trackPurchase } from '@layers/client';
|
|
541
|
+
*
|
|
542
|
+
* trackPurchase(layers, {
|
|
543
|
+
* productId: 'premium_monthly',
|
|
544
|
+
* price: 9.99,
|
|
545
|
+
* currency: 'USD',
|
|
546
|
+
* transactionId: 'txn_abc123',
|
|
547
|
+
* });
|
|
548
|
+
* ```
|
|
549
|
+
*/
|
|
550
|
+
function trackPurchase(layers, params) {
|
|
551
|
+
const quantity = params.quantity ?? 1;
|
|
552
|
+
const props = {
|
|
553
|
+
product_id: params.productId,
|
|
554
|
+
price: params.price,
|
|
555
|
+
currency: params.currency,
|
|
556
|
+
quantity,
|
|
557
|
+
revenue: params.price * quantity,
|
|
558
|
+
...params.properties
|
|
559
|
+
};
|
|
560
|
+
if (params.transactionId !== void 0) props.transaction_id = params.transactionId;
|
|
561
|
+
if (params.isRestored !== void 0) props.is_restored = params.isRestored;
|
|
562
|
+
if (params.store !== void 0) props.store = params.store;
|
|
563
|
+
layers.track("purchase_success", props);
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Track a failed purchase attempt.
|
|
567
|
+
*/
|
|
568
|
+
function trackPurchaseFailed(layers, params) {
|
|
569
|
+
const props = {
|
|
570
|
+
product_id: params.productId,
|
|
571
|
+
currency: params.currency,
|
|
572
|
+
error_code: params.errorCode,
|
|
573
|
+
...params.properties
|
|
574
|
+
};
|
|
575
|
+
if (params.errorMessage !== void 0) props.error_message = params.errorMessage;
|
|
576
|
+
layers.track("purchase_failed", props);
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Track a subscription purchase or renewal.
|
|
580
|
+
*
|
|
581
|
+
* ```ts
|
|
582
|
+
* import { trackSubscription } from '@layers/client';
|
|
583
|
+
*
|
|
584
|
+
* trackSubscription(layers, {
|
|
585
|
+
* productId: 'pro_annual',
|
|
586
|
+
* price: 49.99,
|
|
587
|
+
* currency: 'USD',
|
|
588
|
+
* period: 'P1Y',
|
|
589
|
+
* });
|
|
590
|
+
* ```
|
|
591
|
+
*/
|
|
592
|
+
function trackSubscription(layers, params) {
|
|
593
|
+
const props = {
|
|
594
|
+
product_id: params.productId,
|
|
595
|
+
price: params.price,
|
|
596
|
+
currency: params.currency,
|
|
597
|
+
quantity: 1,
|
|
598
|
+
revenue: params.price,
|
|
599
|
+
...params.properties
|
|
600
|
+
};
|
|
601
|
+
if (params.transactionId !== void 0) props.transaction_id = params.transactionId;
|
|
602
|
+
if (params.period !== void 0) props.period = params.period;
|
|
603
|
+
if (params.isRenewal !== void 0) props.is_renewal = params.isRenewal;
|
|
604
|
+
if (params.isTrial !== void 0) props.is_trial = params.isTrial;
|
|
605
|
+
if (params.subscriptionGroupId !== void 0) props.subscription_group_id = params.subscriptionGroupId;
|
|
606
|
+
if (params.originalTransactionId !== void 0) props.original_transaction_id = params.originalTransactionId;
|
|
607
|
+
layers.track("subscribe", props);
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Track a completed order with multiple line items.
|
|
611
|
+
*/
|
|
612
|
+
function trackOrder(layers, params) {
|
|
613
|
+
const currency = params.currency ?? "USD";
|
|
614
|
+
let total = params.subtotal;
|
|
615
|
+
if (params.tax !== void 0) total += params.tax;
|
|
616
|
+
if (params.shipping !== void 0) total += params.shipping;
|
|
617
|
+
if (params.discount !== void 0) total -= params.discount;
|
|
618
|
+
const props = {
|
|
619
|
+
order_id: params.orderId,
|
|
620
|
+
subtotal: params.subtotal,
|
|
621
|
+
total,
|
|
622
|
+
currency,
|
|
623
|
+
item_count: params.items.length,
|
|
624
|
+
revenue: total,
|
|
625
|
+
product_ids: params.items.map((i) => i.productId).join(","),
|
|
626
|
+
...params.properties
|
|
627
|
+
};
|
|
628
|
+
if (params.tax !== void 0) props.tax = params.tax;
|
|
629
|
+
if (params.shipping !== void 0) props.shipping = params.shipping;
|
|
630
|
+
if (params.discount !== void 0) props.discount = params.discount;
|
|
631
|
+
if (params.couponCode !== void 0) props.coupon_code = params.couponCode;
|
|
632
|
+
layers.track("purchase_success", props);
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Track an item being added to the cart.
|
|
636
|
+
*/
|
|
637
|
+
function trackAddToCart(layers, item, properties) {
|
|
638
|
+
const quantity = item.quantity ?? 1;
|
|
639
|
+
const props = {
|
|
640
|
+
product_id: item.productId,
|
|
641
|
+
product_name: item.name,
|
|
642
|
+
price: item.price,
|
|
643
|
+
quantity,
|
|
644
|
+
value: item.price * quantity,
|
|
645
|
+
...properties
|
|
646
|
+
};
|
|
647
|
+
if (item.category !== void 0) props.category = item.category;
|
|
648
|
+
layers.track("add_to_cart", props);
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Track an item being removed from the cart.
|
|
652
|
+
*/
|
|
653
|
+
function trackRemoveFromCart(layers, item, properties) {
|
|
654
|
+
const quantity = item.quantity ?? 1;
|
|
655
|
+
const props = {
|
|
656
|
+
product_id: item.productId,
|
|
657
|
+
product_name: item.name,
|
|
658
|
+
price: item.price,
|
|
659
|
+
quantity,
|
|
660
|
+
...properties
|
|
661
|
+
};
|
|
662
|
+
if (item.category !== void 0) props.category = item.category;
|
|
663
|
+
layers.track("remove_from_cart", props);
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Track beginning the checkout flow.
|
|
667
|
+
*/
|
|
668
|
+
function trackBeginCheckout(layers, items, currency = "USD", properties) {
|
|
669
|
+
const total = items.reduce((sum, item) => sum + item.price * (item.quantity ?? 1), 0);
|
|
670
|
+
const props = {
|
|
671
|
+
item_count: items.length,
|
|
672
|
+
value: total,
|
|
673
|
+
currency,
|
|
674
|
+
product_ids: items.map((i) => i.productId).join(","),
|
|
675
|
+
...properties
|
|
676
|
+
};
|
|
677
|
+
layers.track("begin_checkout", props);
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Track viewing a product detail page.
|
|
681
|
+
*/
|
|
682
|
+
function trackViewProduct(layers, productId, name, price, currency = "USD", category, properties) {
|
|
683
|
+
const props = {
|
|
684
|
+
product_id: productId,
|
|
685
|
+
product_name: name,
|
|
686
|
+
price,
|
|
687
|
+
currency,
|
|
688
|
+
...properties
|
|
689
|
+
};
|
|
690
|
+
if (category !== void 0) props.category = category;
|
|
691
|
+
layers.track("view_item", props);
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Track a refund.
|
|
695
|
+
*/
|
|
696
|
+
function trackRefund(layers, params) {
|
|
697
|
+
const props = {
|
|
698
|
+
transaction_id: params.transactionId,
|
|
699
|
+
amount: params.amount,
|
|
700
|
+
currency: params.currency,
|
|
701
|
+
...params.properties
|
|
702
|
+
};
|
|
703
|
+
if (params.reason !== void 0) props.reason = params.reason;
|
|
704
|
+
layers.track("refund", props);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
//#endregion
|
|
708
|
+
//#region src/index.ts
|
|
709
|
+
const INSTALL_ID_KEY = "layers_install_id";
|
|
710
|
+
const FIRST_LAUNCH_TRACKED_KEY = "layers_first_launch_tracked";
|
|
711
|
+
const FIRST_INSTALL_TIME_KEY = "layers_first_install_time";
|
|
712
|
+
const CLIPBOARD_CHECKED_KEY = "layers_clipboard_checked";
|
|
713
|
+
const ATTRIBUTION_DATA_KEY = "layers_attribution_data";
|
|
714
|
+
/** Maximum age of an installation for install event gating (24 hours). */
|
|
715
|
+
const INSTALL_EVENT_MAX_DIFF_MS = 1440 * 60 * 1e3;
|
|
716
|
+
var LayersClient = class LayersClient {
|
|
717
|
+
core;
|
|
718
|
+
appUserId;
|
|
719
|
+
isOnline = true;
|
|
720
|
+
enableDebug;
|
|
721
|
+
baseUrl;
|
|
722
|
+
config;
|
|
723
|
+
onlineListener = null;
|
|
724
|
+
offlineListener = null;
|
|
725
|
+
visibilityListener = null;
|
|
726
|
+
beforeUnloadListener = null;
|
|
727
|
+
deepLinkUnsubscribe = null;
|
|
728
|
+
errorListeners = /* @__PURE__ */ new Set();
|
|
729
|
+
_installId = null;
|
|
730
|
+
_hadPriorSdkState = false;
|
|
731
|
+
_attributionData = null;
|
|
732
|
+
static MAX_RECENT_EVENTS = 20;
|
|
733
|
+
_recentEvents = [];
|
|
734
|
+
_isInitialized = false;
|
|
735
|
+
_initListener = null;
|
|
736
|
+
_debugOverlayActive = false;
|
|
737
|
+
constructor(config) {
|
|
738
|
+
this.enableDebug = config.enableDebug ?? false;
|
|
739
|
+
this.baseUrl = (config.baseUrl ?? "https://in.layers.com").replace(/\/$/, "");
|
|
740
|
+
this.appUserId = config.appUserId;
|
|
741
|
+
this.config = config;
|
|
742
|
+
const persistence = createDefaultPersistence(config.appId);
|
|
743
|
+
const httpClient = new FetchHttpClient();
|
|
744
|
+
this.core = LayersCore.init({
|
|
745
|
+
config: {
|
|
746
|
+
appId: config.appId,
|
|
747
|
+
environment: config.environment ?? "production",
|
|
748
|
+
...config.baseUrl != null && { baseUrl: config.baseUrl },
|
|
749
|
+
...config.enableDebug != null && { enableDebug: config.enableDebug },
|
|
750
|
+
...config.flushIntervalMs != null && { flushIntervalMs: config.flushIntervalMs },
|
|
751
|
+
...config.flushThreshold != null && { flushThreshold: config.flushThreshold },
|
|
752
|
+
...config.maxQueueSize != null && { maxQueueSize: config.maxQueueSize },
|
|
753
|
+
sdkVersion: `client/${SDK_VERSION}`
|
|
754
|
+
},
|
|
755
|
+
httpClient,
|
|
756
|
+
persistence
|
|
757
|
+
});
|
|
758
|
+
if (this.appUserId) this.core.identify(this.appUserId);
|
|
759
|
+
}
|
|
760
|
+
/** Initialize the client: detects device info, attaches lifecycle listeners, and fetches remote config. */
|
|
761
|
+
async init() {
|
|
762
|
+
const initStartTime = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
763
|
+
this.initializeInstallId();
|
|
764
|
+
this.initializeDeviceInfo();
|
|
765
|
+
this.setupNetworkListener();
|
|
766
|
+
this.setupLifecycleListeners();
|
|
767
|
+
captureAttribution();
|
|
768
|
+
this.restoreAttributionData();
|
|
769
|
+
const mainThreadEndTime = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
770
|
+
const mainThreadDurationMs = Math.round(mainThreadEndTime - initStartTime);
|
|
771
|
+
await this.core.fetchRemoteConfig().catch(() => {});
|
|
772
|
+
this.checkClipboardAttribution();
|
|
773
|
+
if (this.config.autoTrackAppOpen !== false) {
|
|
774
|
+
const appOpenProps = {};
|
|
775
|
+
const isFirstLaunch = this.shouldTreatAsNewInstall();
|
|
776
|
+
appOpenProps.is_first_launch = isFirstLaunch;
|
|
777
|
+
if (isFirstLaunch) this.storageSet(FIRST_LAUNCH_TRACKED_KEY, "true");
|
|
778
|
+
this.track("app_open", appOpenProps);
|
|
779
|
+
}
|
|
780
|
+
if (this.config.autoTrackDeepLinks !== false) this.setupDeepLinkAutoTracking();
|
|
781
|
+
this._isInitialized = true;
|
|
782
|
+
const totalEndTime = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
783
|
+
const totalDurationMs = Math.round(totalEndTime - initStartTime);
|
|
784
|
+
this.trackInitTiming(mainThreadDurationMs, totalDurationMs);
|
|
785
|
+
if (this.enableDebug) console.log(`[Layers] Init timing: mainThread=${String(mainThreadDurationMs)}ms, total=${String(totalDurationMs)}ms`);
|
|
786
|
+
if (this._initListener) try {
|
|
787
|
+
this._initListener(mainThreadDurationMs, totalDurationMs);
|
|
788
|
+
} catch (e) {
|
|
789
|
+
if (this.enableDebug) console.warn("[Layers] InitListener threw:", e);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Record a custom analytics event with an optional property bag.
|
|
794
|
+
*
|
|
795
|
+
* Events are batched and flushed automatically when the queue reaches
|
|
796
|
+
* `flushThreshold` or the periodic flush timer fires.
|
|
797
|
+
*/
|
|
798
|
+
track(eventName, properties) {
|
|
799
|
+
if (this.enableDebug) console.log(`[Layers] track("${eventName}", ${Object.keys(properties ?? {}).length} properties)`);
|
|
800
|
+
try {
|
|
801
|
+
const merged = this.mergeAllProperties(properties);
|
|
802
|
+
const depthBefore = this.core.queueDepth();
|
|
803
|
+
this.core.track(eventName, merged, this.appUserId);
|
|
804
|
+
const depthAfter = this.core.queueDepth();
|
|
805
|
+
if (depthAfter <= depthBefore && this.enableDebug) console.warn(`[Layers] track("${eventName}") — event may not have been queued (depth before=${String(depthBefore)}, after=${String(depthAfter)})`);
|
|
806
|
+
this.recordRecentEvent(eventName, properties);
|
|
807
|
+
} catch (e) {
|
|
808
|
+
this.emitError(e);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Record a screen view event with an optional property bag.
|
|
813
|
+
*
|
|
814
|
+
* Events are batched and flushed automatically when the queue reaches
|
|
815
|
+
* `flushThreshold` or the periodic flush timer fires.
|
|
816
|
+
*/
|
|
817
|
+
screen(screenName, properties) {
|
|
818
|
+
if (this.enableDebug) console.log(`[Layers] screen("${screenName}", ${Object.keys(properties ?? {}).length} properties)`);
|
|
819
|
+
try {
|
|
820
|
+
const merged = this.mergeAllProperties(properties);
|
|
821
|
+
const depthBefore = this.core.queueDepth();
|
|
822
|
+
this.core.screen(screenName, merged, this.appUserId);
|
|
823
|
+
const depthAfter = this.core.queueDepth();
|
|
824
|
+
if (depthAfter <= depthBefore && this.enableDebug) console.warn(`[Layers] screen("${screenName}") — event may not have been queued (depth before=${String(depthBefore)}, after=${String(depthAfter)})`);
|
|
825
|
+
this.recordRecentEvent(`screen:${screenName}`, properties);
|
|
826
|
+
} catch (e) {
|
|
827
|
+
this.emitError(e);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
/** Set or update user-level properties that persist across sessions. */
|
|
831
|
+
setUserProperties(properties) {
|
|
832
|
+
this.core.setUserProperties(properties);
|
|
833
|
+
}
|
|
834
|
+
/** Set user-level properties with "set once" semantics — only keys not previously set are applied. */
|
|
835
|
+
setUserPropertiesOnce(properties) {
|
|
836
|
+
this.core.setUserPropertiesOnce(properties);
|
|
837
|
+
}
|
|
838
|
+
/** Update the user's consent state for analytics and advertising data collection. */
|
|
839
|
+
setConsent(consent) {
|
|
840
|
+
this.core.setConsent(consent);
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Associate all subsequent events with a group (company, team, organization).
|
|
844
|
+
* Pass `undefined` or empty string to clear the group association.
|
|
845
|
+
*/
|
|
846
|
+
group(groupId, properties) {
|
|
847
|
+
if (this.enableDebug) console.log(`[Layers] group(${groupId ? `"${groupId}"` : "undefined"}, ${Object.keys(properties ?? {}).length} properties)`);
|
|
848
|
+
try {
|
|
849
|
+
this.core.group(groupId ?? "", properties);
|
|
850
|
+
} catch (e) {
|
|
851
|
+
this.emitError(e);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
/** Associate all subsequent events with the given user ID, or clear it with `undefined`. */
|
|
855
|
+
setAppUserId(appUserId) {
|
|
856
|
+
if (this.enableDebug) console.log(`[Layers] setAppUserId(${appUserId ? `"${appUserId}"` : "undefined"})`);
|
|
857
|
+
this.appUserId = appUserId;
|
|
858
|
+
if (appUserId) this.core.identify(appUserId);
|
|
859
|
+
else this.core.identify("");
|
|
860
|
+
}
|
|
861
|
+
/** @deprecated Use setAppUserId instead */
|
|
862
|
+
setUserId(userId) {
|
|
863
|
+
this.setAppUserId(userId);
|
|
864
|
+
}
|
|
865
|
+
/** Return the current app user ID, or `undefined` if not set. */
|
|
866
|
+
getAppUserId() {
|
|
867
|
+
return this.appUserId;
|
|
868
|
+
}
|
|
869
|
+
/** @deprecated Use getAppUserId instead */
|
|
870
|
+
getUserId() {
|
|
871
|
+
return this.appUserId;
|
|
872
|
+
}
|
|
873
|
+
/** Return the current anonymous session ID. */
|
|
874
|
+
getSessionId() {
|
|
875
|
+
return this.core.getSessionId();
|
|
876
|
+
}
|
|
877
|
+
/** Return the current consent state for analytics and advertising. */
|
|
878
|
+
getConsentState() {
|
|
879
|
+
return this.core.getConsentState();
|
|
880
|
+
}
|
|
881
|
+
/** Override device-level context fields (platform, OS, locale, etc.). */
|
|
882
|
+
setDeviceInfo(deviceInfo) {
|
|
883
|
+
this.core.setDeviceContext(deviceInfo);
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Return the install ID (persistent UUID stored in localStorage).
|
|
887
|
+
* Generated on first init, persists across sessions.
|
|
888
|
+
*/
|
|
889
|
+
getInstallId() {
|
|
890
|
+
return this._installId;
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Return the number of events currently queued.
|
|
894
|
+
*/
|
|
895
|
+
getQueueDepth() {
|
|
896
|
+
return this.core.queueDepth();
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Store attribution data that will be attached to all subsequent events.
|
|
900
|
+
*
|
|
901
|
+
* The values are persisted in localStorage so they survive page reloads.
|
|
902
|
+
* Pass `null` to clear a value.
|
|
903
|
+
*
|
|
904
|
+
* When set, `deeplinkId`, `gclid`, `fbclid`, `ttclid`, and/or `msclkid`
|
|
905
|
+
* are included in every event's properties.
|
|
906
|
+
*/
|
|
907
|
+
setAttributionData(data) {
|
|
908
|
+
this._attributionData = data;
|
|
909
|
+
try {
|
|
910
|
+
const ctx = { ...this.core.getDeviceContext() };
|
|
911
|
+
if (data.deeplinkId) ctx.deeplinkId = data.deeplinkId;
|
|
912
|
+
else delete ctx.deeplinkId;
|
|
913
|
+
this.core.setDeviceContext(ctx);
|
|
914
|
+
} catch {}
|
|
915
|
+
try {
|
|
916
|
+
if (typeof localStorage !== "undefined") localStorage.setItem(ATTRIBUTION_DATA_KEY, JSON.stringify(data));
|
|
917
|
+
} catch {}
|
|
918
|
+
if (this.enableDebug) console.log(`[Layers] setAttributionData(${JSON.stringify(data)})`);
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Set a listener to receive SDK initialization timing metrics.
|
|
922
|
+
* Must be called **before** `init()` to receive the callback.
|
|
923
|
+
* Pass `null` to clear the listener.
|
|
924
|
+
*/
|
|
925
|
+
setInitListener(listener) {
|
|
926
|
+
this._initListener = listener;
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Enable the debug overlay — a lightweight DOM panel showing SDK state.
|
|
930
|
+
* The overlay updates every second with queue depth, session info, and recent events.
|
|
931
|
+
*/
|
|
932
|
+
enableDebugOverlay() {
|
|
933
|
+
this._debugOverlayActive = true;
|
|
934
|
+
enableDebugOverlay(() => ({
|
|
935
|
+
sdkVersion: SDK_VERSION,
|
|
936
|
+
queueDepth: this.core.queueDepth(),
|
|
937
|
+
sessionId: this.core.getSessionId(),
|
|
938
|
+
installId: this._installId,
|
|
939
|
+
appId: this.config.appId,
|
|
940
|
+
environment: this.config.environment ?? "production",
|
|
941
|
+
isOnline: this.isOnline,
|
|
942
|
+
recentEvents: this._recentEvents
|
|
943
|
+
}));
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Disable and remove the debug overlay from the DOM.
|
|
947
|
+
*/
|
|
948
|
+
disableDebugOverlay() {
|
|
949
|
+
this._debugOverlayActive = false;
|
|
950
|
+
disableDebugOverlay();
|
|
951
|
+
}
|
|
952
|
+
/** Whether the SDK has completed async initialization. */
|
|
953
|
+
get isInitialized() {
|
|
954
|
+
return this._isInitialized;
|
|
955
|
+
}
|
|
956
|
+
/** SDK version string. */
|
|
957
|
+
getSdkVersion() {
|
|
958
|
+
return SDK_VERSION;
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Returns the most recent tracked events (newest first).
|
|
962
|
+
* Each entry is a formatted string like "12:34:56 event_name (3 props)".
|
|
963
|
+
*/
|
|
964
|
+
getRecentEvents() {
|
|
965
|
+
return this._recentEvents;
|
|
966
|
+
}
|
|
967
|
+
/** Flush all queued events to the server. Falls back to synchronous persistence on failure. */
|
|
968
|
+
async flush() {
|
|
969
|
+
try {
|
|
970
|
+
await this.core.flushAsync();
|
|
971
|
+
} catch (e) {
|
|
972
|
+
this.emitError(e);
|
|
973
|
+
this.core.flush();
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
/** Immediately shut down the client, removing all event listeners. Queued events are persisted but not flushed. */
|
|
977
|
+
shutdown() {
|
|
978
|
+
this.cleanupListeners();
|
|
979
|
+
if (this._debugOverlayActive) this.disableDebugOverlay();
|
|
980
|
+
this._attributionData = null;
|
|
981
|
+
this._initListener = null;
|
|
982
|
+
this.core.shutdown();
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Async shutdown: flushes remaining events before shutting down.
|
|
986
|
+
* @param timeoutMs Maximum time to wait for flush (default 3000ms).
|
|
987
|
+
*/
|
|
988
|
+
async shutdownAsync(timeoutMs = 3e3) {
|
|
989
|
+
this.cleanupListeners();
|
|
990
|
+
if (this._debugOverlayActive) this.disableDebugOverlay();
|
|
991
|
+
this._attributionData = null;
|
|
992
|
+
this._initListener = null;
|
|
993
|
+
try {
|
|
994
|
+
await Promise.race([this.core.flushAsync(), new Promise((resolve) => setTimeout(resolve, timeoutMs))]);
|
|
995
|
+
} catch {}
|
|
996
|
+
this.core.shutdown();
|
|
997
|
+
}
|
|
998
|
+
cleanupListeners() {
|
|
999
|
+
if (typeof window !== "undefined") {
|
|
1000
|
+
if (this.onlineListener) {
|
|
1001
|
+
window.removeEventListener("online", this.onlineListener);
|
|
1002
|
+
this.onlineListener = null;
|
|
1003
|
+
}
|
|
1004
|
+
if (this.offlineListener) {
|
|
1005
|
+
window.removeEventListener("offline", this.offlineListener);
|
|
1006
|
+
this.offlineListener = null;
|
|
1007
|
+
}
|
|
1008
|
+
if (this.beforeUnloadListener) {
|
|
1009
|
+
window.removeEventListener("beforeunload", this.beforeUnloadListener);
|
|
1010
|
+
this.beforeUnloadListener = null;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
if (typeof document !== "undefined" && this.visibilityListener) {
|
|
1014
|
+
document.removeEventListener("visibilitychange", this.visibilityListener);
|
|
1015
|
+
this.visibilityListener = null;
|
|
1016
|
+
}
|
|
1017
|
+
if (this.deepLinkUnsubscribe) {
|
|
1018
|
+
this.deepLinkUnsubscribe();
|
|
1019
|
+
this.deepLinkUnsubscribe = null;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
/** End the current session and start a new one with a fresh session ID. */
|
|
1023
|
+
startNewSession() {
|
|
1024
|
+
this.core.startNewSession();
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Register an error listener. Errors from track/screen/flush
|
|
1028
|
+
* that would otherwise be silently dropped are forwarded here.
|
|
1029
|
+
*/
|
|
1030
|
+
on(event, listener) {
|
|
1031
|
+
if (event === "error") this.errorListeners.add(listener);
|
|
1032
|
+
return this;
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Remove a previously registered error listener.
|
|
1036
|
+
*/
|
|
1037
|
+
off(event, listener) {
|
|
1038
|
+
if (event === "error") this.errorListeners.delete(listener);
|
|
1039
|
+
return this;
|
|
1040
|
+
}
|
|
1041
|
+
emitError(error) {
|
|
1042
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1043
|
+
for (const listener of this.errorListeners) try {
|
|
1044
|
+
listener(err);
|
|
1045
|
+
} catch {}
|
|
1046
|
+
if (this.enableDebug && this.errorListeners.size === 0) console.warn("[Layers]", err.message);
|
|
1047
|
+
}
|
|
1048
|
+
initializeInstallId() {
|
|
1049
|
+
if (typeof localStorage === "undefined") return;
|
|
1050
|
+
try {
|
|
1051
|
+
const existing = localStorage.getItem(INSTALL_ID_KEY);
|
|
1052
|
+
if (existing) {
|
|
1053
|
+
this._installId = existing;
|
|
1054
|
+
this._hadPriorSdkState = true;
|
|
1055
|
+
} else {
|
|
1056
|
+
const newId = generateUUID();
|
|
1057
|
+
localStorage.setItem(INSTALL_ID_KEY, newId);
|
|
1058
|
+
this._installId = newId;
|
|
1059
|
+
this._hadPriorSdkState = false;
|
|
1060
|
+
if (!localStorage.getItem(FIRST_INSTALL_TIME_KEY)) localStorage.setItem(FIRST_INSTALL_TIME_KEY, String(Date.now()));
|
|
1061
|
+
}
|
|
1062
|
+
} catch {
|
|
1063
|
+
this._installId = generateUUID();
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
shouldTreatAsNewInstall() {
|
|
1067
|
+
if (this.storageGet(FIRST_LAUNCH_TRACKED_KEY) === "true") return false;
|
|
1068
|
+
if (this._hadPriorSdkState) return true;
|
|
1069
|
+
const installTimeStr = this.storageGet(FIRST_INSTALL_TIME_KEY);
|
|
1070
|
+
if (installTimeStr) {
|
|
1071
|
+
const installTime = Number(installTimeStr);
|
|
1072
|
+
if (Number.isFinite(installTime)) {
|
|
1073
|
+
const elapsed = Date.now() - installTime;
|
|
1074
|
+
const isRecentInstall = elapsed <= INSTALL_EVENT_MAX_DIFF_MS;
|
|
1075
|
+
if (!isRecentInstall && this.enableDebug) console.log(`[Layers] Install event gated: installed ${Math.round(elapsed / 1e3)}s ago (threshold=${INSTALL_EVENT_MAX_DIFF_MS / 1e3}s), no prior SDK state — suppressing is_first_launch`);
|
|
1076
|
+
return isRecentInstall;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
return true;
|
|
1080
|
+
}
|
|
1081
|
+
restoreAttributionData() {
|
|
1082
|
+
try {
|
|
1083
|
+
if (typeof localStorage === "undefined") return;
|
|
1084
|
+
const raw = localStorage.getItem(ATTRIBUTION_DATA_KEY);
|
|
1085
|
+
if (!raw) return;
|
|
1086
|
+
const data = JSON.parse(raw);
|
|
1087
|
+
this._attributionData = data;
|
|
1088
|
+
if (data.deeplinkId) try {
|
|
1089
|
+
const currentContext = this.core.getDeviceContext();
|
|
1090
|
+
this.core.setDeviceContext({
|
|
1091
|
+
...currentContext,
|
|
1092
|
+
deeplinkId: data.deeplinkId
|
|
1093
|
+
});
|
|
1094
|
+
} catch {}
|
|
1095
|
+
if (this.enableDebug && data) console.log(`[Layers] Restored attribution data: ${JSON.stringify(data)}`);
|
|
1096
|
+
} catch {}
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Merge attribution properties into event properties.
|
|
1100
|
+
* deeplink_id flows through DeviceContext; other fields flow through properties.
|
|
1101
|
+
*/
|
|
1102
|
+
mergeAttributionProperties(properties) {
|
|
1103
|
+
const data = this._attributionData;
|
|
1104
|
+
if (!data) return properties;
|
|
1105
|
+
const merged = { ...properties ?? {} };
|
|
1106
|
+
if (data.gclid) merged.gclid = data.gclid;
|
|
1107
|
+
if (data.fbclid) merged.fbclid = data.fbclid;
|
|
1108
|
+
if (data.ttclid) merged.ttclid = data.ttclid;
|
|
1109
|
+
if (data.msclkid) merged.msclkid = data.msclkid;
|
|
1110
|
+
return merged;
|
|
1111
|
+
}
|
|
1112
|
+
/** Merge CAPI, attribution (url-based), setAttributionData, and user properties. */
|
|
1113
|
+
mergeAllProperties(properties) {
|
|
1114
|
+
const capiProps = getCapiProperties();
|
|
1115
|
+
const urlAttrProps = getAttributionProperties();
|
|
1116
|
+
const attrProps = this.mergeAttributionProperties(void 0) ?? {};
|
|
1117
|
+
return {
|
|
1118
|
+
...capiProps,
|
|
1119
|
+
...urlAttrProps,
|
|
1120
|
+
...attrProps,
|
|
1121
|
+
...properties
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
checkClipboardAttribution() {
|
|
1125
|
+
if (typeof navigator === "undefined" || typeof localStorage === "undefined") return;
|
|
1126
|
+
try {
|
|
1127
|
+
if (localStorage.getItem(CLIPBOARD_CHECKED_KEY) === "true") return;
|
|
1128
|
+
} catch {
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
let clipboardEnabled = false;
|
|
1132
|
+
try {
|
|
1133
|
+
const configJson = this.core.getRemoteConfigJson?.();
|
|
1134
|
+
if (configJson) clipboardEnabled = JSON.parse(configJson)?.clipboard_attribution_enabled === true;
|
|
1135
|
+
} catch {}
|
|
1136
|
+
this.storageSet(CLIPBOARD_CHECKED_KEY, "true");
|
|
1137
|
+
if (!clipboardEnabled) return;
|
|
1138
|
+
if (navigator.clipboard?.readText) navigator.clipboard.readText().then((text) => {
|
|
1139
|
+
if (!text) return;
|
|
1140
|
+
const match = text.match(/https?:\/\/(in\.layers\.com|link\.layers\.com)\/c\/([^?\s]+)/);
|
|
1141
|
+
if (match) {
|
|
1142
|
+
this.track("clipboard_attribution", {
|
|
1143
|
+
clipboard_url: text,
|
|
1144
|
+
clipboard_click_id: match[2]
|
|
1145
|
+
});
|
|
1146
|
+
if (this.enableDebug) console.log(`[Layers] Clipboard attribution found: ${match[2]}`);
|
|
1147
|
+
}
|
|
1148
|
+
}).catch(() => {});
|
|
1149
|
+
}
|
|
1150
|
+
_trackedDeepLinkUrls = /* @__PURE__ */ new Set();
|
|
1151
|
+
setupDeepLinkAutoTracking() {
|
|
1152
|
+
this.deepLinkUnsubscribe = setupDeepLinkListener((data) => {
|
|
1153
|
+
if (this._trackedDeepLinkUrls.has(data.url)) return;
|
|
1154
|
+
this._trackedDeepLinkUrls.add(data.url);
|
|
1155
|
+
setTimeout(() => this._trackedDeepLinkUrls.delete(data.url), 2e3);
|
|
1156
|
+
try {
|
|
1157
|
+
const properties = {
|
|
1158
|
+
...data.queryParams,
|
|
1159
|
+
url: data.url,
|
|
1160
|
+
scheme: data.scheme,
|
|
1161
|
+
host: data.host,
|
|
1162
|
+
path: data.path
|
|
1163
|
+
};
|
|
1164
|
+
this.track("deep_link_opened", properties);
|
|
1165
|
+
if (this.enableDebug) console.log(`[Layers] auto-tracked deep_link_opened: ${data.url}`);
|
|
1166
|
+
} catch (e) {
|
|
1167
|
+
this.emitError(e);
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
trackInitTiming(mainThreadDurationMs, totalDurationMs) {
|
|
1172
|
+
try {
|
|
1173
|
+
this.core.track("layers_init_timing", {
|
|
1174
|
+
duration_ms: totalDurationMs,
|
|
1175
|
+
main_thread_duration_ms: mainThreadDurationMs
|
|
1176
|
+
}, this.appUserId);
|
|
1177
|
+
} catch {}
|
|
1178
|
+
}
|
|
1179
|
+
recordRecentEvent(eventName, properties) {
|
|
1180
|
+
const now = /* @__PURE__ */ new Date();
|
|
1181
|
+
const time = `${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}:${String(now.getSeconds()).padStart(2, "0")}`;
|
|
1182
|
+
const propCount = Object.keys(properties ?? {}).length;
|
|
1183
|
+
const entry = `${time} ${eventName}${propCount > 0 ? ` (${String(propCount)} props)` : ""}`;
|
|
1184
|
+
this._recentEvents.unshift(entry);
|
|
1185
|
+
if (this._recentEvents.length > LayersClient.MAX_RECENT_EVENTS) this._recentEvents.pop();
|
|
1186
|
+
}
|
|
1187
|
+
initializeDeviceInfo() {
|
|
1188
|
+
const context = {
|
|
1189
|
+
platform: "web",
|
|
1190
|
+
osVersion: this.detectOS(),
|
|
1191
|
+
appVersion: SDK_VERSION,
|
|
1192
|
+
deviceModel: this.detectDeviceModel(),
|
|
1193
|
+
locale: this.detectLocale(),
|
|
1194
|
+
screenSize: this.detectScreenSize(),
|
|
1195
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
1196
|
+
...this._installId != null && { installId: this._installId }
|
|
1197
|
+
};
|
|
1198
|
+
this.core.setDeviceContext(context);
|
|
1199
|
+
}
|
|
1200
|
+
detectOS() {
|
|
1201
|
+
if (typeof navigator === "undefined") return "unknown";
|
|
1202
|
+
const ua = navigator.userAgent;
|
|
1203
|
+
if (ua.includes("Windows")) return "Windows";
|
|
1204
|
+
if (ua.includes("Mac OS")) return "macOS";
|
|
1205
|
+
if (ua.includes("Linux")) return "Linux";
|
|
1206
|
+
if (ua.includes("Android")) return "Android";
|
|
1207
|
+
if (ua.includes("iPhone") || ua.includes("iPad")) return "iOS";
|
|
1208
|
+
return "unknown";
|
|
1209
|
+
}
|
|
1210
|
+
detectDeviceModel() {
|
|
1211
|
+
if (typeof navigator === "undefined") return "unknown";
|
|
1212
|
+
if ("userAgentData" in navigator) {
|
|
1213
|
+
const uaData = navigator.userAgentData;
|
|
1214
|
+
if (uaData?.platform) return uaData.platform;
|
|
1215
|
+
}
|
|
1216
|
+
return navigator.platform ?? "unknown";
|
|
1217
|
+
}
|
|
1218
|
+
detectLocale() {
|
|
1219
|
+
if (typeof navigator === "undefined") return "en-US";
|
|
1220
|
+
return navigator.language ?? "en-US";
|
|
1221
|
+
}
|
|
1222
|
+
detectScreenSize() {
|
|
1223
|
+
if (typeof window === "undefined" || typeof screen === "undefined") return "unknown";
|
|
1224
|
+
return `${screen.width}x${screen.height}`;
|
|
1225
|
+
}
|
|
1226
|
+
setupNetworkListener() {
|
|
1227
|
+
if (typeof window === "undefined") return;
|
|
1228
|
+
this.isOnline = navigator?.onLine ?? true;
|
|
1229
|
+
this.onlineListener = () => {
|
|
1230
|
+
this.isOnline = true;
|
|
1231
|
+
this.core.flushAsync().catch(() => {});
|
|
1232
|
+
};
|
|
1233
|
+
this.offlineListener = () => {
|
|
1234
|
+
this.isOnline = false;
|
|
1235
|
+
};
|
|
1236
|
+
window.addEventListener("online", this.onlineListener);
|
|
1237
|
+
window.addEventListener("offline", this.offlineListener);
|
|
1238
|
+
}
|
|
1239
|
+
getBeaconUrl() {
|
|
1240
|
+
return `${this.baseUrl}/events`;
|
|
1241
|
+
}
|
|
1242
|
+
setupLifecycleListeners() {
|
|
1243
|
+
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
1244
|
+
this.visibilityListener = () => {
|
|
1245
|
+
if (document.visibilityState === "hidden") {
|
|
1246
|
+
try {
|
|
1247
|
+
this.core.track("app_background", {}, this.appUserId);
|
|
1248
|
+
this.recordRecentEvent("app_background");
|
|
1249
|
+
} catch {}
|
|
1250
|
+
if (typeof navigator !== "undefined" && navigator.sendBeacon && this.core.queueDepth() > 0) try {
|
|
1251
|
+
const batch = this.core.createBeaconPayload();
|
|
1252
|
+
if (batch) {
|
|
1253
|
+
if (navigator.sendBeacon(this.getBeaconUrl(), batch)) {
|
|
1254
|
+
this.core.clearBeaconEvents();
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
this.core.requeueBeaconEvents();
|
|
1258
|
+
this.core.flush();
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
} catch {
|
|
1262
|
+
this.core.requeueBeaconEvents();
|
|
1263
|
+
}
|
|
1264
|
+
this.core.flush();
|
|
1265
|
+
} else if (document.visibilityState === "visible") {
|
|
1266
|
+
try {
|
|
1267
|
+
this.core.track("app_foreground", {}, this.appUserId);
|
|
1268
|
+
this.recordRecentEvent("app_foreground");
|
|
1269
|
+
} catch {}
|
|
1270
|
+
if (this.isOnline) this.core.flushAsync().catch(() => {});
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
document.addEventListener("visibilitychange", this.visibilityListener);
|
|
1274
|
+
this.beforeUnloadListener = () => {
|
|
1275
|
+
this.core.flush();
|
|
1276
|
+
};
|
|
1277
|
+
window.addEventListener("beforeunload", this.beforeUnloadListener);
|
|
1278
|
+
}
|
|
1279
|
+
storageGet(key) {
|
|
1280
|
+
if (typeof localStorage === "undefined") return null;
|
|
1281
|
+
try {
|
|
1282
|
+
return localStorage.getItem(key);
|
|
1283
|
+
} catch {
|
|
1284
|
+
return null;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
storageSet(key, value) {
|
|
1288
|
+
if (typeof localStorage === "undefined") return;
|
|
1289
|
+
try {
|
|
1290
|
+
localStorage.setItem(key, value);
|
|
1291
|
+
} catch {}
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
function generateUUID() {
|
|
1295
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) return crypto.randomUUID();
|
|
1296
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
1297
|
+
const r = Math.floor(Math.random() * 16);
|
|
1298
|
+
return (c === "x" ? r : r % 4 + 8).toString(16);
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
const SDK_VERSION = typeof __LAYERS_CLIENT_VERSION__ !== "undefined" ? __LAYERS_CLIENT_VERSION__ : "0.1.1-alpha.1";
|
|
1302
|
+
|
|
1303
|
+
//#endregion
|
|
1304
|
+
export { parseDeepLink as A, shareEvent as C, tutorialCompleteEvent as D, subscribeEvent as E, getFbpCookie as F, getPageUrl as I, getTtpCookie as L, formatFbc as M, getCapiProperties as N, viewContentEvent as O, getFbc as P, getAttribution as R, searchEvent as S, startTrialEvent as T, levelStartEvent as _, trackOrder as a, registerEvent as b, trackRefund as c, trackViewProduct as d, StandardEvents as f, levelCompleteEvent as g, initiateCheckoutEvent as h, trackBeginCheckout as i, setupDeepLinkListener as j, viewItemEvent as k, trackRemoveFromCart as l, addToWishlistEvent as m, LayersError$1 as n, trackPurchase as o, addToCartEvent as p, trackAddToCart as r, trackPurchaseFailed as s, LayersClient as t, trackSubscription as u, loginEvent as v, signUpEvent as w, screenViewEvent as x, purchaseEvent as y, getAttributionProperties as z };
|
|
1305
|
+
//# sourceMappingURL=src-DnOWq7k2.js.map
|