analyzer-analytics 0.1.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 +139 -0
- package/dist/analytics.d.ts +204 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.esm.js +1753 -0
- package/dist/analytics.full.min.js +1 -0
- package/dist/analytics.js +1786 -0
- package/dist/analytics.min.js +1 -0
- package/dist/core.d.ts +46 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/network-adapter.d.ts +243 -0
- package/dist/network-adapter.d.ts.map +1 -0
- package/dist/network.d.ts +175 -0
- package/dist/network.d.ts.map +1 -0
- package/dist/slim.d.ts +26 -0
- package/dist/slim.d.ts.map +1 -0
- package/dist/storage.d.ts +88 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/types.d.ts +197 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils.d.ts +150 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +82 -0
|
@@ -0,0 +1,1786 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
7
|
+
var __toCommonJS = (from) => {
|
|
8
|
+
var entry = __moduleCache.get(from), desc;
|
|
9
|
+
if (entry)
|
|
10
|
+
return entry;
|
|
11
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
13
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
14
|
+
get: () => from[key],
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
}));
|
|
17
|
+
__moduleCache.set(from, entry);
|
|
18
|
+
return entry;
|
|
19
|
+
};
|
|
20
|
+
var __export = (target, all) => {
|
|
21
|
+
for (var name in all)
|
|
22
|
+
__defProp(target, name, {
|
|
23
|
+
get: all[name],
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
set: (newValue) => all[name] = () => newValue
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var exports_src = {};
|
|
32
|
+
__export(exports_src, {
|
|
33
|
+
throttle: () => throttle,
|
|
34
|
+
supportsFetch: () => supportsFetch,
|
|
35
|
+
supportsCompression: () => supportsCompression,
|
|
36
|
+
supportsBeacon: () => supportsBeacon,
|
|
37
|
+
sortByPriority: () => sortByPriority,
|
|
38
|
+
setSessionItem: () => setSessionItem,
|
|
39
|
+
setItem: () => setItem,
|
|
40
|
+
setDebugMode: () => setDebugMode,
|
|
41
|
+
sendWithRetry: () => sendWithRetry,
|
|
42
|
+
sendFetch: () => sendFetch,
|
|
43
|
+
sendBeacon: () => sendBeacon,
|
|
44
|
+
sendAdaptive: () => sendAdaptive,
|
|
45
|
+
send: () => send,
|
|
46
|
+
sanitizeProperties: () => sanitizeProperties,
|
|
47
|
+
sanitizeEventName: () => sanitizeEventName,
|
|
48
|
+
resetStorageCache: () => resetStorageCache,
|
|
49
|
+
resetNetworkAdapter: () => resetNetworkAdapter,
|
|
50
|
+
resetCapabilityCache: () => resetCapabilityCache,
|
|
51
|
+
queueOfflineEvents: () => queueOfflineEvents,
|
|
52
|
+
parseQueryString: () => parseQueryString,
|
|
53
|
+
onOnline: () => onOnline,
|
|
54
|
+
isValidUtmParams: () => isValidUtmParams,
|
|
55
|
+
isValidQueuedEvent: () => isValidQueuedEvent,
|
|
56
|
+
isValidEvent: () => isValidEvent,
|
|
57
|
+
isValidConfig: () => isValidConfig,
|
|
58
|
+
isStorageAvailable: () => isStorageAvailable,
|
|
59
|
+
isOffline: () => isOffline,
|
|
60
|
+
isDataSaverEnabled: () => isDataSaverEnabled,
|
|
61
|
+
isBot: () => isBot,
|
|
62
|
+
getVisitorId: () => getVisitorId,
|
|
63
|
+
getUtmParams: () => getUtmParams,
|
|
64
|
+
getUserAgent: () => getUserAgent,
|
|
65
|
+
getSessionItem: () => getSessionItem,
|
|
66
|
+
getSessionId: () => getSessionId,
|
|
67
|
+
getScreenDimensions: () => getScreenDimensions,
|
|
68
|
+
getRtt: () => getRtt,
|
|
69
|
+
getReferrer: () => getReferrer,
|
|
70
|
+
getPageUrl: () => getPageUrl,
|
|
71
|
+
getPageTitle: () => getPageTitle,
|
|
72
|
+
getOfflineEvents: () => getOfflineEvents,
|
|
73
|
+
getNetworkAdapter: () => getNetworkAdapter,
|
|
74
|
+
getItem: () => getItem,
|
|
75
|
+
getDownlink: () => getDownlink,
|
|
76
|
+
getConnectionType: () => getConnectionType,
|
|
77
|
+
getConnectionInfo: () => getConnectionInfo,
|
|
78
|
+
generateId: () => generateId,
|
|
79
|
+
default: () => src_default,
|
|
80
|
+
debounce: () => debounce,
|
|
81
|
+
createVisitorId: () => createVisitorId,
|
|
82
|
+
createSessionId: () => createSessionId,
|
|
83
|
+
createAnalytics: () => createAnalytics,
|
|
84
|
+
clearVisitorId: () => clearVisitorId,
|
|
85
|
+
clearSessionId: () => clearSessionId,
|
|
86
|
+
clearOfflineEvents: () => clearOfflineEvents,
|
|
87
|
+
assignPriority: () => assignPriority,
|
|
88
|
+
VERSION: () => VERSION,
|
|
89
|
+
STORAGE_KEYS: () => STORAGE_KEYS,
|
|
90
|
+
RetryStrategy: () => RetryStrategy,
|
|
91
|
+
NetworkError: () => NetworkError,
|
|
92
|
+
NetworkAdapter: () => NetworkAdapter,
|
|
93
|
+
EventPriority: () => EventPriority,
|
|
94
|
+
DEFAULT_CONFIG: () => DEFAULT_CONFIG,
|
|
95
|
+
Analytics: () => Analytics
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// src/types.ts
|
|
99
|
+
function isObject(value) {
|
|
100
|
+
return typeof value === "object" && value !== null;
|
|
101
|
+
}
|
|
102
|
+
function isValidUtmParams(value) {
|
|
103
|
+
if (!isObject(value))
|
|
104
|
+
return false;
|
|
105
|
+
const allowedKeys = ["source", "medium", "campaign", "term", "content"];
|
|
106
|
+
const keys = Object.keys(value);
|
|
107
|
+
return keys.every((key) => allowedKeys.includes(key) && (value[key] === undefined || typeof value[key] === "string"));
|
|
108
|
+
}
|
|
109
|
+
function isValidEvent(value) {
|
|
110
|
+
if (!isObject(value))
|
|
111
|
+
return false;
|
|
112
|
+
const event = value;
|
|
113
|
+
if (typeof event.event !== "string" || event.event.length === 0)
|
|
114
|
+
return false;
|
|
115
|
+
if (typeof event.visitor_id !== "string")
|
|
116
|
+
return false;
|
|
117
|
+
if (typeof event.session_id !== "string")
|
|
118
|
+
return false;
|
|
119
|
+
if (typeof event.page_url !== "string")
|
|
120
|
+
return false;
|
|
121
|
+
if (typeof event.user_agent !== "string")
|
|
122
|
+
return false;
|
|
123
|
+
if (typeof event.timestamp !== "number" || !Number.isFinite(event.timestamp))
|
|
124
|
+
return false;
|
|
125
|
+
if (typeof event.screen_width !== "number" || !Number.isFinite(event.screen_width))
|
|
126
|
+
return false;
|
|
127
|
+
if (typeof event.screen_height !== "number" || !Number.isFinite(event.screen_height))
|
|
128
|
+
return false;
|
|
129
|
+
if (!isObject(event.properties))
|
|
130
|
+
return false;
|
|
131
|
+
if (event.page_title !== undefined && typeof event.page_title !== "string")
|
|
132
|
+
return false;
|
|
133
|
+
if (event.referrer !== undefined && typeof event.referrer !== "string")
|
|
134
|
+
return false;
|
|
135
|
+
if (event.utm !== undefined && !isValidUtmParams(event.utm))
|
|
136
|
+
return false;
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
function isValidConfig(value) {
|
|
140
|
+
if (!isObject(value))
|
|
141
|
+
return false;
|
|
142
|
+
const config = value;
|
|
143
|
+
if (typeof config.apiKey !== "string" || config.apiKey.length === 0)
|
|
144
|
+
return false;
|
|
145
|
+
if (config.endpoint !== undefined && typeof config.endpoint !== "string")
|
|
146
|
+
return false;
|
|
147
|
+
if (config.debug !== undefined && typeof config.debug !== "boolean")
|
|
148
|
+
return false;
|
|
149
|
+
if (config.maxQueueSize !== undefined) {
|
|
150
|
+
if (typeof config.maxQueueSize !== "number" || !Number.isInteger(config.maxQueueSize) || config.maxQueueSize < 1)
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
if (config.flushInterval !== undefined) {
|
|
154
|
+
if (typeof config.flushInterval !== "number" || !Number.isInteger(config.flushInterval) || config.flushInterval < 0)
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
if (config.autoTrack !== undefined && typeof config.autoTrack !== "boolean")
|
|
158
|
+
return false;
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
function isValidQueuedEvent(value) {
|
|
162
|
+
if (!isObject(value))
|
|
163
|
+
return false;
|
|
164
|
+
const event = value;
|
|
165
|
+
if (typeof event.event !== "string" || event.event.length === 0)
|
|
166
|
+
return false;
|
|
167
|
+
if (!isObject(event.properties))
|
|
168
|
+
return false;
|
|
169
|
+
if (typeof event.timestamp !== "number" || !Number.isFinite(event.timestamp))
|
|
170
|
+
return false;
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
function createVisitorId(id) {
|
|
174
|
+
return id;
|
|
175
|
+
}
|
|
176
|
+
function createSessionId(id) {
|
|
177
|
+
return id;
|
|
178
|
+
}
|
|
179
|
+
var DEFAULT_CONFIG = {
|
|
180
|
+
endpoint: "http://localhost:8080",
|
|
181
|
+
debug: false,
|
|
182
|
+
maxQueueSize: 10,
|
|
183
|
+
flushInterval: 5000,
|
|
184
|
+
autoTrack: true,
|
|
185
|
+
adaptiveNetwork: true
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// src/storage.ts
|
|
189
|
+
var VISITOR_ID_KEY = "_analytics_vid";
|
|
190
|
+
var SESSION_ID_KEY = "_analytics_sid";
|
|
191
|
+
var STORAGE_TEST_KEY = "_analytics_test";
|
|
192
|
+
function getStorage(type) {
|
|
193
|
+
try {
|
|
194
|
+
if (typeof window !== "undefined" && window[type]) {
|
|
195
|
+
return window[type];
|
|
196
|
+
}
|
|
197
|
+
if (typeof globalThis !== "undefined" && globalThis[type]) {
|
|
198
|
+
return globalThis[type];
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
} catch {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
var memoryStorage = new Map;
|
|
206
|
+
function getFromMemory(key) {
|
|
207
|
+
return memoryStorage.get(key) ?? null;
|
|
208
|
+
}
|
|
209
|
+
function setInMemory(key, value) {
|
|
210
|
+
memoryStorage.set(key, value);
|
|
211
|
+
}
|
|
212
|
+
var storageAvailabilityCache = {
|
|
213
|
+
localStorage: null,
|
|
214
|
+
sessionStorage: null
|
|
215
|
+
};
|
|
216
|
+
function isStorageAvailable(type) {
|
|
217
|
+
if (storageAvailabilityCache[type] !== null) {
|
|
218
|
+
return storageAvailabilityCache[type];
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
const storage = getStorage(type);
|
|
222
|
+
if (!storage) {
|
|
223
|
+
storageAvailabilityCache[type] = false;
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
storage.setItem(STORAGE_TEST_KEY, "test");
|
|
227
|
+
storage.removeItem(STORAGE_TEST_KEY);
|
|
228
|
+
storageAvailabilityCache[type] = true;
|
|
229
|
+
return true;
|
|
230
|
+
} catch {
|
|
231
|
+
storageAvailabilityCache[type] = false;
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function resetStorageCache() {
|
|
236
|
+
storageAvailabilityCache.localStorage = null;
|
|
237
|
+
storageAvailabilityCache.sessionStorage = null;
|
|
238
|
+
memoryStorage.clear();
|
|
239
|
+
}
|
|
240
|
+
function getItem(key) {
|
|
241
|
+
if (isStorageAvailable("localStorage")) {
|
|
242
|
+
try {
|
|
243
|
+
const storage = getStorage("localStorage");
|
|
244
|
+
return storage?.getItem(key) ?? null;
|
|
245
|
+
} catch {}
|
|
246
|
+
}
|
|
247
|
+
return getFromMemory(key);
|
|
248
|
+
}
|
|
249
|
+
function setItem(key, value) {
|
|
250
|
+
if (isStorageAvailable("localStorage")) {
|
|
251
|
+
try {
|
|
252
|
+
const storage = getStorage("localStorage");
|
|
253
|
+
storage?.setItem(key, value);
|
|
254
|
+
return true;
|
|
255
|
+
} catch {}
|
|
256
|
+
}
|
|
257
|
+
setInMemory(key, value);
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
function getSessionItem(key) {
|
|
261
|
+
if (isStorageAvailable("sessionStorage")) {
|
|
262
|
+
try {
|
|
263
|
+
const storage = getStorage("sessionStorage");
|
|
264
|
+
return storage?.getItem(key) ?? null;
|
|
265
|
+
} catch {}
|
|
266
|
+
}
|
|
267
|
+
return getFromMemory(key);
|
|
268
|
+
}
|
|
269
|
+
function setSessionItem(key, value) {
|
|
270
|
+
if (isStorageAvailable("sessionStorage")) {
|
|
271
|
+
try {
|
|
272
|
+
const storage = getStorage("sessionStorage");
|
|
273
|
+
storage?.setItem(key, value);
|
|
274
|
+
return true;
|
|
275
|
+
} catch {}
|
|
276
|
+
}
|
|
277
|
+
setInMemory(key, value);
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
function generateId() {
|
|
281
|
+
const timestamp = Date.now();
|
|
282
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
283
|
+
try {
|
|
284
|
+
return `${timestamp}_${crypto.randomUUID()}`;
|
|
285
|
+
} catch {}
|
|
286
|
+
}
|
|
287
|
+
const randomPart = generateRandomString(8);
|
|
288
|
+
return `${timestamp}_${randomPart}`;
|
|
289
|
+
}
|
|
290
|
+
function generateRandomString(length) {
|
|
291
|
+
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
292
|
+
let result = "";
|
|
293
|
+
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
294
|
+
try {
|
|
295
|
+
const array = new Uint8Array(length);
|
|
296
|
+
crypto.getRandomValues(array);
|
|
297
|
+
for (let i = 0;i < length; i++) {
|
|
298
|
+
result += chars[array[i] % chars.length];
|
|
299
|
+
}
|
|
300
|
+
return result;
|
|
301
|
+
} catch {}
|
|
302
|
+
}
|
|
303
|
+
for (let i = 0;i < length; i++) {
|
|
304
|
+
result += chars[Math.floor(Math.random() * chars.length)];
|
|
305
|
+
}
|
|
306
|
+
return result;
|
|
307
|
+
}
|
|
308
|
+
var cachedVisitorId = null;
|
|
309
|
+
function getVisitorId() {
|
|
310
|
+
if (cachedVisitorId !== null) {
|
|
311
|
+
return cachedVisitorId;
|
|
312
|
+
}
|
|
313
|
+
let existingId = getItem(VISITOR_ID_KEY);
|
|
314
|
+
if (existingId) {
|
|
315
|
+
cachedVisitorId = createVisitorId(existingId);
|
|
316
|
+
return cachedVisitorId;
|
|
317
|
+
}
|
|
318
|
+
if (isStorageAvailable("sessionStorage")) {
|
|
319
|
+
try {
|
|
320
|
+
const storage = getStorage("sessionStorage");
|
|
321
|
+
existingId = storage?.getItem(VISITOR_ID_KEY) ?? null;
|
|
322
|
+
if (existingId) {
|
|
323
|
+
cachedVisitorId = createVisitorId(existingId);
|
|
324
|
+
return cachedVisitorId;
|
|
325
|
+
}
|
|
326
|
+
} catch {}
|
|
327
|
+
}
|
|
328
|
+
existingId = getFromMemory(VISITOR_ID_KEY);
|
|
329
|
+
if (existingId) {
|
|
330
|
+
cachedVisitorId = createVisitorId(existingId);
|
|
331
|
+
return cachedVisitorId;
|
|
332
|
+
}
|
|
333
|
+
const newId = generateId();
|
|
334
|
+
cachedVisitorId = createVisitorId(newId);
|
|
335
|
+
setItem(VISITOR_ID_KEY, newId);
|
|
336
|
+
if (isStorageAvailable("sessionStorage")) {
|
|
337
|
+
try {
|
|
338
|
+
const storage = getStorage("sessionStorage");
|
|
339
|
+
storage?.setItem(VISITOR_ID_KEY, newId);
|
|
340
|
+
} catch {}
|
|
341
|
+
}
|
|
342
|
+
return cachedVisitorId;
|
|
343
|
+
}
|
|
344
|
+
function clearVisitorId() {
|
|
345
|
+
cachedVisitorId = null;
|
|
346
|
+
if (isStorageAvailable("localStorage")) {
|
|
347
|
+
try {
|
|
348
|
+
const storage = getStorage("localStorage");
|
|
349
|
+
storage?.removeItem(VISITOR_ID_KEY);
|
|
350
|
+
} catch {}
|
|
351
|
+
}
|
|
352
|
+
if (isStorageAvailable("sessionStorage")) {
|
|
353
|
+
try {
|
|
354
|
+
const storage = getStorage("sessionStorage");
|
|
355
|
+
storage?.removeItem(VISITOR_ID_KEY);
|
|
356
|
+
} catch {}
|
|
357
|
+
}
|
|
358
|
+
memoryStorage.delete(VISITOR_ID_KEY);
|
|
359
|
+
}
|
|
360
|
+
var cachedSessionId = null;
|
|
361
|
+
function getSessionId() {
|
|
362
|
+
if (cachedSessionId !== null) {
|
|
363
|
+
return cachedSessionId;
|
|
364
|
+
}
|
|
365
|
+
let existingId = getSessionItem(SESSION_ID_KEY);
|
|
366
|
+
if (existingId) {
|
|
367
|
+
cachedSessionId = createSessionId(existingId);
|
|
368
|
+
return cachedSessionId;
|
|
369
|
+
}
|
|
370
|
+
existingId = getFromMemory(SESSION_ID_KEY);
|
|
371
|
+
if (existingId) {
|
|
372
|
+
cachedSessionId = createSessionId(existingId);
|
|
373
|
+
return cachedSessionId;
|
|
374
|
+
}
|
|
375
|
+
const newId = generateId();
|
|
376
|
+
cachedSessionId = createSessionId(newId);
|
|
377
|
+
setSessionItem(SESSION_ID_KEY, newId);
|
|
378
|
+
return cachedSessionId;
|
|
379
|
+
}
|
|
380
|
+
function clearSessionId() {
|
|
381
|
+
cachedSessionId = null;
|
|
382
|
+
if (isStorageAvailable("sessionStorage")) {
|
|
383
|
+
try {
|
|
384
|
+
const storage = getStorage("sessionStorage");
|
|
385
|
+
storage?.removeItem(SESSION_ID_KEY);
|
|
386
|
+
} catch {}
|
|
387
|
+
}
|
|
388
|
+
memoryStorage.delete(SESSION_ID_KEY);
|
|
389
|
+
}
|
|
390
|
+
var STORAGE_KEYS = {
|
|
391
|
+
VISITOR_ID: VISITOR_ID_KEY,
|
|
392
|
+
SESSION_ID: SESSION_ID_KEY
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// src/utils.ts
|
|
396
|
+
var capabilityCache = {
|
|
397
|
+
beacon: null,
|
|
398
|
+
fetch: null,
|
|
399
|
+
compression: null
|
|
400
|
+
};
|
|
401
|
+
function parseQueryString(search) {
|
|
402
|
+
if (!search || search === "?") {
|
|
403
|
+
return {};
|
|
404
|
+
}
|
|
405
|
+
const queryString = search.startsWith("?") ? search.slice(1) : search;
|
|
406
|
+
if (!queryString) {
|
|
407
|
+
return {};
|
|
408
|
+
}
|
|
409
|
+
if (typeof URLSearchParams !== "undefined") {
|
|
410
|
+
try {
|
|
411
|
+
const params = new URLSearchParams(queryString);
|
|
412
|
+
const result2 = {};
|
|
413
|
+
params.forEach((value, key) => {
|
|
414
|
+
result2[key] = value;
|
|
415
|
+
});
|
|
416
|
+
return result2;
|
|
417
|
+
} catch {}
|
|
418
|
+
}
|
|
419
|
+
const result = {};
|
|
420
|
+
const pairs = queryString.split("&");
|
|
421
|
+
for (const pair of pairs) {
|
|
422
|
+
if (!pair)
|
|
423
|
+
continue;
|
|
424
|
+
const [key, ...valueParts] = pair.split("=");
|
|
425
|
+
if (!key)
|
|
426
|
+
continue;
|
|
427
|
+
const value = valueParts.join("=");
|
|
428
|
+
try {
|
|
429
|
+
result[decodeURIComponent(key)] = decodeURIComponent(value || "");
|
|
430
|
+
} catch {
|
|
431
|
+
result[key] = value || "";
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return result;
|
|
435
|
+
}
|
|
436
|
+
function getUtmParams() {
|
|
437
|
+
if (typeof window === "undefined" && typeof globalThis === "undefined") {
|
|
438
|
+
return {};
|
|
439
|
+
}
|
|
440
|
+
let search = "";
|
|
441
|
+
try {
|
|
442
|
+
if (typeof window !== "undefined" && window.location) {
|
|
443
|
+
search = window.location.search;
|
|
444
|
+
if (!search && window.location.hash) {
|
|
445
|
+
const hashQueryIndex = window.location.hash.indexOf("?");
|
|
446
|
+
if (hashQueryIndex !== -1) {
|
|
447
|
+
search = window.location.hash.slice(hashQueryIndex);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
} catch {
|
|
452
|
+
return {};
|
|
453
|
+
}
|
|
454
|
+
const params = parseQueryString(search);
|
|
455
|
+
return {
|
|
456
|
+
...params.utm_source && { source: params.utm_source },
|
|
457
|
+
...params.utm_medium && { medium: params.utm_medium },
|
|
458
|
+
...params.utm_campaign && { campaign: params.utm_campaign },
|
|
459
|
+
...params.utm_term && { term: params.utm_term },
|
|
460
|
+
...params.utm_content && { content: params.utm_content }
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function getNavigatorConnection() {
|
|
464
|
+
if (typeof navigator === "undefined")
|
|
465
|
+
return null;
|
|
466
|
+
const nav = navigator;
|
|
467
|
+
return nav.connection || nav.mozConnection || nav.webkitConnection || null;
|
|
468
|
+
}
|
|
469
|
+
function getConnectionInfo() {
|
|
470
|
+
const connection = getNavigatorConnection();
|
|
471
|
+
if (!connection) {
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
const effectiveType = connection.effectiveType;
|
|
475
|
+
return {
|
|
476
|
+
effectiveType: effectiveType || "unknown",
|
|
477
|
+
downlink: connection.downlink,
|
|
478
|
+
rtt: connection.rtt,
|
|
479
|
+
saveData: connection.saveData
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function getConnectionType() {
|
|
483
|
+
const connection = getNavigatorConnection();
|
|
484
|
+
if (!connection || !connection.effectiveType) {
|
|
485
|
+
return "unknown";
|
|
486
|
+
}
|
|
487
|
+
const type = connection.effectiveType;
|
|
488
|
+
if (type === "slow-2g" || type === "2g" || type === "3g" || type === "4g") {
|
|
489
|
+
return type;
|
|
490
|
+
}
|
|
491
|
+
return "unknown";
|
|
492
|
+
}
|
|
493
|
+
var BOT_PATTERNS = [
|
|
494
|
+
"googlebot",
|
|
495
|
+
"bingbot",
|
|
496
|
+
"slurp",
|
|
497
|
+
"duckduckbot",
|
|
498
|
+
"baiduspider",
|
|
499
|
+
"yandexbot",
|
|
500
|
+
"sogou",
|
|
501
|
+
"exabot",
|
|
502
|
+
"facebot",
|
|
503
|
+
"ia_archiver",
|
|
504
|
+
"headless",
|
|
505
|
+
"phantom",
|
|
506
|
+
"selenium",
|
|
507
|
+
"puppeteer",
|
|
508
|
+
"playwright",
|
|
509
|
+
"webdriver",
|
|
510
|
+
"curl",
|
|
511
|
+
"wget",
|
|
512
|
+
"python-requests",
|
|
513
|
+
"python-urllib",
|
|
514
|
+
"java/",
|
|
515
|
+
"apache-httpclient",
|
|
516
|
+
"okhttp",
|
|
517
|
+
"axios/",
|
|
518
|
+
"bot",
|
|
519
|
+
"crawler",
|
|
520
|
+
"spider",
|
|
521
|
+
"scraper",
|
|
522
|
+
"lighthouse",
|
|
523
|
+
"pagespeed",
|
|
524
|
+
"gtmetrix"
|
|
525
|
+
];
|
|
526
|
+
function isBot() {
|
|
527
|
+
if (typeof navigator === "undefined") {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
const userAgent = navigator.userAgent?.toLowerCase() || "";
|
|
531
|
+
if (!userAgent) {
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
for (const pattern of BOT_PATTERNS) {
|
|
535
|
+
if (userAgent.includes(pattern)) {
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (navigator.webdriver === true) {
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
function debounce(func, wait) {
|
|
545
|
+
let timeoutId = null;
|
|
546
|
+
return function(...args) {
|
|
547
|
+
if (timeoutId !== null) {
|
|
548
|
+
clearTimeout(timeoutId);
|
|
549
|
+
}
|
|
550
|
+
timeoutId = setTimeout(() => {
|
|
551
|
+
func.apply(this, args);
|
|
552
|
+
timeoutId = null;
|
|
553
|
+
}, wait);
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
function throttle(func, limit) {
|
|
557
|
+
let inThrottle = false;
|
|
558
|
+
let lastArgs = null;
|
|
559
|
+
return function(...args) {
|
|
560
|
+
if (!inThrottle) {
|
|
561
|
+
func.apply(this, args);
|
|
562
|
+
inThrottle = true;
|
|
563
|
+
setTimeout(() => {
|
|
564
|
+
inThrottle = false;
|
|
565
|
+
if (lastArgs !== null) {
|
|
566
|
+
func.apply(this, lastArgs);
|
|
567
|
+
lastArgs = null;
|
|
568
|
+
}
|
|
569
|
+
}, limit);
|
|
570
|
+
} else {
|
|
571
|
+
lastArgs = args;
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
function supportsBeacon() {
|
|
576
|
+
if (capabilityCache.beacon !== null) {
|
|
577
|
+
return capabilityCache.beacon;
|
|
578
|
+
}
|
|
579
|
+
const supported = typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function";
|
|
580
|
+
capabilityCache.beacon = supported;
|
|
581
|
+
return supported;
|
|
582
|
+
}
|
|
583
|
+
function supportsFetch() {
|
|
584
|
+
if (capabilityCache.fetch !== null) {
|
|
585
|
+
return capabilityCache.fetch;
|
|
586
|
+
}
|
|
587
|
+
const supported = typeof fetch === "function";
|
|
588
|
+
capabilityCache.fetch = supported;
|
|
589
|
+
return supported;
|
|
590
|
+
}
|
|
591
|
+
function supportsCompression() {
|
|
592
|
+
if (capabilityCache.compression !== null) {
|
|
593
|
+
return capabilityCache.compression;
|
|
594
|
+
}
|
|
595
|
+
const supported = typeof CompressionStream !== "undefined";
|
|
596
|
+
capabilityCache.compression = supported;
|
|
597
|
+
return supported;
|
|
598
|
+
}
|
|
599
|
+
function resetCapabilityCache() {
|
|
600
|
+
capabilityCache.beacon = null;
|
|
601
|
+
capabilityCache.fetch = null;
|
|
602
|
+
capabilityCache.compression = null;
|
|
603
|
+
}
|
|
604
|
+
var MAX_EVENT_NAME_LENGTH = 100;
|
|
605
|
+
var MAX_STRING_LENGTH = 1000;
|
|
606
|
+
var MAX_OBJECT_DEPTH = 5;
|
|
607
|
+
function sanitizeEventName(name) {
|
|
608
|
+
if (typeof name !== "string") {
|
|
609
|
+
return "unknown_event";
|
|
610
|
+
}
|
|
611
|
+
if (!name.trim()) {
|
|
612
|
+
return "unknown_event";
|
|
613
|
+
}
|
|
614
|
+
return name.toLowerCase().trim().replace(/\s+/g, "_").replace(/[^a-z0-9_]/g, "").slice(0, MAX_EVENT_NAME_LENGTH);
|
|
615
|
+
}
|
|
616
|
+
function sanitizeValue(value, depth) {
|
|
617
|
+
if (value === null || value === undefined) {
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
if (typeof value === "string") {
|
|
621
|
+
return value.slice(0, MAX_STRING_LENGTH);
|
|
622
|
+
}
|
|
623
|
+
if (typeof value === "number") {
|
|
624
|
+
if (!Number.isFinite(value)) {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
return value;
|
|
628
|
+
}
|
|
629
|
+
if (typeof value === "boolean") {
|
|
630
|
+
return value;
|
|
631
|
+
}
|
|
632
|
+
if (value instanceof Date) {
|
|
633
|
+
return value.toISOString();
|
|
634
|
+
}
|
|
635
|
+
if (depth >= MAX_OBJECT_DEPTH) {
|
|
636
|
+
return "[max depth]";
|
|
637
|
+
}
|
|
638
|
+
if (Array.isArray(value)) {
|
|
639
|
+
return value.slice(0, 100).map((item) => sanitizeValue(item, depth + 1));
|
|
640
|
+
}
|
|
641
|
+
if (typeof value === "object") {
|
|
642
|
+
try {
|
|
643
|
+
const result = {};
|
|
644
|
+
const keys = Object.keys(value);
|
|
645
|
+
for (const key of keys.slice(0, 100)) {
|
|
646
|
+
const propValue = value[key];
|
|
647
|
+
if (propValue !== undefined) {
|
|
648
|
+
result[key] = sanitizeValue(propValue, depth + 1);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return result;
|
|
652
|
+
} catch {
|
|
653
|
+
return "[object]";
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
function sanitizeProperties(props) {
|
|
659
|
+
if (!props || typeof props !== "object" || Array.isArray(props)) {
|
|
660
|
+
return {};
|
|
661
|
+
}
|
|
662
|
+
try {
|
|
663
|
+
return sanitizeValue(props, 0);
|
|
664
|
+
} catch {
|
|
665
|
+
return {};
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function getPageUrl() {
|
|
669
|
+
try {
|
|
670
|
+
if (typeof window !== "undefined" && window.location) {
|
|
671
|
+
return window.location.href;
|
|
672
|
+
}
|
|
673
|
+
} catch {}
|
|
674
|
+
return "";
|
|
675
|
+
}
|
|
676
|
+
function getPageTitle() {
|
|
677
|
+
try {
|
|
678
|
+
if (typeof document !== "undefined" && document.title) {
|
|
679
|
+
return document.title;
|
|
680
|
+
}
|
|
681
|
+
} catch {}
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
function getReferrer() {
|
|
685
|
+
try {
|
|
686
|
+
if (typeof document !== "undefined" && document.referrer) {
|
|
687
|
+
return document.referrer;
|
|
688
|
+
}
|
|
689
|
+
} catch {}
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
function getUserAgent() {
|
|
693
|
+
try {
|
|
694
|
+
if (typeof navigator !== "undefined" && navigator.userAgent) {
|
|
695
|
+
return navigator.userAgent;
|
|
696
|
+
}
|
|
697
|
+
} catch {}
|
|
698
|
+
return "";
|
|
699
|
+
}
|
|
700
|
+
function getScreenDimensions() {
|
|
701
|
+
try {
|
|
702
|
+
if (typeof window !== "undefined" && window.screen) {
|
|
703
|
+
return {
|
|
704
|
+
width: window.screen.width || 0,
|
|
705
|
+
height: window.screen.height || 0
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
} catch {}
|
|
709
|
+
return { width: 0, height: 0 };
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// src/network.ts
|
|
713
|
+
var OFFLINE_QUEUE_KEY = "_analytics_offline_queue";
|
|
714
|
+
var SENT_REQUESTS_KEY = "_analytics_sent_requests";
|
|
715
|
+
var MAX_OFFLINE_EVENTS = 100;
|
|
716
|
+
var MAX_TRACKED_REQUESTS = 100;
|
|
717
|
+
var MAX_BEACON_PAYLOAD = 64 * 1024;
|
|
718
|
+
var BASE_RETRY_DELAY = 1000;
|
|
719
|
+
var debugMode = false;
|
|
720
|
+
function setDebugMode(enabled) {
|
|
721
|
+
debugMode = enabled;
|
|
722
|
+
}
|
|
723
|
+
function debugLog(message, ...args) {
|
|
724
|
+
if (debugMode && typeof console !== "undefined") {
|
|
725
|
+
console.log(`[Analytics Network] ${message}`, ...args);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
function logError(message, error) {
|
|
729
|
+
if (debugMode && typeof console !== "undefined") {
|
|
730
|
+
console.error(`[Analytics Network] ${message}`, error);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
class NetworkError extends Error {
|
|
735
|
+
statusCode;
|
|
736
|
+
retry;
|
|
737
|
+
constructor(message, statusCode, retry) {
|
|
738
|
+
super(message);
|
|
739
|
+
this.statusCode = statusCode;
|
|
740
|
+
this.retry = retry;
|
|
741
|
+
this.name = "NetworkError";
|
|
742
|
+
if (Error.captureStackTrace) {
|
|
743
|
+
Error.captureStackTrace(this, NetworkError);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
function sendBeacon(url, data) {
|
|
748
|
+
try {
|
|
749
|
+
if (!supportsBeacon()) {
|
|
750
|
+
debugLog("sendBeacon not available");
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
const payload = JSON.stringify(data);
|
|
754
|
+
if (payload.length > MAX_BEACON_PAYLOAD) {
|
|
755
|
+
debugLog("Payload too large for sendBeacon:", payload.length);
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
const blob = new Blob([payload], { type: "application/json" });
|
|
759
|
+
const success = navigator.sendBeacon(url, blob);
|
|
760
|
+
if (success) {
|
|
761
|
+
debugLog("Beacon sent successfully");
|
|
762
|
+
} else {
|
|
763
|
+
debugLog("Beacon failed to queue");
|
|
764
|
+
}
|
|
765
|
+
return success;
|
|
766
|
+
} catch (error) {
|
|
767
|
+
logError("sendBeacon error:", error);
|
|
768
|
+
return false;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
async function sendFetch(url, data, apiKey) {
|
|
772
|
+
if (!supportsFetch()) {
|
|
773
|
+
throw new NetworkError("Fetch API not available", undefined, false);
|
|
774
|
+
}
|
|
775
|
+
try {
|
|
776
|
+
const payload = JSON.stringify(data);
|
|
777
|
+
const headers = {
|
|
778
|
+
"Content-Type": "application/json"
|
|
779
|
+
};
|
|
780
|
+
if (apiKey) {
|
|
781
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
782
|
+
}
|
|
783
|
+
const response = await fetch(url, {
|
|
784
|
+
method: "POST",
|
|
785
|
+
headers,
|
|
786
|
+
body: payload,
|
|
787
|
+
keepalive: true
|
|
788
|
+
});
|
|
789
|
+
if (!response.ok) {
|
|
790
|
+
const shouldRetry = response.status >= 500 || response.status === 429;
|
|
791
|
+
throw new NetworkError(`HTTP error: ${response.status} ${response.statusText}`, response.status, shouldRetry);
|
|
792
|
+
}
|
|
793
|
+
debugLog("Fetch sent successfully, status:", response.status);
|
|
794
|
+
} catch (error) {
|
|
795
|
+
if (error instanceof NetworkError) {
|
|
796
|
+
throw error;
|
|
797
|
+
}
|
|
798
|
+
throw new NetworkError(error instanceof Error ? error.message : "Network request failed", undefined, true);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
function delay(ms) {
|
|
802
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
803
|
+
}
|
|
804
|
+
async function sendWithRetry(url, data, maxRetries = 3, apiKey) {
|
|
805
|
+
let lastError;
|
|
806
|
+
for (let attempt = 0;attempt <= maxRetries; attempt++) {
|
|
807
|
+
try {
|
|
808
|
+
await sendFetch(url, data, apiKey);
|
|
809
|
+
debugLog("Request succeeded on attempt:", attempt + 1);
|
|
810
|
+
return;
|
|
811
|
+
} catch (error) {
|
|
812
|
+
lastError = error instanceof NetworkError ? error : new NetworkError("Unknown error", undefined, true);
|
|
813
|
+
if (!lastError.retry) {
|
|
814
|
+
debugLog("Not retrying, error is not retryable:", lastError.message);
|
|
815
|
+
throw lastError;
|
|
816
|
+
}
|
|
817
|
+
if (attempt >= maxRetries) {
|
|
818
|
+
debugLog("Max retries exceeded");
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
const backoffDelay = BASE_RETRY_DELAY * Math.pow(2, attempt);
|
|
822
|
+
debugLog(`Retry ${attempt + 1}/${maxRetries} after ${backoffDelay}ms`);
|
|
823
|
+
await delay(backoffDelay);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
throw lastError || new NetworkError("All retries failed", undefined, false);
|
|
827
|
+
}
|
|
828
|
+
async function compressData(data) {
|
|
829
|
+
if (!supportsCompression()) {
|
|
830
|
+
debugLog("CompressionStream not available, returning uncompressed");
|
|
831
|
+
return data;
|
|
832
|
+
}
|
|
833
|
+
try {
|
|
834
|
+
const blob = new Blob([data], { type: "application/json" });
|
|
835
|
+
const compressionStream = new CompressionStream("gzip");
|
|
836
|
+
const compressedStream = blob.stream().pipeThrough(compressionStream);
|
|
837
|
+
const compressedBlob = await new Response(compressedStream).blob();
|
|
838
|
+
debugLog(`Compressed ${data.length} bytes to ${compressedBlob.size} bytes (${Math.round((1 - compressedBlob.size / data.length) * 100)}% reduction)`);
|
|
839
|
+
return compressedBlob;
|
|
840
|
+
} catch (error) {
|
|
841
|
+
logError("Compression failed, returning uncompressed:", error);
|
|
842
|
+
return data;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
async function sendCompressed(url, data, apiKey) {
|
|
846
|
+
if (!supportsFetch()) {
|
|
847
|
+
throw new NetworkError("Fetch API not available", undefined, false);
|
|
848
|
+
}
|
|
849
|
+
try {
|
|
850
|
+
const jsonString = JSON.stringify(data);
|
|
851
|
+
const compressed = await compressData(jsonString);
|
|
852
|
+
const isCompressed = compressed instanceof Blob;
|
|
853
|
+
const headers = {
|
|
854
|
+
"Content-Type": "application/json",
|
|
855
|
+
...isCompressed && { "Content-Encoding": "gzip" }
|
|
856
|
+
};
|
|
857
|
+
if (apiKey) {
|
|
858
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
859
|
+
}
|
|
860
|
+
const response = await fetch(url, {
|
|
861
|
+
method: "POST",
|
|
862
|
+
headers,
|
|
863
|
+
body: compressed,
|
|
864
|
+
keepalive: true
|
|
865
|
+
});
|
|
866
|
+
if (!response.ok) {
|
|
867
|
+
const shouldRetry = response.status >= 500 || response.status === 429;
|
|
868
|
+
throw new NetworkError(`HTTP error: ${response.status}`, response.status, shouldRetry);
|
|
869
|
+
}
|
|
870
|
+
debugLog("Compressed fetch sent successfully");
|
|
871
|
+
} catch (error) {
|
|
872
|
+
if (error instanceof NetworkError) {
|
|
873
|
+
throw error;
|
|
874
|
+
}
|
|
875
|
+
throw new NetworkError(error instanceof Error ? error.message : "Compressed send failed", undefined, true);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
function queueOfflineEvents(events) {
|
|
879
|
+
try {
|
|
880
|
+
if (typeof localStorage === "undefined") {
|
|
881
|
+
debugLog("localStorage not available for offline queue");
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
const existingQueue = getOfflineEvents();
|
|
885
|
+
const combined = [...existingQueue, ...events];
|
|
886
|
+
const limited = combined.length > MAX_OFFLINE_EVENTS ? combined.slice(-MAX_OFFLINE_EVENTS) : combined;
|
|
887
|
+
localStorage.setItem(OFFLINE_QUEUE_KEY, JSON.stringify(limited));
|
|
888
|
+
debugLog(`Queued ${events.length} offline events (total: ${limited.length})`);
|
|
889
|
+
} catch (error) {
|
|
890
|
+
logError("Failed to queue offline events:", error);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function getOfflineEvents() {
|
|
894
|
+
try {
|
|
895
|
+
if (typeof localStorage === "undefined") {
|
|
896
|
+
return [];
|
|
897
|
+
}
|
|
898
|
+
const stored = localStorage.getItem(OFFLINE_QUEUE_KEY);
|
|
899
|
+
if (!stored) {
|
|
900
|
+
return [];
|
|
901
|
+
}
|
|
902
|
+
const parsed = JSON.parse(stored);
|
|
903
|
+
if (!Array.isArray(parsed)) {
|
|
904
|
+
debugLog("Invalid offline queue format, clearing");
|
|
905
|
+
clearOfflineEvents();
|
|
906
|
+
return [];
|
|
907
|
+
}
|
|
908
|
+
return parsed;
|
|
909
|
+
} catch (error) {
|
|
910
|
+
logError("Failed to get offline events:", error);
|
|
911
|
+
return [];
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
function clearOfflineEvents() {
|
|
915
|
+
try {
|
|
916
|
+
if (typeof localStorage !== "undefined") {
|
|
917
|
+
localStorage.removeItem(OFFLINE_QUEUE_KEY);
|
|
918
|
+
debugLog("Cleared offline queue");
|
|
919
|
+
}
|
|
920
|
+
} catch (error) {
|
|
921
|
+
logError("Failed to clear offline events:", error);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
function isOffline() {
|
|
925
|
+
if (typeof navigator === "undefined") {
|
|
926
|
+
return false;
|
|
927
|
+
}
|
|
928
|
+
return navigator.onLine === false;
|
|
929
|
+
}
|
|
930
|
+
function onOnline(callback) {
|
|
931
|
+
if (typeof window === "undefined") {
|
|
932
|
+
return () => {};
|
|
933
|
+
}
|
|
934
|
+
window.addEventListener("online", callback);
|
|
935
|
+
return () => {
|
|
936
|
+
window.removeEventListener("online", callback);
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
function getSentRequestIds() {
|
|
940
|
+
try {
|
|
941
|
+
if (typeof sessionStorage === "undefined") {
|
|
942
|
+
return [];
|
|
943
|
+
}
|
|
944
|
+
const stored = sessionStorage.getItem(SENT_REQUESTS_KEY);
|
|
945
|
+
if (!stored) {
|
|
946
|
+
return [];
|
|
947
|
+
}
|
|
948
|
+
const parsed = JSON.parse(stored);
|
|
949
|
+
if (!Array.isArray(parsed)) {
|
|
950
|
+
return [];
|
|
951
|
+
}
|
|
952
|
+
return parsed;
|
|
953
|
+
} catch {
|
|
954
|
+
return [];
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
function saveSentRequestIds(ids) {
|
|
958
|
+
try {
|
|
959
|
+
if (typeof sessionStorage !== "undefined") {
|
|
960
|
+
sessionStorage.setItem(SENT_REQUESTS_KEY, JSON.stringify(ids));
|
|
961
|
+
}
|
|
962
|
+
} catch {}
|
|
963
|
+
}
|
|
964
|
+
function wasRequestSent(requestId) {
|
|
965
|
+
const sentIds = getSentRequestIds();
|
|
966
|
+
return sentIds.includes(requestId);
|
|
967
|
+
}
|
|
968
|
+
function markRequestSent(requestId) {
|
|
969
|
+
try {
|
|
970
|
+
const sentIds = getSentRequestIds();
|
|
971
|
+
sentIds.push(requestId);
|
|
972
|
+
const limited = sentIds.length > MAX_TRACKED_REQUESTS ? sentIds.slice(-MAX_TRACKED_REQUESTS) : sentIds;
|
|
973
|
+
saveSentRequestIds(limited);
|
|
974
|
+
debugLog("Marked request as sent:", requestId);
|
|
975
|
+
} catch (error) {
|
|
976
|
+
logError("Failed to mark request as sent:", error);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
async function send(url, data, options = {}) {
|
|
980
|
+
const {
|
|
981
|
+
useBeacon = true,
|
|
982
|
+
compress = false,
|
|
983
|
+
retry = true,
|
|
984
|
+
requestId,
|
|
985
|
+
apiKey
|
|
986
|
+
} = options;
|
|
987
|
+
if (requestId && wasRequestSent(requestId)) {
|
|
988
|
+
debugLog("Skipping duplicate request:", requestId);
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
if (isOffline()) {
|
|
992
|
+
debugLog("Offline, queuing events");
|
|
993
|
+
queueOfflineEvents(data.events);
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
try {
|
|
997
|
+
if (useBeacon && sendBeacon(url, data)) {
|
|
998
|
+
if (requestId) {
|
|
999
|
+
markRequestSent(requestId);
|
|
1000
|
+
}
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
if (compress) {
|
|
1004
|
+
await sendCompressed(url, data, apiKey);
|
|
1005
|
+
} else if (retry) {
|
|
1006
|
+
await sendWithRetry(url, data, 3, apiKey);
|
|
1007
|
+
} else {
|
|
1008
|
+
await sendFetch(url, data, apiKey);
|
|
1009
|
+
}
|
|
1010
|
+
if (requestId) {
|
|
1011
|
+
markRequestSent(requestId);
|
|
1012
|
+
}
|
|
1013
|
+
} catch (error) {
|
|
1014
|
+
logError("Send failed:", error);
|
|
1015
|
+
if (error instanceof NetworkError && error.retry) {
|
|
1016
|
+
queueOfflineEvents(data.events);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// src/network-adapter.ts
|
|
1022
|
+
var EventPriority;
|
|
1023
|
+
((EventPriority2) => {
|
|
1024
|
+
EventPriority2[EventPriority2["LOW"] = 0] = "LOW";
|
|
1025
|
+
EventPriority2[EventPriority2["NORMAL"] = 1] = "NORMAL";
|
|
1026
|
+
EventPriority2[EventPriority2["HIGH"] = 2] = "HIGH";
|
|
1027
|
+
})(EventPriority ||= {});
|
|
1028
|
+
var SLOW_2G_SETTINGS = {
|
|
1029
|
+
maxQueueSize: 3,
|
|
1030
|
+
flushInterval: 15000,
|
|
1031
|
+
compressionEnabled: true,
|
|
1032
|
+
batchingStrategy: "aggressive"
|
|
1033
|
+
};
|
|
1034
|
+
var TWO_G_SETTINGS = {
|
|
1035
|
+
maxQueueSize: 5,
|
|
1036
|
+
flushInterval: 1e4,
|
|
1037
|
+
compressionEnabled: true,
|
|
1038
|
+
batchingStrategy: "aggressive"
|
|
1039
|
+
};
|
|
1040
|
+
var THREE_G_SETTINGS = {
|
|
1041
|
+
maxQueueSize: 10,
|
|
1042
|
+
flushInterval: 5000,
|
|
1043
|
+
compressionEnabled: false,
|
|
1044
|
+
batchingStrategy: "balanced"
|
|
1045
|
+
};
|
|
1046
|
+
var FOUR_G_SETTINGS = {
|
|
1047
|
+
maxQueueSize: 20,
|
|
1048
|
+
flushInterval: 3000,
|
|
1049
|
+
compressionEnabled: false,
|
|
1050
|
+
batchingStrategy: "conservative"
|
|
1051
|
+
};
|
|
1052
|
+
var DATA_SAVER_SETTINGS = {
|
|
1053
|
+
maxQueueSize: 10,
|
|
1054
|
+
flushInterval: 20000,
|
|
1055
|
+
compressionEnabled: true,
|
|
1056
|
+
batchingStrategy: "aggressive"
|
|
1057
|
+
};
|
|
1058
|
+
var MAX_BACKOFF_DELAY = 30000;
|
|
1059
|
+
var BASE_BACKOFF_DELAY = 1000;
|
|
1060
|
+
function getNavigatorConnection2() {
|
|
1061
|
+
if (typeof navigator === "undefined") {
|
|
1062
|
+
return null;
|
|
1063
|
+
}
|
|
1064
|
+
const nav = navigator;
|
|
1065
|
+
return nav.connection || nav.mozConnection || nav.webkitConnection || null;
|
|
1066
|
+
}
|
|
1067
|
+
function isDataSaverEnabled() {
|
|
1068
|
+
const connection = getNavigatorConnection2();
|
|
1069
|
+
return connection?.saveData === true;
|
|
1070
|
+
}
|
|
1071
|
+
function getRtt() {
|
|
1072
|
+
const connection = getNavigatorConnection2();
|
|
1073
|
+
return connection?.rtt ?? 100;
|
|
1074
|
+
}
|
|
1075
|
+
function getDownlink() {
|
|
1076
|
+
const connection = getNavigatorConnection2();
|
|
1077
|
+
return connection?.downlink ?? 10;
|
|
1078
|
+
}
|
|
1079
|
+
function sortByPriority(events) {
|
|
1080
|
+
return [...events].sort((a, b) => b.priority - a.priority);
|
|
1081
|
+
}
|
|
1082
|
+
function assignPriority(event) {
|
|
1083
|
+
const eventName = event.event.toLowerCase();
|
|
1084
|
+
if (eventName.includes("purchase") || eventName.includes("conversion") || eventName.includes("signup") || eventName.includes("error") || eventName.includes("checkout") || eventName.includes("subscribe")) {
|
|
1085
|
+
return { ...event, priority: 2 /* HIGH */ };
|
|
1086
|
+
}
|
|
1087
|
+
if (eventName === "pageview" || eventName.includes("impression") || eventName.includes("scroll") || eventName.includes("hover")) {
|
|
1088
|
+
return { ...event, priority: 0 /* LOW */ };
|
|
1089
|
+
}
|
|
1090
|
+
return { ...event, priority: 1 /* NORMAL */ };
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
class RetryStrategy {
|
|
1094
|
+
attempts = 0;
|
|
1095
|
+
maxRetries;
|
|
1096
|
+
constructor(connectionType = "unknown") {
|
|
1097
|
+
switch (connectionType) {
|
|
1098
|
+
case "slow-2g":
|
|
1099
|
+
this.maxRetries = 5;
|
|
1100
|
+
break;
|
|
1101
|
+
case "2g":
|
|
1102
|
+
this.maxRetries = 3;
|
|
1103
|
+
break;
|
|
1104
|
+
case "3g":
|
|
1105
|
+
this.maxRetries = 2;
|
|
1106
|
+
break;
|
|
1107
|
+
default:
|
|
1108
|
+
this.maxRetries = 2;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
getBackoffDelay() {
|
|
1112
|
+
const delay2 = BASE_BACKOFF_DELAY * Math.pow(2, this.attempts);
|
|
1113
|
+
return Math.min(delay2, MAX_BACKOFF_DELAY);
|
|
1114
|
+
}
|
|
1115
|
+
shouldRetry(error) {
|
|
1116
|
+
if (this.attempts >= this.maxRetries) {
|
|
1117
|
+
return false;
|
|
1118
|
+
}
|
|
1119
|
+
if (error instanceof NetworkError) {
|
|
1120
|
+
if (error.retry === false) {
|
|
1121
|
+
return false;
|
|
1122
|
+
}
|
|
1123
|
+
if (error.statusCode !== undefined && error.statusCode >= 400 && error.statusCode < 500 && error.statusCode !== 429) {
|
|
1124
|
+
return false;
|
|
1125
|
+
}
|
|
1126
|
+
return true;
|
|
1127
|
+
}
|
|
1128
|
+
return true;
|
|
1129
|
+
}
|
|
1130
|
+
recordAttempt() {
|
|
1131
|
+
this.attempts++;
|
|
1132
|
+
}
|
|
1133
|
+
reset() {
|
|
1134
|
+
this.attempts = 0;
|
|
1135
|
+
}
|
|
1136
|
+
getAttempts() {
|
|
1137
|
+
return this.attempts;
|
|
1138
|
+
}
|
|
1139
|
+
getMaxRetries() {
|
|
1140
|
+
return this.maxRetries;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
class NetworkAdapter {
|
|
1145
|
+
currentSettings;
|
|
1146
|
+
connection = null;
|
|
1147
|
+
changeCallbacks = [];
|
|
1148
|
+
boundHandleChange;
|
|
1149
|
+
cachedConnectionType = "unknown";
|
|
1150
|
+
lastUpdateTime = 0;
|
|
1151
|
+
cacheTimeout = 1000;
|
|
1152
|
+
constructor() {
|
|
1153
|
+
this.connection = getNavigatorConnection2();
|
|
1154
|
+
this.currentSettings = this.computeSettings();
|
|
1155
|
+
this.boundHandleChange = debounce(() => {
|
|
1156
|
+
this.handleConnectionChange();
|
|
1157
|
+
}, 100);
|
|
1158
|
+
if (this.connection?.addEventListener) {
|
|
1159
|
+
this.connection.addEventListener("change", this.boundHandleChange);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
computeSettings() {
|
|
1163
|
+
if (isDataSaverEnabled()) {
|
|
1164
|
+
return { ...DATA_SAVER_SETTINGS };
|
|
1165
|
+
}
|
|
1166
|
+
const connectionType = this.getConnectionType();
|
|
1167
|
+
switch (connectionType) {
|
|
1168
|
+
case "slow-2g":
|
|
1169
|
+
return { ...SLOW_2G_SETTINGS };
|
|
1170
|
+
case "2g":
|
|
1171
|
+
return { ...TWO_G_SETTINGS };
|
|
1172
|
+
case "3g":
|
|
1173
|
+
return { ...THREE_G_SETTINGS };
|
|
1174
|
+
case "4g":
|
|
1175
|
+
case "unknown":
|
|
1176
|
+
default:
|
|
1177
|
+
return { ...FOUR_G_SETTINGS };
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
handleConnectionChange() {
|
|
1181
|
+
this.lastUpdateTime = 0;
|
|
1182
|
+
const newSettings = this.computeSettings();
|
|
1183
|
+
if (newSettings.maxQueueSize === this.currentSettings.maxQueueSize && newSettings.flushInterval === this.currentSettings.flushInterval && newSettings.compressionEnabled === this.currentSettings.compressionEnabled) {
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
this.currentSettings = newSettings;
|
|
1187
|
+
for (const callback of this.changeCallbacks) {
|
|
1188
|
+
try {
|
|
1189
|
+
callback(newSettings);
|
|
1190
|
+
} catch {}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
getConnectionType() {
|
|
1194
|
+
const now = Date.now();
|
|
1195
|
+
if (now - this.lastUpdateTime < this.cacheTimeout) {
|
|
1196
|
+
return this.cachedConnectionType;
|
|
1197
|
+
}
|
|
1198
|
+
this.cachedConnectionType = getConnectionType();
|
|
1199
|
+
this.lastUpdateTime = now;
|
|
1200
|
+
return this.cachedConnectionType;
|
|
1201
|
+
}
|
|
1202
|
+
getSettings() {
|
|
1203
|
+
return { ...this.currentSettings };
|
|
1204
|
+
}
|
|
1205
|
+
onConnectionChange(callback) {
|
|
1206
|
+
this.changeCallbacks.push(callback);
|
|
1207
|
+
return () => {
|
|
1208
|
+
const index = this.changeCallbacks.indexOf(callback);
|
|
1209
|
+
if (index > -1) {
|
|
1210
|
+
this.changeCallbacks.splice(index, 1);
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
shouldCompress() {
|
|
1215
|
+
if (!supportsCompression()) {
|
|
1216
|
+
return false;
|
|
1217
|
+
}
|
|
1218
|
+
if (isDataSaverEnabled()) {
|
|
1219
|
+
return true;
|
|
1220
|
+
}
|
|
1221
|
+
return this.currentSettings.compressionEnabled;
|
|
1222
|
+
}
|
|
1223
|
+
getOptimalBatchSize() {
|
|
1224
|
+
const rtt = getRtt();
|
|
1225
|
+
const downlink = getDownlink();
|
|
1226
|
+
if (isDataSaverEnabled()) {
|
|
1227
|
+
return 15;
|
|
1228
|
+
}
|
|
1229
|
+
if (rtt > 500) {
|
|
1230
|
+
return Math.min(this.currentSettings.maxQueueSize + 5, 20);
|
|
1231
|
+
}
|
|
1232
|
+
if (downlink < 1) {
|
|
1233
|
+
return Math.min(this.currentSettings.maxQueueSize + 3, 15);
|
|
1234
|
+
}
|
|
1235
|
+
return this.currentSettings.maxQueueSize;
|
|
1236
|
+
}
|
|
1237
|
+
getRetryStrategy() {
|
|
1238
|
+
return new RetryStrategy(this.getConnectionType());
|
|
1239
|
+
}
|
|
1240
|
+
isSlowConnection() {
|
|
1241
|
+
const type = this.getConnectionType();
|
|
1242
|
+
return type === "slow-2g" || type === "2g";
|
|
1243
|
+
}
|
|
1244
|
+
getRecommendedTimeout() {
|
|
1245
|
+
const rtt = getRtt();
|
|
1246
|
+
const type = this.getConnectionType();
|
|
1247
|
+
let timeout = 1e4;
|
|
1248
|
+
switch (type) {
|
|
1249
|
+
case "slow-2g":
|
|
1250
|
+
timeout = 30000;
|
|
1251
|
+
break;
|
|
1252
|
+
case "2g":
|
|
1253
|
+
timeout = 20000;
|
|
1254
|
+
break;
|
|
1255
|
+
case "3g":
|
|
1256
|
+
timeout = 15000;
|
|
1257
|
+
break;
|
|
1258
|
+
}
|
|
1259
|
+
timeout += rtt * 2;
|
|
1260
|
+
return timeout;
|
|
1261
|
+
}
|
|
1262
|
+
destroy() {
|
|
1263
|
+
if (this.connection?.removeEventListener) {
|
|
1264
|
+
this.connection.removeEventListener("change", this.boundHandleChange);
|
|
1265
|
+
}
|
|
1266
|
+
this.changeCallbacks = [];
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
function delay2(ms) {
|
|
1270
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1271
|
+
}
|
|
1272
|
+
async function sendAdaptive(url, data, adapter) {
|
|
1273
|
+
const retryStrategy = adapter.getRetryStrategy();
|
|
1274
|
+
const shouldCompress = adapter.shouldCompress();
|
|
1275
|
+
if (adapter.isSlowConnection()) {
|
|
1276
|
+
const prioritizedEvents = data.events.map((e) => assignPriority(e));
|
|
1277
|
+
const sortedEvents = sortByPriority(prioritizedEvents);
|
|
1278
|
+
data = {
|
|
1279
|
+
...data,
|
|
1280
|
+
events: sortedEvents
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
const connectionType = getConnectionType();
|
|
1284
|
+
while (true) {
|
|
1285
|
+
try {
|
|
1286
|
+
if (connectionType === "4g" || connectionType === "unknown") {
|
|
1287
|
+
if (sendBeacon(url, data)) {
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
if (shouldCompress) {
|
|
1292
|
+
await sendCompressed(url, data);
|
|
1293
|
+
} else {
|
|
1294
|
+
await sendFetch(url, data);
|
|
1295
|
+
}
|
|
1296
|
+
return;
|
|
1297
|
+
} catch (error) {
|
|
1298
|
+
retryStrategy.recordAttempt();
|
|
1299
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1300
|
+
if (!retryStrategy.shouldRetry(err)) {
|
|
1301
|
+
throw error;
|
|
1302
|
+
}
|
|
1303
|
+
const backoffDelay = retryStrategy.getBackoffDelay();
|
|
1304
|
+
await delay2(backoffDelay);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
var globalAdapter = null;
|
|
1309
|
+
function getNetworkAdapter() {
|
|
1310
|
+
if (!globalAdapter) {
|
|
1311
|
+
globalAdapter = new NetworkAdapter;
|
|
1312
|
+
}
|
|
1313
|
+
return globalAdapter;
|
|
1314
|
+
}
|
|
1315
|
+
function resetNetworkAdapter() {
|
|
1316
|
+
if (globalAdapter) {
|
|
1317
|
+
globalAdapter.destroy();
|
|
1318
|
+
globalAdapter = null;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// src/analytics.ts
|
|
1323
|
+
var PAGEVIEW_DEBOUNCE_MS = 100;
|
|
1324
|
+
|
|
1325
|
+
class Analytics {
|
|
1326
|
+
config;
|
|
1327
|
+
queue = [];
|
|
1328
|
+
visitorId;
|
|
1329
|
+
sessionId;
|
|
1330
|
+
flushTimer = null;
|
|
1331
|
+
isInitialized = false;
|
|
1332
|
+
lastPageUrl = "";
|
|
1333
|
+
boundHandlePageUnload;
|
|
1334
|
+
boundHandleOnline;
|
|
1335
|
+
boundHandleOffline;
|
|
1336
|
+
boundHandlePopstate;
|
|
1337
|
+
debouncedTrackPageView;
|
|
1338
|
+
originalPushState = null;
|
|
1339
|
+
originalReplaceState = null;
|
|
1340
|
+
adapter = null;
|
|
1341
|
+
adapterCleanup = null;
|
|
1342
|
+
useAdaptiveNetwork;
|
|
1343
|
+
constructor(config) {
|
|
1344
|
+
if (!config.apiKey || typeof config.apiKey !== "string") {
|
|
1345
|
+
throw new Error("[Analytics] API key is required");
|
|
1346
|
+
}
|
|
1347
|
+
this.config = {
|
|
1348
|
+
apiKey: config.apiKey,
|
|
1349
|
+
endpoint: config.endpoint ?? DEFAULT_CONFIG.endpoint,
|
|
1350
|
+
debug: config.debug ?? DEFAULT_CONFIG.debug,
|
|
1351
|
+
maxQueueSize: config.maxQueueSize ?? DEFAULT_CONFIG.maxQueueSize,
|
|
1352
|
+
flushInterval: config.flushInterval ?? DEFAULT_CONFIG.flushInterval,
|
|
1353
|
+
autoTrack: config.autoTrack ?? DEFAULT_CONFIG.autoTrack,
|
|
1354
|
+
adaptiveNetwork: config.adaptiveNetwork ?? DEFAULT_CONFIG.adaptiveNetwork
|
|
1355
|
+
};
|
|
1356
|
+
setDebugMode(this.config.debug);
|
|
1357
|
+
this.visitorId = getVisitorId();
|
|
1358
|
+
this.sessionId = getSessionId();
|
|
1359
|
+
this.boundHandlePageUnload = this.handlePageUnload.bind(this);
|
|
1360
|
+
this.boundHandleOnline = this.handleOnline.bind(this);
|
|
1361
|
+
this.boundHandleOffline = this.handleOffline.bind(this);
|
|
1362
|
+
this.boundHandlePopstate = this.handlePopstate.bind(this);
|
|
1363
|
+
this.debouncedTrackPageView = debounce(() => {
|
|
1364
|
+
this.trackPageViewInternal();
|
|
1365
|
+
}, PAGEVIEW_DEBOUNCE_MS);
|
|
1366
|
+
this.useAdaptiveNetwork = this.config.adaptiveNetwork;
|
|
1367
|
+
if (this.useAdaptiveNetwork) {
|
|
1368
|
+
this.initNetworkAdapter();
|
|
1369
|
+
}
|
|
1370
|
+
if (isBot()) {
|
|
1371
|
+
this.log("Bot detected, skipping initialization");
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
this.init();
|
|
1375
|
+
}
|
|
1376
|
+
init() {
|
|
1377
|
+
if (this.isInitialized) {
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
this.isInitialized = true;
|
|
1381
|
+
this.lastPageUrl = getPageUrl();
|
|
1382
|
+
if (this.config.autoTrack) {
|
|
1383
|
+
this.trackPageViewInternal();
|
|
1384
|
+
}
|
|
1385
|
+
if (this.config.autoTrack) {
|
|
1386
|
+
this.setupPageViewTracking();
|
|
1387
|
+
}
|
|
1388
|
+
this.startFlushTimer();
|
|
1389
|
+
if (typeof window !== "undefined") {
|
|
1390
|
+
window.addEventListener("beforeunload", this.boundHandlePageUnload);
|
|
1391
|
+
window.addEventListener("online", this.boundHandleOnline);
|
|
1392
|
+
window.addEventListener("offline", this.boundHandleOffline);
|
|
1393
|
+
}
|
|
1394
|
+
this.sendOfflineEvents();
|
|
1395
|
+
this.log("Initialized successfully");
|
|
1396
|
+
}
|
|
1397
|
+
initNetworkAdapter() {
|
|
1398
|
+
if (!this.useAdaptiveNetwork) {
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
try {
|
|
1402
|
+
this.adapter = new NetworkAdapter;
|
|
1403
|
+
this.applyAdaptiveSettings(this.adapter.getSettings());
|
|
1404
|
+
this.adapterCleanup = this.adapter.onConnectionChange((settings) => {
|
|
1405
|
+
this.applyAdaptiveSettings(settings);
|
|
1406
|
+
});
|
|
1407
|
+
} catch (error) {
|
|
1408
|
+
this.warn("Failed to initialize network adapter", error);
|
|
1409
|
+
this.adapter = null;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
applyAdaptiveSettings(settings) {
|
|
1413
|
+
const oldFlushInterval = this.config.flushInterval;
|
|
1414
|
+
this.config = {
|
|
1415
|
+
...this.config,
|
|
1416
|
+
maxQueueSize: settings.maxQueueSize,
|
|
1417
|
+
flushInterval: settings.flushInterval
|
|
1418
|
+
};
|
|
1419
|
+
if (settings.flushInterval !== oldFlushInterval && this.isInitialized) {
|
|
1420
|
+
this.stopFlushTimer();
|
|
1421
|
+
this.startFlushTimer();
|
|
1422
|
+
}
|
|
1423
|
+
this.log("Adapted to network conditions:", settings);
|
|
1424
|
+
}
|
|
1425
|
+
track(eventName, properties = {}) {
|
|
1426
|
+
if (!this.isInitialized) {
|
|
1427
|
+
this.warn("SDK not initialized, event not tracked");
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
try {
|
|
1431
|
+
const sanitizedName = sanitizeEventName(eventName);
|
|
1432
|
+
const sanitizedProps = sanitizeProperties(properties);
|
|
1433
|
+
const queuedEvent = {
|
|
1434
|
+
event: sanitizedName,
|
|
1435
|
+
properties: sanitizedProps,
|
|
1436
|
+
timestamp: Date.now()
|
|
1437
|
+
};
|
|
1438
|
+
this.queue.push(queuedEvent);
|
|
1439
|
+
this.log(`Tracked event: ${sanitizedName}`, sanitizedProps);
|
|
1440
|
+
if (this.queue.length >= this.config.maxQueueSize) {
|
|
1441
|
+
this.flush();
|
|
1442
|
+
}
|
|
1443
|
+
} catch (error) {
|
|
1444
|
+
this.error("Failed to track event", error);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
trackPageView() {
|
|
1448
|
+
this.debouncedTrackPageView();
|
|
1449
|
+
}
|
|
1450
|
+
trackPageViewInternal() {
|
|
1451
|
+
const currentUrl = getPageUrl();
|
|
1452
|
+
if (currentUrl === this.lastPageUrl && this.queue.length > 0) {
|
|
1453
|
+
const lastEvent = this.queue[this.queue.length - 1];
|
|
1454
|
+
if (lastEvent.event === "pageview") {
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
this.lastPageUrl = currentUrl;
|
|
1459
|
+
this.track("pageview", {});
|
|
1460
|
+
}
|
|
1461
|
+
async flush() {
|
|
1462
|
+
if (this.queue.length === 0) {
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
const eventsToSend = this.queue;
|
|
1466
|
+
this.queue = [];
|
|
1467
|
+
const rawEvents = this.buildRawEvents(eventsToSend);
|
|
1468
|
+
const batch = {
|
|
1469
|
+
api_key: this.config.apiKey,
|
|
1470
|
+
events: rawEvents
|
|
1471
|
+
};
|
|
1472
|
+
const endpoint = `${this.config.endpoint}/events`;
|
|
1473
|
+
if (isOffline()) {
|
|
1474
|
+
this.log("Offline, queuing events for later");
|
|
1475
|
+
queueOfflineEvents(rawEvents);
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
try {
|
|
1479
|
+
if (this.adapter) {
|
|
1480
|
+
await sendAdaptive(endpoint, batch, this.adapter);
|
|
1481
|
+
this.log(`Flushed ${rawEvents.length} events via adaptive send`);
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
if (sendBeacon(endpoint, batch)) {
|
|
1485
|
+
this.log(`Flushed ${rawEvents.length} events via beacon`);
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
await sendFetch(endpoint, batch);
|
|
1489
|
+
this.log(`Flushed ${rawEvents.length} events via fetch`);
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
this.error("Failed to send events, queuing for retry", error);
|
|
1492
|
+
queueOfflineEvents(rawEvents);
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
buildRawEvents(queuedEvents) {
|
|
1496
|
+
const pageUrl = getPageUrl();
|
|
1497
|
+
const pageTitle = getPageTitle();
|
|
1498
|
+
const referrer = getReferrer();
|
|
1499
|
+
const userAgent = getUserAgent();
|
|
1500
|
+
const screen = getScreenDimensions();
|
|
1501
|
+
const utm = getUtmParams();
|
|
1502
|
+
return queuedEvents.map((queued) => ({
|
|
1503
|
+
event: queued.event,
|
|
1504
|
+
properties: queued.properties,
|
|
1505
|
+
timestamp: queued.timestamp,
|
|
1506
|
+
visitor_id: this.visitorId,
|
|
1507
|
+
session_id: this.sessionId,
|
|
1508
|
+
page_url: pageUrl,
|
|
1509
|
+
page_title: pageTitle,
|
|
1510
|
+
referrer,
|
|
1511
|
+
user_agent: userAgent,
|
|
1512
|
+
screen_width: screen.width,
|
|
1513
|
+
screen_height: screen.height,
|
|
1514
|
+
...Object.keys(utm).length > 0 && { utm }
|
|
1515
|
+
}));
|
|
1516
|
+
}
|
|
1517
|
+
startFlushTimer() {
|
|
1518
|
+
if (this.flushTimer !== null) {
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
if (this.config.flushInterval <= 0) {
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
this.flushTimer = setInterval(() => {
|
|
1525
|
+
this.flush();
|
|
1526
|
+
}, this.config.flushInterval);
|
|
1527
|
+
this.log(`Started flush timer: ${this.config.flushInterval}ms`);
|
|
1528
|
+
}
|
|
1529
|
+
stopFlushTimer() {
|
|
1530
|
+
if (this.flushTimer !== null) {
|
|
1531
|
+
clearInterval(this.flushTimer);
|
|
1532
|
+
this.flushTimer = null;
|
|
1533
|
+
this.log("Stopped flush timer");
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
setupPageViewTracking() {
|
|
1537
|
+
if (typeof window === "undefined" || typeof history === "undefined") {
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
this.wrapHistoryMethod("pushState");
|
|
1541
|
+
this.wrapHistoryMethod("replaceState");
|
|
1542
|
+
window.addEventListener("popstate", this.boundHandlePopstate);
|
|
1543
|
+
this.log("Set up SPA navigation tracking");
|
|
1544
|
+
}
|
|
1545
|
+
wrapHistoryMethod(method) {
|
|
1546
|
+
if (typeof history === "undefined") {
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
const original = history[method];
|
|
1550
|
+
if (!original) {
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
if (method === "pushState") {
|
|
1554
|
+
this.originalPushState = original;
|
|
1555
|
+
} else {
|
|
1556
|
+
this.originalReplaceState = original;
|
|
1557
|
+
}
|
|
1558
|
+
const analytics = this;
|
|
1559
|
+
history[method] = function(data, unused, url) {
|
|
1560
|
+
const result = original.apply(this, [data, unused, url]);
|
|
1561
|
+
analytics.trackPageView();
|
|
1562
|
+
return result;
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
handlePopstate = () => {
|
|
1566
|
+
this.trackPageView();
|
|
1567
|
+
};
|
|
1568
|
+
restoreHistoryMethods() {
|
|
1569
|
+
if (typeof history === "undefined") {
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
if (this.originalPushState) {
|
|
1573
|
+
history.pushState = this.originalPushState;
|
|
1574
|
+
this.originalPushState = null;
|
|
1575
|
+
}
|
|
1576
|
+
if (this.originalReplaceState) {
|
|
1577
|
+
history.replaceState = this.originalReplaceState;
|
|
1578
|
+
this.originalReplaceState = null;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
handlePageUnload = () => {
|
|
1582
|
+
if (this.queue.length === 0) {
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
const rawEvents = this.buildRawEvents(this.queue);
|
|
1586
|
+
const batch = {
|
|
1587
|
+
api_key: this.config.apiKey,
|
|
1588
|
+
events: rawEvents
|
|
1589
|
+
};
|
|
1590
|
+
const endpoint = `${this.config.endpoint}/events`;
|
|
1591
|
+
if (!sendBeacon(endpoint, batch)) {
|
|
1592
|
+
queueOfflineEvents(rawEvents);
|
|
1593
|
+
}
|
|
1594
|
+
this.queue = [];
|
|
1595
|
+
};
|
|
1596
|
+
handleOnline = () => {
|
|
1597
|
+
this.log("Back online, sending queued events");
|
|
1598
|
+
this.sendOfflineEvents();
|
|
1599
|
+
};
|
|
1600
|
+
handleOffline = () => {
|
|
1601
|
+
this.log("Gone offline, queuing events");
|
|
1602
|
+
if (this.queue.length > 0) {
|
|
1603
|
+
const rawEvents = this.buildRawEvents(this.queue);
|
|
1604
|
+
queueOfflineEvents(rawEvents);
|
|
1605
|
+
this.queue = [];
|
|
1606
|
+
}
|
|
1607
|
+
};
|
|
1608
|
+
sendOfflineEvents() {
|
|
1609
|
+
const offlineEvents = getOfflineEvents();
|
|
1610
|
+
if (offlineEvents.length === 0) {
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
this.log(`Found ${offlineEvents.length} offline events to send`);
|
|
1614
|
+
clearOfflineEvents();
|
|
1615
|
+
const batch = {
|
|
1616
|
+
api_key: this.config.apiKey,
|
|
1617
|
+
events: offlineEvents
|
|
1618
|
+
};
|
|
1619
|
+
const endpoint = `${this.config.endpoint}/events`;
|
|
1620
|
+
if (!sendBeacon(endpoint, batch)) {
|
|
1621
|
+
sendFetch(endpoint, batch).catch((error) => {
|
|
1622
|
+
this.error("Failed to send offline events", error);
|
|
1623
|
+
queueOfflineEvents(offlineEvents);
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
setConfig(updates) {
|
|
1628
|
+
const oldFlushInterval = this.config.flushInterval;
|
|
1629
|
+
this.config = {
|
|
1630
|
+
...this.config,
|
|
1631
|
+
...updates.endpoint !== undefined && { endpoint: updates.endpoint },
|
|
1632
|
+
...updates.debug !== undefined && { debug: updates.debug },
|
|
1633
|
+
...updates.maxQueueSize !== undefined && {
|
|
1634
|
+
maxQueueSize: updates.maxQueueSize
|
|
1635
|
+
},
|
|
1636
|
+
...updates.flushInterval !== undefined && {
|
|
1637
|
+
flushInterval: updates.flushInterval
|
|
1638
|
+
},
|
|
1639
|
+
...updates.autoTrack !== undefined && { autoTrack: updates.autoTrack }
|
|
1640
|
+
};
|
|
1641
|
+
if (updates.debug !== undefined) {
|
|
1642
|
+
setDebugMode(updates.debug);
|
|
1643
|
+
}
|
|
1644
|
+
if (updates.flushInterval !== undefined && updates.flushInterval !== oldFlushInterval) {
|
|
1645
|
+
this.stopFlushTimer();
|
|
1646
|
+
this.startFlushTimer();
|
|
1647
|
+
}
|
|
1648
|
+
this.log("Config updated", updates);
|
|
1649
|
+
}
|
|
1650
|
+
getVisitorId() {
|
|
1651
|
+
return this.visitorId;
|
|
1652
|
+
}
|
|
1653
|
+
getSessionId() {
|
|
1654
|
+
return this.sessionId;
|
|
1655
|
+
}
|
|
1656
|
+
getConfig() {
|
|
1657
|
+
return { ...this.config };
|
|
1658
|
+
}
|
|
1659
|
+
getQueueLength() {
|
|
1660
|
+
return this.queue.length;
|
|
1661
|
+
}
|
|
1662
|
+
isActive() {
|
|
1663
|
+
return this.isInitialized;
|
|
1664
|
+
}
|
|
1665
|
+
destroy() {
|
|
1666
|
+
this.log("Destroying instance");
|
|
1667
|
+
if (this.queue.length > 0) {
|
|
1668
|
+
this.handlePageUnload();
|
|
1669
|
+
}
|
|
1670
|
+
this.stopFlushTimer();
|
|
1671
|
+
if (typeof window !== "undefined") {
|
|
1672
|
+
window.removeEventListener("beforeunload", this.boundHandlePageUnload);
|
|
1673
|
+
window.removeEventListener("online", this.boundHandleOnline);
|
|
1674
|
+
window.removeEventListener("offline", this.boundHandleOffline);
|
|
1675
|
+
window.removeEventListener("popstate", this.boundHandlePopstate);
|
|
1676
|
+
}
|
|
1677
|
+
this.restoreHistoryMethods();
|
|
1678
|
+
if (this.adapterCleanup) {
|
|
1679
|
+
this.adapterCleanup();
|
|
1680
|
+
this.adapterCleanup = null;
|
|
1681
|
+
}
|
|
1682
|
+
if (this.adapter) {
|
|
1683
|
+
this.adapter.destroy();
|
|
1684
|
+
this.adapter = null;
|
|
1685
|
+
}
|
|
1686
|
+
this.queue = [];
|
|
1687
|
+
this.isInitialized = false;
|
|
1688
|
+
this.log("Destroyed");
|
|
1689
|
+
}
|
|
1690
|
+
log(message, ...args) {
|
|
1691
|
+
if (this.config.debug && typeof console !== "undefined") {
|
|
1692
|
+
console.log(`[Analytics] ${message}`, ...args);
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
warn(message, ...args) {
|
|
1696
|
+
if (this.config.debug && typeof console !== "undefined") {
|
|
1697
|
+
console.warn(`[Analytics] ${message}`, ...args);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
error(message, ...args) {
|
|
1701
|
+
if (this.config.debug && typeof console !== "undefined") {
|
|
1702
|
+
console.error(`[Analytics] ${message}`, ...args);
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
function createAnalytics(config) {
|
|
1707
|
+
return new Analytics(config);
|
|
1708
|
+
}
|
|
1709
|
+
// src/index.ts
|
|
1710
|
+
var VERSION = "0.1.0";
|
|
1711
|
+
function createGlobalAnalytics() {
|
|
1712
|
+
return {
|
|
1713
|
+
version: VERSION,
|
|
1714
|
+
instance: null,
|
|
1715
|
+
init(apiKey, config = {}) {
|
|
1716
|
+
if (this.instance) {
|
|
1717
|
+
this.instance.destroy();
|
|
1718
|
+
}
|
|
1719
|
+
this.instance = new Analytics({ apiKey, ...config });
|
|
1720
|
+
return this.instance;
|
|
1721
|
+
},
|
|
1722
|
+
track(event, properties = {}) {
|
|
1723
|
+
if (!this.instance) {
|
|
1724
|
+
if (typeof console !== "undefined") {
|
|
1725
|
+
console.warn("[Analytics] Not initialized. Call analytics.init(apiKey) first.");
|
|
1726
|
+
}
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
this.instance.track(event, properties);
|
|
1730
|
+
},
|
|
1731
|
+
trackPageView() {
|
|
1732
|
+
if (!this.instance) {
|
|
1733
|
+
if (typeof console !== "undefined") {
|
|
1734
|
+
console.warn("[Analytics] Not initialized. Call analytics.init(apiKey) first.");
|
|
1735
|
+
}
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
this.instance.trackPageView();
|
|
1739
|
+
},
|
|
1740
|
+
getVisitorId() {
|
|
1741
|
+
return this.instance?.getVisitorId() ?? null;
|
|
1742
|
+
},
|
|
1743
|
+
getSessionId() {
|
|
1744
|
+
return this.instance?.getSessionId() ?? null;
|
|
1745
|
+
},
|
|
1746
|
+
async flush() {
|
|
1747
|
+
if (this.instance) {
|
|
1748
|
+
await this.instance.flush();
|
|
1749
|
+
}
|
|
1750
|
+
},
|
|
1751
|
+
destroy() {
|
|
1752
|
+
if (this.instance) {
|
|
1753
|
+
this.instance.destroy();
|
|
1754
|
+
this.instance = null;
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
var globalAnalytics = createGlobalAnalytics();
|
|
1760
|
+
function autoInitFromScriptTag() {
|
|
1761
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
window.analytics = globalAnalytics;
|
|
1765
|
+
const currentScript = document.currentScript;
|
|
1766
|
+
if (!currentScript) {
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
const apiKey = currentScript.getAttribute("data-api-key");
|
|
1770
|
+
if (!apiKey) {
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
const endpoint = currentScript.getAttribute("data-endpoint") || undefined;
|
|
1774
|
+
const debug = currentScript.getAttribute("data-debug") === "true";
|
|
1775
|
+
const autoTrack = currentScript.getAttribute("data-auto-track") !== "false";
|
|
1776
|
+
const adaptiveNetwork = currentScript.getAttribute("data-adaptive") !== "false";
|
|
1777
|
+
globalAnalytics.init(apiKey, {
|
|
1778
|
+
endpoint,
|
|
1779
|
+
debug,
|
|
1780
|
+
autoTrack,
|
|
1781
|
+
adaptiveNetwork
|
|
1782
|
+
});
|
|
1783
|
+
}
|
|
1784
|
+
autoInitFromScriptTag();
|
|
1785
|
+
var src_default = globalAnalytics;
|
|
1786
|
+
})();
|