sitepong 0.0.1 → 0.0.5

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.mjs CHANGED
@@ -1,21 +1,4043 @@
1
+ // src/flags/anonymous-id.ts
2
+ var STORAGE_KEY = "sitepong_anonymous_id";
3
+ function generateUUID() {
4
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
5
+ return crypto.randomUUID();
6
+ }
7
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
8
+ const r = Math.random() * 16 | 0;
9
+ const v = c === "x" ? r : r & 3 | 8;
10
+ return v.toString(16);
11
+ });
12
+ }
13
+ var memoryId = null;
14
+ function getAnonymousId() {
15
+ if (typeof window !== "undefined" && typeof localStorage !== "undefined") {
16
+ try {
17
+ let id = localStorage.getItem(STORAGE_KEY);
18
+ if (!id) {
19
+ id = generateUUID();
20
+ localStorage.setItem(STORAGE_KEY, id);
21
+ }
22
+ return id;
23
+ } catch {
24
+ if (!memoryId) {
25
+ memoryId = generateUUID();
26
+ }
27
+ return memoryId;
28
+ }
29
+ }
30
+ if (!memoryId) {
31
+ memoryId = generateUUID();
32
+ }
33
+ return memoryId;
34
+ }
35
+ function clearAnonymousId() {
36
+ if (typeof window !== "undefined" && typeof localStorage !== "undefined") {
37
+ try {
38
+ localStorage.removeItem(STORAGE_KEY);
39
+ } catch {
40
+ }
41
+ }
42
+ memoryId = null;
43
+ }
44
+ function setAnonymousId(id) {
45
+ if (typeof window !== "undefined" && typeof localStorage !== "undefined") {
46
+ try {
47
+ localStorage.setItem(STORAGE_KEY, id);
48
+ } catch {
49
+ memoryId = id;
50
+ }
51
+ } else {
52
+ memoryId = id;
53
+ }
54
+ }
55
+
56
+ // src/flags/context.ts
57
+ function detectDeviceType(userAgent) {
58
+ const ua = userAgent.toLowerCase();
59
+ if (/ipad/.test(ua) || /android/.test(ua) && !/mobile/.test(ua) || /tablet/.test(ua)) {
60
+ return "tablet";
61
+ }
62
+ if (/mobile/.test(ua) || /iphone/.test(ua) || /ipod/.test(ua) || /android/.test(ua) || /blackberry/.test(ua) || /windows phone/.test(ua)) {
63
+ return "mobile";
64
+ }
65
+ return "desktop";
66
+ }
67
+ function detectBrowser(userAgent) {
68
+ const ua = userAgent.toLowerCase();
69
+ if (/edg/.test(ua)) return "edge";
70
+ if (/opr|opera/.test(ua)) return "opera";
71
+ if (/samsungbrowser/.test(ua)) return "samsung";
72
+ if (/chrome|chromium|crios/.test(ua)) return "chrome";
73
+ if (/safari/.test(ua) && !/chrome/.test(ua)) return "safari";
74
+ if (/firefox|fxios/.test(ua)) return "firefox";
75
+ if (/msie|trident/.test(ua)) return "ie";
76
+ return "other";
77
+ }
78
+ function detectOS(userAgent) {
79
+ const ua = userAgent.toLowerCase();
80
+ if (/iphone|ipad|ipod/.test(ua)) return "ios";
81
+ if (/android/.test(ua)) return "android";
82
+ if (/mac os|macos|macintosh/.test(ua)) return "macos";
83
+ if (/windows/.test(ua)) return "windows";
84
+ if (/linux/.test(ua)) return "linux";
85
+ return "other";
86
+ }
87
+ function getEvaluationContext() {
88
+ const userAgent = typeof navigator !== "undefined" ? navigator.userAgent : "";
89
+ return {
90
+ anonymous_id: getAnonymousId(),
91
+ device_type: userAgent ? detectDeviceType(userAgent) : void 0,
92
+ browser: userAgent ? detectBrowser(userAgent) : void 0,
93
+ os: userAgent ? detectOS(userAgent) : void 0,
94
+ user_agent: userAgent || void 0,
95
+ timestamp: /* @__PURE__ */ new Date()
96
+ };
97
+ }
98
+
99
+ // src/flags/evaluator.ts
100
+ function djb2Hash(str) {
101
+ let hash = 5381;
102
+ for (let i = 0; i < str.length; i++) {
103
+ hash = (hash << 5) + hash ^ str.charCodeAt(i);
104
+ }
105
+ return hash >>> 0;
106
+ }
107
+ function evaluatePercentageRollout(config) {
108
+ return Math.random() * 100 < config.percentage;
109
+ }
110
+ function evaluateUserbasePercentage(config, context, flagKey) {
111
+ const hashInput = `${context.anonymous_id}:${flagKey}`;
112
+ const hash = djb2Hash(hashInput);
113
+ const bucket = hash % 100;
114
+ return bucket < config.percentage;
115
+ }
116
+ function parseTime(timeStr) {
117
+ const [hours, minutes] = timeStr.split(":").map(Number);
118
+ return hours * 60 + minutes;
119
+ }
120
+ function evaluateTimeBased(config, context) {
121
+ const now = context.timestamp || /* @__PURE__ */ new Date();
122
+ const formatter = new Intl.DateTimeFormat("en-US", {
123
+ timeZone: config.timezone,
124
+ weekday: "short",
125
+ hour: "2-digit",
126
+ minute: "2-digit",
127
+ hour12: false
128
+ });
129
+ const parts = formatter.formatToParts(now);
130
+ const dayOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].indexOf(
131
+ parts.find((p) => p.type === "weekday")?.value || ""
132
+ );
133
+ const hour = parseInt(parts.find((p) => p.type === "hour")?.value || "0", 10);
134
+ const minute = parseInt(parts.find((p) => p.type === "minute")?.value || "0", 10);
135
+ const currentMinutes = hour * 60 + minute;
136
+ if (config.days_of_week && config.days_of_week.length > 0) {
137
+ if (!config.days_of_week.includes(dayOfWeek)) {
138
+ return false;
139
+ }
140
+ }
141
+ if (config.start_time && config.end_time) {
142
+ const startMinutes = parseTime(config.start_time);
143
+ const endMinutes = parseTime(config.end_time);
144
+ if (startMinutes > endMinutes) {
145
+ if (currentMinutes < startMinutes && currentMinutes >= endMinutes) {
146
+ return false;
147
+ }
148
+ } else {
149
+ if (currentMinutes < startMinutes || currentMinutes >= endMinutes) {
150
+ return false;
151
+ }
152
+ }
153
+ } else if (config.start_time) {
154
+ const startMinutes = parseTime(config.start_time);
155
+ if (currentMinutes < startMinutes) {
156
+ return false;
157
+ }
158
+ } else if (config.end_time) {
159
+ const endMinutes = parseTime(config.end_time);
160
+ if (currentMinutes >= endMinutes) {
161
+ return false;
162
+ }
163
+ }
164
+ return true;
165
+ }
166
+ function evaluateDeviceType(config, context) {
167
+ if (!context.device_type) {
168
+ return false;
169
+ }
170
+ return config.types.includes(context.device_type);
171
+ }
172
+ function evaluateBrowser(config, context) {
173
+ if (!context.browser) {
174
+ return false;
175
+ }
176
+ return config.browsers.includes(context.browser);
177
+ }
178
+ function evaluateOS(config, context) {
179
+ if (!context.os) {
180
+ return false;
181
+ }
182
+ return config.operating_systems.includes(context.os);
183
+ }
184
+ function evaluateRule(ruleType, config, context, flagKey) {
185
+ switch (ruleType) {
186
+ case "percentage_rollout":
187
+ return evaluatePercentageRollout(config);
188
+ case "userbase_percentage":
189
+ return evaluateUserbasePercentage(
190
+ config,
191
+ context,
192
+ flagKey
193
+ );
194
+ case "time_based":
195
+ return evaluateTimeBased(config, context);
196
+ case "device_type":
197
+ return evaluateDeviceType(config, context);
198
+ case "browser":
199
+ return evaluateBrowser(config, context);
200
+ case "os":
201
+ return evaluateOS(config, context);
202
+ default:
203
+ return false;
204
+ }
205
+ }
206
+ function evaluateFlag(flag, context) {
207
+ if (flag.override === "force_on") {
208
+ return true;
209
+ }
210
+ if (flag.override === "force_off") {
211
+ return false;
212
+ }
213
+ if (!flag.enabled) {
214
+ return false;
215
+ }
216
+ if (!flag.rules || flag.rules.length === 0) {
217
+ return true;
218
+ }
219
+ for (const rule of flag.rules) {
220
+ const result = evaluateRule(rule.rule_type, rule.config, context, flag.key);
221
+ if (!result) {
222
+ return false;
223
+ }
224
+ }
225
+ return true;
226
+ }
227
+ function evaluateVariant(flag, context) {
228
+ if (!evaluateFlag(flag, context)) {
229
+ return null;
230
+ }
231
+ if (flag.flag_type !== "multivariate" || !flag.variants || flag.variants.length === 0) {
232
+ return null;
233
+ }
234
+ const hashInput = `${context.anonymous_id}:${flag.key}:variant`;
235
+ const hash = djb2Hash(hashInput);
236
+ const totalWeight = flag.variants.reduce((sum, v) => sum + v.weight, 0);
237
+ const bucket = hash % totalWeight;
238
+ let cumulative = 0;
239
+ for (const variant of flag.variants) {
240
+ cumulative += variant.weight;
241
+ if (bucket < cumulative) {
242
+ return variant.key;
243
+ }
244
+ }
245
+ return flag.variants[0].key;
246
+ }
247
+ function getVariantPayload(flag, variantKey) {
248
+ if (flag.variant_payloads && flag.variant_payloads[variantKey] !== void 0) {
249
+ return flag.variant_payloads[variantKey];
250
+ }
251
+ const variant = flag.variants?.find((v) => v.key === variantKey);
252
+ return variant?.payload || null;
253
+ }
254
+
255
+ // src/flags/manager.ts
256
+ var DEFAULT_ENDPOINT = "https://api.sitepong.com";
257
+ var FlagManager = class {
258
+ constructor(config) {
259
+ this.flags = /* @__PURE__ */ new Map();
260
+ this.evaluatedFlags = /* @__PURE__ */ new Map();
261
+ this.evaluatedVariants = /* @__PURE__ */ new Map();
262
+ this.context = null;
263
+ this.initialized = false;
264
+ this.initPromise = null;
265
+ this.config = {
266
+ endpoint: DEFAULT_ENDPOINT,
267
+ debug: false,
268
+ ...config
269
+ };
270
+ }
271
+ /**
272
+ * Initialize the flag manager by fetching flags
273
+ * Flags are evaluated once on init and cached
274
+ */
275
+ async init() {
276
+ if (this.initialized) {
277
+ return;
278
+ }
279
+ if (this.initPromise) {
280
+ return this.initPromise;
281
+ }
282
+ this.initPromise = this.fetchAndEvaluateFlags();
283
+ try {
284
+ await this.initPromise;
285
+ this.initialized = true;
286
+ } catch (err) {
287
+ this.log("Failed to initialize flags:", err);
288
+ this.initialized = true;
289
+ } finally {
290
+ this.initPromise = null;
291
+ }
292
+ }
293
+ /**
294
+ * Fetch flags from the API and evaluate them
295
+ */
296
+ async fetchAndEvaluateFlags() {
297
+ try {
298
+ const response = await fetch(`${this.config.endpoint}/api/sdk/flags`, {
299
+ method: "GET",
300
+ headers: {
301
+ "Content-Type": "application/json",
302
+ "X-API-Key": this.config.apiKey
303
+ }
304
+ });
305
+ if (!response.ok) {
306
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
307
+ }
308
+ const data = await response.json();
309
+ this.log("Fetched flags:", data.flags.length);
310
+ this.context = getEvaluationContext();
311
+ this.log("Evaluation context:", this.context);
312
+ this.flags.clear();
313
+ this.evaluatedFlags.clear();
314
+ this.evaluatedVariants.clear();
315
+ for (const flag of data.flags) {
316
+ this.flags.set(flag.key, flag);
317
+ const result = evaluateFlag(flag, this.context);
318
+ this.evaluatedFlags.set(flag.key, result);
319
+ if (flag.flag_type === "multivariate" && flag.variants) {
320
+ const variant = evaluateVariant(flag, this.context);
321
+ this.evaluatedVariants.set(flag.key, variant);
322
+ this.log(`Flag "${flag.key}": ${result}, variant: ${variant}`);
323
+ } else {
324
+ this.log(`Flag "${flag.key}": ${result}`);
325
+ }
326
+ }
327
+ } catch (err) {
328
+ this.log("Error fetching flags:", err);
329
+ throw err;
330
+ }
331
+ }
332
+ /**
333
+ * Get a flag value
334
+ * Returns the cached evaluated result or default value
335
+ */
336
+ getFlag(key, defaultValue = false) {
337
+ if (!this.initialized) {
338
+ this.log(`Flag "${key}" requested before init, returning default:`, defaultValue);
339
+ return defaultValue;
340
+ }
341
+ const value = this.evaluatedFlags.get(key);
342
+ if (value === void 0) {
343
+ this.log(`Flag "${key}" not found, returning default:`, defaultValue);
344
+ return defaultValue;
345
+ }
346
+ return value;
347
+ }
348
+ /**
349
+ * Get a multivariate flag variant
350
+ * Returns the assigned variant key or the default value
351
+ */
352
+ getVariant(key, defaultValue = null) {
353
+ if (!this.initialized) {
354
+ this.log(`Variant "${key}" requested before init, returning default:`, defaultValue);
355
+ return defaultValue;
356
+ }
357
+ const variant = this.evaluatedVariants.get(key);
358
+ if (variant === void 0) {
359
+ this.log(`Variant "${key}" not found, returning default:`, defaultValue);
360
+ return defaultValue;
361
+ }
362
+ return variant;
363
+ }
364
+ /**
365
+ * Get the payload for a multivariate flag's assigned variant
366
+ */
367
+ getVariantPayload(key, defaultValue = null) {
368
+ const variant = this.getVariant(key);
369
+ if (!variant) return defaultValue;
370
+ const flag = this.flags.get(key);
371
+ if (!flag) return defaultValue;
372
+ const payload = getVariantPayload(flag, variant);
373
+ return payload ?? defaultValue;
374
+ }
375
+ /**
376
+ * Get all flag values
377
+ */
378
+ getAllFlags() {
379
+ const result = {};
380
+ for (const [key, value] of this.evaluatedFlags) {
381
+ result[key] = value;
382
+ }
383
+ return result;
384
+ }
385
+ /**
386
+ * Check if a flag exists
387
+ */
388
+ hasFlag(key) {
389
+ return this.flags.has(key);
390
+ }
391
+ /**
392
+ * Check if flags are initialized
393
+ */
394
+ isInitialized() {
395
+ return this.initialized;
396
+ }
397
+ /**
398
+ * Wait for initialization to complete
399
+ */
400
+ async waitForInit() {
401
+ if (this.initialized) {
402
+ return;
403
+ }
404
+ await this.init();
405
+ }
406
+ /**
407
+ * Force re-fetch and re-evaluate flags
408
+ * Call this if you need to refresh flags (e.g., on explicit user action)
409
+ */
410
+ async refresh() {
411
+ this.initialized = false;
412
+ await this.init();
413
+ }
414
+ /**
415
+ * Get the current evaluation context
416
+ */
417
+ getContext() {
418
+ return this.context;
419
+ }
420
+ log(...args) {
421
+ if (this.config.debug) {
422
+ console.log("[SitePong Flags]", ...args);
423
+ }
424
+ }
425
+ };
426
+
427
+ // src/analytics/session.ts
428
+ var SESSION_KEY = "sitepong_session_id";
429
+ var SESSION_TIMESTAMP_KEY = "sitepong_session_ts";
430
+ var SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
431
+ var memorySessionId = null;
432
+ var memorySessionTs = null;
433
+ function generateSessionId() {
434
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
435
+ return crypto.randomUUID();
436
+ }
437
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
438
+ const r = Math.random() * 16 | 0;
439
+ const v = c === "x" ? r : r & 3 | 8;
440
+ return v.toString(16);
441
+ });
442
+ }
443
+ function isSessionExpired(lastActivity) {
444
+ return Date.now() - lastActivity > SESSION_TIMEOUT_MS;
445
+ }
446
+ function getSessionId() {
447
+ if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
448
+ try {
449
+ const existingId = sessionStorage.getItem(SESSION_KEY);
450
+ const lastTs = sessionStorage.getItem(SESSION_TIMESTAMP_KEY);
451
+ if (existingId && lastTs && !isSessionExpired(parseInt(lastTs, 10))) {
452
+ sessionStorage.setItem(SESSION_TIMESTAMP_KEY, String(Date.now()));
453
+ return existingId;
454
+ }
455
+ const newId = generateSessionId();
456
+ sessionStorage.setItem(SESSION_KEY, newId);
457
+ sessionStorage.setItem(SESSION_TIMESTAMP_KEY, String(Date.now()));
458
+ return newId;
459
+ } catch {
460
+ return getMemorySession();
461
+ }
462
+ }
463
+ return getMemorySession();
464
+ }
465
+ function getMemorySession() {
466
+ if (memorySessionId && memorySessionTs && !isSessionExpired(memorySessionTs)) {
467
+ memorySessionTs = Date.now();
468
+ return memorySessionId;
469
+ }
470
+ memorySessionId = generateSessionId();
471
+ memorySessionTs = Date.now();
472
+ return memorySessionId;
473
+ }
474
+ function clearSession() {
475
+ if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
476
+ try {
477
+ sessionStorage.removeItem(SESSION_KEY);
478
+ sessionStorage.removeItem(SESSION_TIMESTAMP_KEY);
479
+ } catch {
480
+ }
481
+ }
482
+ memorySessionId = null;
483
+ memorySessionTs = null;
484
+ }
485
+
486
+ // src/analytics/autocapture.ts
487
+ var DEFAULT_BLOCK_SELECTORS = [
488
+ "[data-sp-no-capture]",
489
+ ".sp-no-capture"
490
+ ];
491
+ var MAX_TEXT_LENGTH = 255;
492
+ var AutocaptureModule = class {
493
+ constructor(config, callback) {
494
+ this.clickHandler = null;
495
+ this.submitHandler = null;
496
+ this.active = false;
497
+ this.config = {
498
+ clicks: true,
499
+ forms: true,
500
+ pageviews: true,
501
+ blockSelectors: DEFAULT_BLOCK_SELECTORS,
502
+ maxTextLength: MAX_TEXT_LENGTH,
503
+ debug: false,
504
+ ...config
505
+ };
506
+ this.callback = callback;
507
+ }
508
+ start() {
509
+ if (this.active || typeof window === "undefined") return;
510
+ this.active = true;
511
+ if (this.config.clicks) {
512
+ this.setupClickCapture();
513
+ }
514
+ if (this.config.forms) {
515
+ this.setupFormCapture();
516
+ }
517
+ this.log("Autocapture started");
518
+ }
519
+ stop() {
520
+ if (!this.active) return;
521
+ this.active = false;
522
+ if (this.clickHandler) {
523
+ document.removeEventListener("click", this.clickHandler, true);
524
+ this.clickHandler = null;
525
+ }
526
+ if (this.submitHandler) {
527
+ document.removeEventListener("submit", this.submitHandler, true);
528
+ this.submitHandler = null;
529
+ }
530
+ this.log("Autocapture stopped");
531
+ }
532
+ setupClickCapture() {
533
+ this.clickHandler = (e) => {
534
+ const target = e.target;
535
+ if (!target || !this.shouldCapture(target)) return;
536
+ const properties = this.getElementProperties(target);
537
+ properties.$event_type = "click";
538
+ properties.$x = e.clientX;
539
+ properties.$y = e.clientY;
540
+ this.callback({
541
+ type: "click",
542
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
543
+ properties
544
+ });
545
+ };
546
+ document.addEventListener("click", this.clickHandler, true);
547
+ }
548
+ setupFormCapture() {
549
+ this.submitHandler = (e) => {
550
+ const form = e.target;
551
+ if (!form || !this.shouldCapture(form)) return;
552
+ const properties = {
553
+ $event_type: "form_submit",
554
+ $form_action: form.action || void 0,
555
+ $form_method: form.method || "get",
556
+ $form_name: form.name || form.id || void 0,
557
+ $field_names: this.getFormFieldNames(form),
558
+ $field_count: form.elements.length
559
+ };
560
+ const elemProps = this.getElementProperties(form);
561
+ Object.assign(properties, elemProps);
562
+ this.callback({
563
+ type: "form_submit",
564
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
565
+ properties
566
+ });
567
+ };
568
+ document.addEventListener("submit", this.submitHandler, true);
569
+ }
570
+ shouldCapture(element) {
571
+ const blockSelectors = this.config.blockSelectors || DEFAULT_BLOCK_SELECTORS;
572
+ for (const selector of blockSelectors) {
573
+ if (element.matches(selector) || element.closest(selector)) {
574
+ return false;
575
+ }
576
+ }
577
+ if (this.config.allowSelectors && this.config.allowSelectors.length > 0) {
578
+ const matches = this.config.allowSelectors.some(
579
+ (sel) => element.matches(sel) || element.closest(sel) !== null
580
+ );
581
+ if (!matches) return false;
582
+ }
583
+ if (element.offsetParent === null && element.tagName !== "BODY") {
584
+ return false;
585
+ }
586
+ return true;
587
+ }
588
+ getElementProperties(element) {
589
+ const properties = {};
590
+ properties.$tag_name = element.tagName.toLowerCase();
591
+ properties.$el_id = element.id || void 0;
592
+ if (element.classList.length > 0) {
593
+ properties.$el_classes = Array.from(element.classList).slice(0, 5);
594
+ }
595
+ const text = this.getElementText(element);
596
+ if (text) {
597
+ properties.$el_text = text;
598
+ }
599
+ if (element instanceof HTMLAnchorElement) {
600
+ properties.$href = element.href || void 0;
601
+ properties.$target = element.target || void 0;
602
+ }
603
+ if (element instanceof HTMLInputElement || element instanceof HTMLButtonElement) {
604
+ properties.$el_type = element.type || void 0;
605
+ properties.$el_name = element.name || void 0;
606
+ }
607
+ const ariaLabel = element.getAttribute("aria-label");
608
+ if (ariaLabel) {
609
+ properties.$aria_label = this.truncate(ariaLabel);
610
+ }
611
+ const role = element.getAttribute("role");
612
+ if (role) {
613
+ properties.$el_role = role;
614
+ }
615
+ properties.$selector = this.getSelector(element);
616
+ properties.$current_url = window.location.href;
617
+ properties.$pathname = window.location.pathname;
618
+ return properties;
619
+ }
620
+ getElementText(element) {
621
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
622
+ return void 0;
623
+ }
624
+ let text = "";
625
+ if (element instanceof HTMLButtonElement || element instanceof HTMLAnchorElement) {
626
+ text = element.innerText || element.textContent || "";
627
+ } else {
628
+ for (const node of Array.from(element.childNodes)) {
629
+ if (node.nodeType === Node.TEXT_NODE) {
630
+ text += node.textContent || "";
631
+ }
632
+ }
633
+ }
634
+ text = text.trim().replace(/\s+/g, " ");
635
+ return text ? this.truncate(text) : void 0;
636
+ }
637
+ getFormFieldNames(form) {
638
+ const names = [];
639
+ for (const element of Array.from(form.elements)) {
640
+ const input = element;
641
+ if (input.name && input.type !== "password" && input.type !== "hidden") {
642
+ names.push(input.name);
643
+ }
644
+ }
645
+ return names.slice(0, 20);
646
+ }
647
+ getSelector(element) {
648
+ const parts = [];
649
+ let current = element;
650
+ let depth = 0;
651
+ while (current && current !== document.body && depth < 5) {
652
+ let selector = current.tagName.toLowerCase();
653
+ if (current.id) {
654
+ selector = `#${current.id}`;
655
+ parts.unshift(selector);
656
+ break;
657
+ }
658
+ if (current.classList.length > 0) {
659
+ selector += `.${Array.from(current.classList).slice(0, 2).join(".")}`;
660
+ }
661
+ const parent = current.parentElement;
662
+ if (parent) {
663
+ const siblings = Array.from(parent.children).filter(
664
+ (c) => c.tagName === current.tagName
665
+ );
666
+ if (siblings.length > 1) {
667
+ const index = siblings.indexOf(current) + 1;
668
+ selector += `:nth-of-type(${index})`;
669
+ }
670
+ }
671
+ parts.unshift(selector);
672
+ current = current.parentElement;
673
+ depth++;
674
+ }
675
+ return parts.join(" > ");
676
+ }
677
+ truncate(text) {
678
+ const max = this.config.maxTextLength || MAX_TEXT_LENGTH;
679
+ return text.length > max ? text.slice(0, max) + "..." : text;
680
+ }
681
+ log(...args) {
682
+ if (this.config.debug) {
683
+ console.log("[SitePong Autocapture]", ...args);
684
+ }
685
+ }
686
+ };
687
+
688
+ // src/analytics/manager.ts
689
+ var DEFAULT_ENDPOINT2 = "https://ingest.sitepong.com";
690
+ var DEFAULT_BATCH_SIZE = 20;
691
+ var DEFAULT_FLUSH_INTERVAL = 1e4;
692
+ var AnalyticsManager = class {
693
+ constructor(config) {
694
+ this.eventQueue = [];
695
+ this.flushTimer = null;
696
+ this.userId = null;
697
+ this.groupId = null;
698
+ this.userTraits = null;
699
+ this.groupTraits = null;
700
+ this.lastUrl = null;
701
+ this.popstateHandler = null;
702
+ this.autocaptureModule = null;
703
+ this.config = {
704
+ endpoint: DEFAULT_ENDPOINT2,
705
+ enabled: true,
706
+ autocapturePageviews: false,
707
+ maxBatchSize: DEFAULT_BATCH_SIZE,
708
+ flushInterval: DEFAULT_FLUSH_INTERVAL,
709
+ debug: false,
710
+ ...config
711
+ };
712
+ }
713
+ init() {
714
+ if (!this.config.enabled) {
715
+ return;
716
+ }
717
+ this.startFlushTimer();
718
+ this.setupPageHideListener();
719
+ if (this.config.autocapturePageviews) {
720
+ this.setupSPATracking();
721
+ }
722
+ this.setupAutocapture();
723
+ this.log("Analytics initialized");
724
+ }
725
+ setupAutocapture() {
726
+ const ac = this.config.autocapture;
727
+ const clicksEnabled = this.config.autocaptureClicks;
728
+ const formsEnabled = this.config.autocaptureForms;
729
+ let captureClicks = clicksEnabled || false;
730
+ let captureForms = formsEnabled || false;
731
+ let blockSelectors;
732
+ let allowSelectors;
733
+ if (ac === true) {
734
+ captureClicks = true;
735
+ captureForms = true;
736
+ } else if (ac && typeof ac === "object") {
737
+ captureClicks = ac.clicks !== false;
738
+ captureForms = ac.forms !== false;
739
+ blockSelectors = ac.blockSelectors;
740
+ allowSelectors = ac.allowSelectors;
741
+ if (ac.pageviews !== false && !this.config.autocapturePageviews) {
742
+ this.setupSPATracking();
743
+ }
744
+ }
745
+ if (!captureClicks && !captureForms) return;
746
+ this.autocaptureModule = new AutocaptureModule(
747
+ {
748
+ clicks: captureClicks,
749
+ forms: captureForms,
750
+ pageviews: false,
751
+ // Handled by SPA tracking above
752
+ blockSelectors,
753
+ allowSelectors,
754
+ debug: this.config.debug
755
+ },
756
+ (event) => {
757
+ const name = event.type === "click" ? "$autocapture_click" : event.type === "form_submit" ? "$autocapture_form_submit" : "$autocapture";
758
+ this.track(name, event.properties);
759
+ }
760
+ );
761
+ this.autocaptureModule.start();
762
+ }
763
+ track(eventName, properties) {
764
+ if (!this.config.enabled) return;
765
+ const event = this.createEvent("track", { name: eventName, properties });
766
+ this.enqueue(event);
767
+ this.log("Track:", eventName, properties);
768
+ }
769
+ trackPageView(url, properties) {
770
+ if (!this.config.enabled) return;
771
+ const pageUrl = url || (typeof window !== "undefined" ? window.location.href : void 0);
772
+ const event = this.createEvent("page", {
773
+ properties: {
774
+ url: pageUrl,
775
+ referrer: typeof document !== "undefined" ? document.referrer : void 0,
776
+ title: typeof document !== "undefined" ? document.title : void 0,
777
+ ...properties
778
+ }
779
+ });
780
+ this.enqueue(event);
781
+ this.log("Page view:", pageUrl);
782
+ }
783
+ identify(userId, traits) {
784
+ if (!this.config.enabled) return;
785
+ this.userId = userId;
786
+ if (traits) {
787
+ this.userTraits = { ...this.userTraits, ...traits };
788
+ }
789
+ const event = this.createEvent("identify", { traits: this.userTraits || void 0 });
790
+ this.enqueue(event);
791
+ this.log("Identify:", userId, traits);
792
+ }
793
+ group(groupId, traits) {
794
+ if (!this.config.enabled) return;
795
+ this.groupId = groupId;
796
+ if (traits) {
797
+ this.groupTraits = { ...this.groupTraits, ...traits };
798
+ }
799
+ const event = this.createEvent("group", { traits: this.groupTraits || void 0 });
800
+ this.enqueue(event);
801
+ this.log("Group:", groupId, traits);
802
+ }
803
+ reset() {
804
+ this.userId = null;
805
+ this.groupId = null;
806
+ this.userTraits = null;
807
+ this.groupTraits = null;
808
+ clearSession();
809
+ this.log("Analytics state reset");
810
+ }
811
+ async flush() {
812
+ if (this.eventQueue.length === 0) return;
813
+ const events = [...this.eventQueue];
814
+ this.eventQueue = [];
815
+ const endpoint = this.config.eventsEndpoint || `${this.config.endpoint}/api/events`;
816
+ try {
817
+ const response = await fetch(endpoint, {
818
+ method: "POST",
819
+ headers: {
820
+ "Content-Type": "application/json",
821
+ "X-API-Key": this.config.apiKey
822
+ },
823
+ body: JSON.stringify({ events })
824
+ });
825
+ if (!response.ok) {
826
+ throw new Error(`HTTP ${response.status}`);
827
+ }
828
+ this.log(`Flushed ${events.length} events`);
829
+ } catch (err) {
830
+ this.eventQueue.unshift(...events);
831
+ this.log("Failed to flush events:", err);
832
+ }
833
+ }
834
+ destroy() {
835
+ if (this.flushTimer) {
836
+ clearInterval(this.flushTimer);
837
+ this.flushTimer = null;
838
+ }
839
+ if (this.popstateHandler && typeof window !== "undefined") {
840
+ window.removeEventListener("popstate", this.popstateHandler);
841
+ this.popstateHandler = null;
842
+ }
843
+ if (this.autocaptureModule) {
844
+ this.autocaptureModule.stop();
845
+ this.autocaptureModule = null;
846
+ }
847
+ this.flushWithBeacon();
848
+ }
849
+ createEvent(type, options) {
850
+ return {
851
+ type,
852
+ name: options.name,
853
+ properties: options.properties,
854
+ userId: this.userId || void 0,
855
+ anonymousId: getAnonymousId(),
856
+ groupId: this.groupId || void 0,
857
+ traits: options.traits,
858
+ sessionId: getSessionId(),
859
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
860
+ url: typeof window !== "undefined" ? window.location.href : void 0,
861
+ referrer: typeof document !== "undefined" ? document.referrer : void 0,
862
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : void 0
863
+ };
864
+ }
865
+ enqueue(event) {
866
+ this.eventQueue.push(event);
867
+ if (this.eventQueue.length >= this.config.maxBatchSize) {
868
+ this.flush();
869
+ }
870
+ }
871
+ startFlushTimer() {
872
+ if (this.flushTimer) {
873
+ clearInterval(this.flushTimer);
874
+ }
875
+ this.flushTimer = setInterval(() => {
876
+ this.flush();
877
+ }, this.config.flushInterval);
878
+ }
879
+ setupPageHideListener() {
880
+ if (typeof window === "undefined") return;
881
+ window.addEventListener("visibilitychange", () => {
882
+ if (document.visibilityState === "hidden") {
883
+ this.flushWithBeacon();
884
+ }
885
+ });
886
+ }
887
+ flushWithBeacon() {
888
+ if (this.eventQueue.length === 0 || typeof navigator?.sendBeacon !== "function") {
889
+ return;
890
+ }
891
+ const events = [...this.eventQueue];
892
+ this.eventQueue = [];
893
+ const endpoint = this.config.eventsEndpoint || `${this.config.endpoint}/api/events`;
894
+ const blob = new Blob(
895
+ [JSON.stringify({ events, apiKey: this.config.apiKey })],
896
+ { type: "application/json" }
897
+ );
898
+ navigator.sendBeacon(endpoint, blob);
899
+ this.log(`Flushed ${events.length} events via beacon`);
900
+ }
901
+ setupSPATracking() {
902
+ if (typeof window === "undefined") return;
903
+ this.lastUrl = window.location.href;
904
+ this.trackPageView();
905
+ this.popstateHandler = () => {
906
+ const currentUrl = window.location.href;
907
+ if (currentUrl !== this.lastUrl) {
908
+ this.lastUrl = currentUrl;
909
+ this.trackPageView(currentUrl);
910
+ }
911
+ };
912
+ window.addEventListener("popstate", this.popstateHandler);
913
+ const originalPushState = history.pushState.bind(history);
914
+ const originalReplaceState = history.replaceState.bind(history);
915
+ history.pushState = (...args) => {
916
+ originalPushState(...args);
917
+ this.handleUrlChange();
918
+ };
919
+ history.replaceState = (...args) => {
920
+ originalReplaceState(...args);
921
+ this.handleUrlChange();
922
+ };
923
+ }
924
+ handleUrlChange() {
925
+ if (typeof window === "undefined") return;
926
+ const currentUrl = window.location.href;
927
+ if (currentUrl !== this.lastUrl) {
928
+ this.lastUrl = currentUrl;
929
+ this.trackPageView(currentUrl);
930
+ }
931
+ }
932
+ log(...args) {
933
+ if (this.config.debug) {
934
+ console.log("[SitePong Analytics]", ...args);
935
+ }
936
+ }
937
+ };
938
+
939
+ // src/fingerprint/tampering.ts
940
+ function detectTampering() {
941
+ const signals = {
942
+ uaSpoofed: false,
943
+ antiDetectBrowser: false,
944
+ canvasNoise: false,
945
+ canvasBlocked: false,
946
+ webglSpoofed: false,
947
+ webglBlocked: false,
948
+ vmIndicators: [],
949
+ platformMismatch: false,
950
+ pluginAnomaly: false,
951
+ featureAnomaly: false,
952
+ geolocationSpoofed: false,
953
+ mitmIndicators: []
954
+ };
955
+ let confidence = 0;
956
+ const browserResult = detectBrowserTampering(signals);
957
+ confidence += browserResult;
958
+ const canvasResult = detectCanvasTampering(signals);
959
+ confidence += canvasResult;
960
+ const webglResult = detectWebGLTampering(signals);
961
+ confidence += webglResult;
962
+ const vmResult = detectVM(signals);
963
+ confidence += vmResult;
964
+ const geoResult = detectGeolocationSpoofing(signals);
965
+ confidence += geoResult;
966
+ const mitmResult = detectMitM(signals);
967
+ confidence += mitmResult;
968
+ confidence = Math.min(confidence, 1);
969
+ return {
970
+ browserTampered: signals.uaSpoofed || signals.antiDetectBrowser || signals.platformMismatch,
971
+ canvasTampered: signals.canvasNoise || signals.canvasBlocked,
972
+ webglTampered: signals.webglSpoofed || signals.webglBlocked,
973
+ vmDetected: signals.vmIndicators.length >= 2,
974
+ geolocationSpoofed: signals.geolocationSpoofed,
975
+ mitmDetected: signals.mitmIndicators.length >= 1,
976
+ confidence,
977
+ signals
978
+ };
979
+ }
980
+ function detectBrowserTampering(signals) {
981
+ if (typeof navigator === "undefined" || typeof window === "undefined") return 0;
982
+ let score = 0;
983
+ const ua = navigator.userAgent;
984
+ const platform = navigator.platform || "";
985
+ const isWindowsUA = /Windows/.test(ua);
986
+ const isMacUA = /Mac/.test(ua);
987
+ const isLinuxUA = /Linux/.test(ua) && !/Android/.test(ua);
988
+ const isWindowsPlatform = /Win/.test(platform);
989
+ const isMacPlatform = /Mac/.test(platform);
990
+ const isLinuxPlatform = /Linux/.test(platform);
991
+ if (isWindowsUA && !isWindowsPlatform || isMacUA && !isMacPlatform || isLinuxUA && !isLinuxPlatform) {
992
+ signals.platformMismatch = true;
993
+ signals.uaSpoofed = true;
994
+ score += 0.3;
995
+ }
996
+ const claimsChrome = /Chrome\/\d+/.test(ua) && !/Edge|OPR|Brave/.test(ua);
997
+ if (claimsChrome) {
998
+ const hasChromiumFeatures = "chrome" in window || "CSS" in window && "paintWorklet" in window.CSS;
999
+ if (!hasChromiumFeatures && !("brave" in navigator)) {
1000
+ signals.uaSpoofed = true;
1001
+ score += 0.2;
1002
+ }
1003
+ }
1004
+ const claimsFirefox = /Firefox\/\d+/.test(ua) && !/Seamonkey/.test(ua);
1005
+ if (claimsFirefox) {
1006
+ const hasFirefoxFeatures = "InstallTrigger" in window || CSS.supports("-moz-appearance", "none");
1007
+ if (!hasFirefoxFeatures) {
1008
+ signals.uaSpoofed = true;
1009
+ score += 0.2;
1010
+ }
1011
+ }
1012
+ const nav = navigator;
1013
+ if (nav.userAgentData?.brands) {
1014
+ const brands = nav.userAgentData.brands.map((b) => b.brand);
1015
+ if (brands.length === 0 || brands.every((b) => b === "" || b === "Not A;Brand")) {
1016
+ signals.antiDetectBrowser = true;
1017
+ score += 0.25;
1018
+ }
1019
+ }
1020
+ if (!/mobile|android|iphone|ipad/i.test(ua)) {
1021
+ const pluginCount = navigator.plugins?.length || 0;
1022
+ if (pluginCount === 0 && claimsChrome) {
1023
+ signals.pluginAnomaly = true;
1024
+ score += 0.1;
1025
+ }
1026
+ }
1027
+ const cores = navigator.hardwareConcurrency || 0;
1028
+ const memory = navigator.deviceMemory || 0;
1029
+ if (cores > 0 && memory > 0) {
1030
+ if (cores >= 16 && memory <= 1) {
1031
+ signals.featureAnomaly = true;
1032
+ score += 0.15;
1033
+ }
1034
+ }
1035
+ return Math.min(score, 0.5);
1036
+ }
1037
+ function detectCanvasTampering(signals) {
1038
+ if (typeof document === "undefined") return 0;
1039
+ let score = 0;
1040
+ try {
1041
+ const canvas = document.createElement("canvas");
1042
+ canvas.width = 100;
1043
+ canvas.height = 20;
1044
+ const ctx = canvas.getContext("2d");
1045
+ if (!ctx) {
1046
+ signals.canvasBlocked = true;
1047
+ return 0.1;
1048
+ }
1049
+ ctx.fillStyle = "#ff0000";
1050
+ ctx.fillRect(0, 0, 100, 20);
1051
+ ctx.fillStyle = "#00ff00";
1052
+ ctx.font = "12px Arial";
1053
+ ctx.fillText("SitePong", 5, 14);
1054
+ const draw1 = canvas.toDataURL();
1055
+ ctx.clearRect(0, 0, 100, 20);
1056
+ ctx.fillStyle = "#ff0000";
1057
+ ctx.fillRect(0, 0, 100, 20);
1058
+ ctx.fillStyle = "#00ff00";
1059
+ ctx.font = "12px Arial";
1060
+ ctx.fillText("SitePong", 5, 14);
1061
+ const draw2 = canvas.toDataURL();
1062
+ if (draw1 !== draw2) {
1063
+ signals.canvasNoise = true;
1064
+ score += 0.35;
1065
+ }
1066
+ const imageData = ctx.getImageData(0, 0, 100, 20);
1067
+ const pixels = imageData.data;
1068
+ let allZero = true;
1069
+ let allSame = true;
1070
+ const firstPixel = pixels[0];
1071
+ for (let i = 0; i < pixels.length; i += 4) {
1072
+ if (pixels[i] !== 0 || pixels[i + 1] !== 0 || pixels[i + 2] !== 0) {
1073
+ allZero = false;
1074
+ }
1075
+ if (pixels[i] !== firstPixel) {
1076
+ allSame = false;
1077
+ }
1078
+ }
1079
+ if (allZero || allSame) {
1080
+ signals.canvasBlocked = true;
1081
+ score += 0.2;
1082
+ }
1083
+ const toDataURLDesc = Object.getOwnPropertyDescriptor(
1084
+ HTMLCanvasElement.prototype,
1085
+ "toDataURL"
1086
+ );
1087
+ if (toDataURLDesc && toDataURLDesc.value !== HTMLCanvasElement.prototype.toDataURL) {
1088
+ signals.canvasNoise = true;
1089
+ score += 0.3;
1090
+ }
1091
+ } catch {
1092
+ signals.canvasBlocked = true;
1093
+ score += 0.1;
1094
+ }
1095
+ return Math.min(score, 0.4);
1096
+ }
1097
+ function detectWebGLTampering(signals) {
1098
+ if (typeof document === "undefined") return 0;
1099
+ let score = 0;
1100
+ try {
1101
+ const canvas = document.createElement("canvas");
1102
+ const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
1103
+ if (!gl || !(gl instanceof WebGLRenderingContext)) {
1104
+ signals.webglBlocked = true;
1105
+ return 0.1;
1106
+ }
1107
+ const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
1108
+ if (!debugInfo) {
1109
+ signals.webglBlocked = true;
1110
+ return 0.1;
1111
+ }
1112
+ const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
1113
+ const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
1114
+ const spoofedVendors = ["Brian Paul", "Mesa", "VMware"];
1115
+ const spoofedRenderers = ["llvmpipe", "softpipe", "Mesa", "SwiftShader"];
1116
+ if (spoofedRenderers.some((s) => renderer.includes(s)) && !spoofedVendors.some((v) => vendor.includes(v))) {
1117
+ signals.webglSpoofed = true;
1118
+ score += 0.25;
1119
+ }
1120
+ const ua = navigator.userAgent;
1121
+ const isMobile = /mobile|android|iphone|ipad/i.test(ua);
1122
+ const isDesktopGPU = /GeForce|Radeon|Intel.*HD|GTX|RTX|Vega/i.test(renderer);
1123
+ if (isMobile && isDesktopGPU) {
1124
+ signals.webglSpoofed = true;
1125
+ score += 0.3;
1126
+ }
1127
+ const getParamDesc = Object.getOwnPropertyDescriptor(
1128
+ WebGLRenderingContext.prototype,
1129
+ "getParameter"
1130
+ );
1131
+ if (getParamDesc && typeof getParamDesc.value === "function") {
1132
+ const fnStr = getParamDesc.value.toString();
1133
+ if (!fnStr.includes("[native code]")) {
1134
+ signals.webglSpoofed = true;
1135
+ score += 0.35;
1136
+ }
1137
+ }
1138
+ const extensions = gl.getSupportedExtensions() || [];
1139
+ if (extensions.length < 5 && isDesktopGPU) {
1140
+ signals.webglSpoofed = true;
1141
+ score += 0.1;
1142
+ }
1143
+ } catch {
1144
+ signals.webglBlocked = true;
1145
+ score += 0.1;
1146
+ }
1147
+ return Math.min(score, 0.4);
1148
+ }
1149
+ function detectVM(signals) {
1150
+ if (typeof navigator === "undefined" || typeof window === "undefined") return 0;
1151
+ const indicators = [];
1152
+ const ua = navigator.userAgent;
1153
+ try {
1154
+ const canvas = document.createElement("canvas");
1155
+ const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
1156
+ if (gl && gl instanceof WebGLRenderingContext) {
1157
+ const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
1158
+ if (debugInfo) {
1159
+ const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL).toLowerCase();
1160
+ const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL).toLowerCase();
1161
+ const vmRenderers = ["vmware", "virtualbox", "hyper-v", "parallels", "qemu", "llvmpipe", "mesa"];
1162
+ const vmVendors = ["vmware", "innotek", "microsoft basic", "parallels"];
1163
+ for (const vm of vmRenderers) {
1164
+ if (renderer.includes(vm)) {
1165
+ indicators.push(`webgl_renderer:${vm}`);
1166
+ }
1167
+ }
1168
+ for (const vm of vmVendors) {
1169
+ if (vendor.includes(vm)) {
1170
+ indicators.push(`webgl_vendor:${vm}`);
1171
+ }
1172
+ }
1173
+ }
1174
+ }
1175
+ } catch {
1176
+ }
1177
+ if (typeof screen !== "undefined") {
1178
+ const w = screen.width;
1179
+ const h = screen.height;
1180
+ const vmResolutions = [
1181
+ [800, 600],
1182
+ [1024, 768],
1183
+ [1280, 800]
1184
+ ];
1185
+ if (vmResolutions.some(([vw, vh]) => w === vw && h === vh) && screen.colorDepth === 24 && navigator.hardwareConcurrency <= 2) {
1186
+ indicators.push("vm_resolution");
1187
+ }
1188
+ }
1189
+ const cores = navigator.hardwareConcurrency || 0;
1190
+ const memory = navigator.deviceMemory || 0;
1191
+ if (cores > 0 && cores <= 2 && memory > 0 && memory <= 2) {
1192
+ if (!/mobile|android|iphone/i.test(ua)) {
1193
+ indicators.push("low_hw_specs");
1194
+ }
1195
+ }
1196
+ if (typeof performance !== "undefined") {
1197
+ const times = [];
1198
+ for (let i = 0; i < 10; i++) {
1199
+ times.push(performance.now());
1200
+ }
1201
+ const uniqueTimes = new Set(times.map((t) => Math.floor(t * 1e3)));
1202
+ if (uniqueTimes.size <= 3 && times.length === 10) {
1203
+ indicators.push("coarse_timing");
1204
+ }
1205
+ }
1206
+ if (typeof navigator !== "undefined") {
1207
+ const connection = navigator.connection;
1208
+ if (connection) {
1209
+ if (connection.downlink && connection.downlink >= 10 && connection.rtt && connection.rtt <= 5) {
1210
+ indicators.push("cloud_vm_network");
1211
+ }
1212
+ }
1213
+ }
1214
+ signals.vmIndicators = indicators;
1215
+ const vmScore = Math.min(indicators.length * 0.15, 0.5);
1216
+ return vmScore;
1217
+ }
1218
+ function detectGeolocationSpoofing(signals) {
1219
+ if (typeof navigator === "undefined") return 0;
1220
+ let score = 0;
1221
+ if ("geolocation" in navigator) {
1222
+ const geoDesc = Object.getOwnPropertyDescriptor(navigator, "geolocation");
1223
+ if (geoDesc && geoDesc.configurable === false && geoDesc.get) {
1224
+ const fnStr = geoDesc.get.toString();
1225
+ if (!fnStr.includes("[native code]")) {
1226
+ signals.geolocationSpoofed = true;
1227
+ score += 0.3;
1228
+ }
1229
+ }
1230
+ try {
1231
+ const getCurrentPositionDesc = Object.getOwnPropertyDescriptor(
1232
+ Geolocation.prototype,
1233
+ "getCurrentPosition"
1234
+ );
1235
+ if (getCurrentPositionDesc?.value) {
1236
+ const fnStr = getCurrentPositionDesc.value.toString();
1237
+ if (!fnStr.includes("[native code]")) {
1238
+ signals.geolocationSpoofed = true;
1239
+ score += 0.3;
1240
+ }
1241
+ }
1242
+ } catch {
1243
+ }
1244
+ }
1245
+ if (typeof window !== "undefined") {
1246
+ try {
1247
+ const navProto = Object.getPrototypeOf(navigator);
1248
+ const geoGetter = Object.getOwnPropertyDescriptor(navProto, "geolocation");
1249
+ if (geoGetter && geoGetter.get && !geoGetter.get.toString().includes("[native code]")) {
1250
+ signals.geolocationSpoofed = true;
1251
+ score += 0.2;
1252
+ }
1253
+ } catch {
1254
+ }
1255
+ }
1256
+ return Math.min(score, 0.4);
1257
+ }
1258
+ function detectMitM(signals) {
1259
+ if (typeof window === "undefined" || typeof document === "undefined") return 0;
1260
+ const indicators = [];
1261
+ if (window.location?.protocol === "https:") {
1262
+ const scripts = document.querySelectorAll("script[src]");
1263
+ for (let i = 0; i < scripts.length; i++) {
1264
+ const src = scripts[i].getAttribute("src") || "";
1265
+ if (src.startsWith("http://") && !src.includes("localhost")) {
1266
+ indicators.push("mixed_content_script");
1267
+ break;
1268
+ }
1269
+ }
1270
+ }
1271
+ const iframes = document.querySelectorAll("iframe");
1272
+ for (let i = 0; i < iframes.length; i++) {
1273
+ const src = iframes[i].getAttribute("src") || "";
1274
+ if (src && !src.startsWith(window.location?.origin || "") && !src.startsWith("about:") && !src.startsWith("javascript:")) {
1275
+ const style = window.getComputedStyle(iframes[i]);
1276
+ if (style.display === "none" || style.visibility === "hidden" || parseInt(style.width) <= 1 || parseInt(style.height) <= 1) {
1277
+ indicators.push("hidden_iframe");
1278
+ }
1279
+ }
1280
+ }
1281
+ if ("serviceWorker" in navigator) {
1282
+ navigator.serviceWorker.getRegistrations().then((registrations) => {
1283
+ for (const reg of registrations) {
1284
+ const swUrl = reg.active?.scriptURL || "";
1285
+ if (swUrl && !swUrl.startsWith(window.location?.origin || "")) {
1286
+ indicators.push("foreign_service_worker");
1287
+ }
1288
+ }
1289
+ }).catch(() => {
1290
+ });
1291
+ }
1292
+ if (typeof performance !== "undefined" && performance.getEntriesByType) {
1293
+ try {
1294
+ const nav = performance.getEntriesByType("navigation")[0];
1295
+ if (nav) {
1296
+ const connectTime = nav.connectEnd - nav.connectStart;
1297
+ const tlsTime = nav.requestStart - nav.secureConnectionStart;
1298
+ if (connectTime > 1e3 && tlsTime > 500) {
1299
+ indicators.push("slow_tls_handshake");
1300
+ }
1301
+ }
1302
+ } catch {
1303
+ }
1304
+ }
1305
+ if (window.location?.protocol === "https:" && typeof performance !== "undefined") {
1306
+ try {
1307
+ const resources = performance.getEntriesByType("resource");
1308
+ for (const resource of resources.slice(0, 20)) {
1309
+ if (resource.transferSize === 0 && resource.encodedBodySize > 0 && !resource.name.includes("cache")) {
1310
+ indicators.push("intercepted_resource");
1311
+ break;
1312
+ }
1313
+ }
1314
+ } catch {
1315
+ }
1316
+ }
1317
+ signals.mitmIndicators = indicators;
1318
+ return Math.min(indicators.length * 0.2, 0.5);
1319
+ }
1320
+
1321
+ // src/fingerprint/signals.ts
1322
+ function collectCoreSignals() {
1323
+ return {
1324
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "",
1325
+ language: typeof navigator !== "undefined" ? navigator.language : "",
1326
+ languages: typeof navigator !== "undefined" ? Array.from(navigator.languages || []) : [],
1327
+ platform: typeof navigator !== "undefined" ? navigator.platform || "" : "",
1328
+ screenWidth: typeof screen !== "undefined" ? screen.width : 0,
1329
+ screenHeight: typeof screen !== "undefined" ? screen.height : 0,
1330
+ screenColorDepth: typeof screen !== "undefined" ? screen.colorDepth : 0,
1331
+ devicePixelRatio: typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1,
1332
+ timezoneOffset: (/* @__PURE__ */ new Date()).getTimezoneOffset(),
1333
+ timezone: Intl?.DateTimeFormat?.()?.resolvedOptions?.()?.timeZone || "",
1334
+ sessionStorage: hasSessionStorage(),
1335
+ localStorage: hasLocalStorage(),
1336
+ indexedDB: typeof indexedDB !== "undefined",
1337
+ cookieEnabled: typeof navigator !== "undefined" ? navigator.cookieEnabled : false,
1338
+ hardwareConcurrency: typeof navigator !== "undefined" ? navigator.hardwareConcurrency || 0 : 0,
1339
+ maxTouchPoints: typeof navigator !== "undefined" ? navigator.maxTouchPoints || 0 : 0,
1340
+ deviceMemory: typeof navigator !== "undefined" ? navigator.deviceMemory : void 0
1341
+ };
1342
+ }
1343
+ function collectExtendedSignals() {
1344
+ const signals = {};
1345
+ signals.canvasFingerprint = getCanvasFingerprint();
1346
+ const webgl = getWebGLInfo();
1347
+ signals.webglVendor = webgl.vendor;
1348
+ signals.webglRenderer = webgl.renderer;
1349
+ signals.audioFingerprint = getAudioFingerprint();
1350
+ return signals;
1351
+ }
1352
+ function collectDeviceSignals(includeExtended) {
1353
+ const core = collectCoreSignals();
1354
+ const signals = { ...core };
1355
+ if (includeExtended) {
1356
+ signals.extended = collectExtendedSignals();
1357
+ }
1358
+ const smart = collectSmartSignals();
1359
+ signals.smart = smart;
1360
+ signals.incognito = smart.incognito;
1361
+ signals.bot = smart.bot;
1362
+ return signals;
1363
+ }
1364
+ function collectSmartSignals() {
1365
+ const signals = {};
1366
+ signals.incognito = detectIncognito();
1367
+ signals.bot = detectBot();
1368
+ signals.privacyBrowser = detectPrivacyBrowser();
1369
+ signals.devToolsOpen = detectDevTools();
1370
+ signals.connectionType = getConnectionType();
1371
+ signals.doNotTrack = getDoNotTrack();
1372
+ signals.notificationsEnabled = getNotificationsPermission();
1373
+ const deviceAge = getDeviceAge();
1374
+ signals.deviceFirstSeen = deviceAge.firstSeen;
1375
+ signals.deviceVisitCount = deviceAge.visitCount;
1376
+ const tampering = detectTampering();
1377
+ signals.browserTampered = tampering.browserTampered;
1378
+ signals.canvasTampered = tampering.canvasTampered;
1379
+ signals.webglTampered = tampering.webglTampered;
1380
+ signals.vmDetected = tampering.vmDetected;
1381
+ signals.vmIndicators = tampering.signals.vmIndicators;
1382
+ return signals;
1383
+ }
1384
+ function hasSessionStorage() {
1385
+ try {
1386
+ const key = "__sp_test__";
1387
+ sessionStorage.setItem(key, "1");
1388
+ sessionStorage.removeItem(key);
1389
+ return true;
1390
+ } catch {
1391
+ return false;
1392
+ }
1393
+ }
1394
+ function hasLocalStorage() {
1395
+ try {
1396
+ const key = "__sp_test__";
1397
+ localStorage.setItem(key, "1");
1398
+ localStorage.removeItem(key);
1399
+ return true;
1400
+ } catch {
1401
+ return false;
1402
+ }
1403
+ }
1404
+ function getCanvasFingerprint() {
1405
+ if (typeof document === "undefined") return void 0;
1406
+ try {
1407
+ const canvas = document.createElement("canvas");
1408
+ const ctx = canvas.getContext("2d");
1409
+ if (!ctx) return void 0;
1410
+ canvas.width = 200;
1411
+ canvas.height = 50;
1412
+ ctx.textBaseline = "top";
1413
+ ctx.font = "14px Arial";
1414
+ ctx.fillStyle = "#f60";
1415
+ ctx.fillRect(0, 0, 200, 50);
1416
+ ctx.fillStyle = "#069";
1417
+ ctx.fillText("SitePong fingerprint", 2, 15);
1418
+ ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
1419
+ ctx.fillText("SitePong fingerprint", 4, 17);
1420
+ return hashString(canvas.toDataURL());
1421
+ } catch {
1422
+ return void 0;
1423
+ }
1424
+ }
1425
+ function getWebGLInfo() {
1426
+ if (typeof document === "undefined") return {};
1427
+ try {
1428
+ const canvas = document.createElement("canvas");
1429
+ const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
1430
+ if (!gl || !(gl instanceof WebGLRenderingContext)) return {};
1431
+ const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
1432
+ if (!debugInfo) return {};
1433
+ return {
1434
+ vendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL),
1435
+ renderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)
1436
+ };
1437
+ } catch {
1438
+ return {};
1439
+ }
1440
+ }
1441
+ function getAudioFingerprint() {
1442
+ if (typeof window === "undefined" || typeof OfflineAudioContext === "undefined") {
1443
+ return void 0;
1444
+ }
1445
+ try {
1446
+ const context = new OfflineAudioContext(1, 44100, 44100);
1447
+ const oscillator = context.createOscillator();
1448
+ oscillator.type = "triangle";
1449
+ oscillator.frequency.setValueAtTime(1e4, context.currentTime);
1450
+ const compressor = context.createDynamicsCompressor();
1451
+ compressor.threshold.setValueAtTime(-50, context.currentTime);
1452
+ compressor.knee.setValueAtTime(40, context.currentTime);
1453
+ compressor.ratio.setValueAtTime(12, context.currentTime);
1454
+ compressor.attack.setValueAtTime(0, context.currentTime);
1455
+ compressor.release.setValueAtTime(0.25, context.currentTime);
1456
+ oscillator.connect(compressor);
1457
+ compressor.connect(context.destination);
1458
+ oscillator.start(0);
1459
+ return hashString(`audio:${context.sampleRate}:${context.length}`);
1460
+ } catch {
1461
+ return void 0;
1462
+ }
1463
+ }
1464
+ function detectIncognito() {
1465
+ if (typeof window === "undefined") return false;
1466
+ try {
1467
+ const testKey = "__sp_incognito_test__";
1468
+ try {
1469
+ localStorage.setItem(testKey, new Array(100).join("x"));
1470
+ localStorage.removeItem(testKey);
1471
+ } catch {
1472
+ return true;
1473
+ }
1474
+ if (typeof indexedDB !== "undefined") {
1475
+ try {
1476
+ const db = indexedDB.open("__sp_test__");
1477
+ db.onerror = () => {
1478
+ };
1479
+ } catch {
1480
+ return true;
1481
+ }
1482
+ }
1483
+ if ("webkitRequestFileSystem" in window) {
1484
+ let detected = false;
1485
+ try {
1486
+ window.webkitRequestFileSystem(
1487
+ 0,
1488
+ 1,
1489
+ () => {
1490
+ detected = false;
1491
+ },
1492
+ () => {
1493
+ detected = true;
1494
+ }
1495
+ );
1496
+ } catch {
1497
+ }
1498
+ if (detected) return true;
1499
+ }
1500
+ const perf = performance;
1501
+ if (perf.memory) {
1502
+ const heapLimit = perf.memory.jsHeapSizeLimit || 0;
1503
+ if (heapLimit > 0 && heapLimit < 1073741824) {
1504
+ return true;
1505
+ }
1506
+ }
1507
+ return false;
1508
+ } catch {
1509
+ return false;
1510
+ }
1511
+ }
1512
+ function detectBot() {
1513
+ if (typeof navigator === "undefined") return false;
1514
+ const ua = navigator.userAgent.toLowerCase();
1515
+ const botPatterns = [
1516
+ "bot",
1517
+ "crawler",
1518
+ "spider",
1519
+ "headless",
1520
+ "phantom",
1521
+ "puppeteer",
1522
+ "selenium",
1523
+ "webdriver",
1524
+ "playwright"
1525
+ ];
1526
+ if (botPatterns.some((pattern) => ua.includes(pattern))) {
1527
+ return true;
1528
+ }
1529
+ if ("webdriver" in navigator && navigator.webdriver) {
1530
+ return true;
1531
+ }
1532
+ if (navigator.plugins && navigator.plugins.length === 0 && !/mobile|android|iphone/i.test(ua)) {
1533
+ return true;
1534
+ }
1535
+ return false;
1536
+ }
1537
+ function detectPrivacyBrowser() {
1538
+ if (typeof navigator === "undefined") return false;
1539
+ const ua = navigator.userAgent.toLowerCase();
1540
+ if ("brave" in navigator) return true;
1541
+ if (ua.includes("focus") && ua.includes("firefox")) return true;
1542
+ if (ua.includes("duckduckgo")) return true;
1543
+ if (typeof window !== "undefined") {
1544
+ if (screen.width === 1e3 && screen.height === 1e3) return true;
1545
+ if ((/* @__PURE__ */ new Date()).getTimezoneOffset() === 0 && navigator.language !== "en") return true;
1546
+ }
1547
+ return false;
1548
+ }
1549
+ function detectDevTools() {
1550
+ if (typeof window === "undefined") return false;
1551
+ try {
1552
+ const widthThreshold = window.outerWidth - window.innerWidth > 160;
1553
+ const heightThreshold = window.outerHeight - window.innerHeight > 160;
1554
+ if (widthThreshold || heightThreshold) return true;
1555
+ } catch {
1556
+ }
1557
+ return false;
1558
+ }
1559
+ function getConnectionType() {
1560
+ if (typeof navigator === "undefined") return void 0;
1561
+ const connection = navigator.connection;
1562
+ if (!connection) return void 0;
1563
+ return connection.effectiveType || connection.type || void 0;
1564
+ }
1565
+ function getDoNotTrack() {
1566
+ if (typeof navigator === "undefined") return false;
1567
+ const dnt = navigator.doNotTrack;
1568
+ return dnt === "1" || dnt === "yes";
1569
+ }
1570
+ function getNotificationsPermission() {
1571
+ if (typeof Notification === "undefined") return false;
1572
+ return Notification.permission === "granted";
1573
+ }
1574
+ function getDeviceAge() {
1575
+ const STORAGE_KEY2 = "sitepong_device_age";
1576
+ const result = { visitCount: 1 };
1577
+ if (typeof localStorage === "undefined") return result;
1578
+ try {
1579
+ const stored = localStorage.getItem(STORAGE_KEY2);
1580
+ if (stored) {
1581
+ const parsed = JSON.parse(stored);
1582
+ result.firstSeen = parsed.firstSeen;
1583
+ result.visitCount = (parsed.visitCount || 0) + 1;
1584
+ } else {
1585
+ result.firstSeen = (/* @__PURE__ */ new Date()).toISOString();
1586
+ }
1587
+ localStorage.setItem(STORAGE_KEY2, JSON.stringify({
1588
+ firstSeen: result.firstSeen,
1589
+ visitCount: result.visitCount
1590
+ }));
1591
+ } catch {
1592
+ }
1593
+ return result;
1594
+ }
1595
+ function hashString(str) {
1596
+ let hash = 0;
1597
+ for (let i = 0; i < str.length; i++) {
1598
+ const char = str.charCodeAt(i);
1599
+ hash = (hash << 5) - hash + char;
1600
+ hash = hash & hash;
1601
+ }
1602
+ return Math.abs(hash).toString(16);
1603
+ }
1604
+
1605
+ // src/fingerprint/behavior.ts
1606
+ var BehaviorAnalyzer = class {
1607
+ constructor() {
1608
+ this.mousePoints = [];
1609
+ this.keyDownTimes = /* @__PURE__ */ new Map();
1610
+ this.keyIntervals = [];
1611
+ this.keyHoldTimes = [];
1612
+ this.lastKeyTime = 0;
1613
+ this.scrollEvents = [];
1614
+ this.clickTimes = [];
1615
+ this.running = false;
1616
+ this.maxSamples = 500;
1617
+ this.handleMouseMove = (e) => {
1618
+ if (this.mousePoints.length >= this.maxSamples) return;
1619
+ this.mousePoints.push({ x: e.clientX, y: e.clientY, t: Date.now() });
1620
+ };
1621
+ this.handleKeyDown = (e) => {
1622
+ if (this.keyIntervals.length >= this.maxSamples) return;
1623
+ const now = Date.now();
1624
+ this.keyDownTimes.set(e.key, now);
1625
+ if (this.lastKeyTime > 0) {
1626
+ this.keyIntervals.push(now - this.lastKeyTime);
1627
+ }
1628
+ this.lastKeyTime = now;
1629
+ };
1630
+ this.handleKeyUp = (e) => {
1631
+ const downTime = this.keyDownTimes.get(e.key);
1632
+ if (downTime) {
1633
+ this.keyHoldTimes.push(Date.now() - downTime);
1634
+ this.keyDownTimes.delete(e.key);
1635
+ }
1636
+ };
1637
+ this.handleScroll = () => {
1638
+ if (this.scrollEvents.length >= this.maxSamples) return;
1639
+ this.scrollEvents.push({ deltaY: window.scrollY, t: Date.now() });
1640
+ };
1641
+ this.handleClick = () => {
1642
+ if (this.clickTimes.length >= this.maxSamples) return;
1643
+ this.clickTimes.push(Date.now());
1644
+ };
1645
+ }
1646
+ start() {
1647
+ if (this.running || typeof window === "undefined") return;
1648
+ this.running = true;
1649
+ window.addEventListener("mousemove", this.handleMouseMove, { passive: true });
1650
+ window.addEventListener("keydown", this.handleKeyDown, { passive: true });
1651
+ window.addEventListener("keyup", this.handleKeyUp, { passive: true });
1652
+ window.addEventListener("scroll", this.handleScroll, { passive: true });
1653
+ window.addEventListener("click", this.handleClick, { passive: true });
1654
+ }
1655
+ stop() {
1656
+ if (!this.running || typeof window === "undefined") return;
1657
+ this.running = false;
1658
+ window.removeEventListener("mousemove", this.handleMouseMove);
1659
+ window.removeEventListener("keydown", this.handleKeyDown);
1660
+ window.removeEventListener("keyup", this.handleKeyUp);
1661
+ window.removeEventListener("scroll", this.handleScroll);
1662
+ window.removeEventListener("click", this.handleClick);
1663
+ }
1664
+ getMetrics() {
1665
+ const mouse = this.analyzeMouseMovement();
1666
+ const keyboard = this.analyzeKeyboard();
1667
+ const scroll = this.analyzeScroll();
1668
+ const clicks = this.analyzeClicks();
1669
+ const score = this.calculateHumanScore(mouse, keyboard, scroll, clicks);
1670
+ return { mouse, keyboard, scroll, clicks, score };
1671
+ }
1672
+ reset() {
1673
+ this.mousePoints = [];
1674
+ this.keyDownTimes.clear();
1675
+ this.keyIntervals = [];
1676
+ this.keyHoldTimes = [];
1677
+ this.lastKeyTime = 0;
1678
+ this.scrollEvents = [];
1679
+ this.clickTimes = [];
1680
+ }
1681
+ analyzeMouseMovement() {
1682
+ if (this.mousePoints.length < 3) {
1683
+ return {
1684
+ totalMovements: this.mousePoints.length,
1685
+ averageSpeed: 0,
1686
+ maxSpeed: 0,
1687
+ averageAcceleration: 0,
1688
+ straightLineRatio: 0,
1689
+ jitter: 0,
1690
+ hasMovement: false
1691
+ };
1692
+ }
1693
+ const speeds = [];
1694
+ const accelerations = [];
1695
+ let straightSegments = 0;
1696
+ let totalSegments = 0;
1697
+ let jitterSum = 0;
1698
+ for (let i = 1; i < this.mousePoints.length; i++) {
1699
+ const prev = this.mousePoints[i - 1];
1700
+ const curr = this.mousePoints[i];
1701
+ const dt = curr.t - prev.t || 1;
1702
+ const dx = curr.x - prev.x;
1703
+ const dy = curr.y - prev.y;
1704
+ const distance = Math.sqrt(dx * dx + dy * dy);
1705
+ const speed = distance / dt;
1706
+ speeds.push(speed);
1707
+ if (distance < 3 && distance > 0) {
1708
+ jitterSum++;
1709
+ }
1710
+ if (i >= 2) {
1711
+ const pp = this.mousePoints[i - 2];
1712
+ const expectedX = pp.x + (curr.x - pp.x) * ((prev.t - pp.t) / (curr.t - pp.t));
1713
+ const expectedY = pp.y + (curr.y - pp.y) * ((prev.t - pp.t) / (curr.t - pp.t));
1714
+ const deviation = Math.sqrt((prev.x - expectedX) ** 2 + (prev.y - expectedY) ** 2);
1715
+ totalSegments++;
1716
+ if (deviation < 2) straightSegments++;
1717
+ }
1718
+ if (i >= 2) {
1719
+ const prevSpeed = speeds[speeds.length - 2] || 0;
1720
+ accelerations.push(Math.abs(speed - prevSpeed) / dt);
1721
+ }
1722
+ }
1723
+ const avgSpeed = speeds.reduce((a, b) => a + b, 0) / speeds.length;
1724
+ const maxSpeed = Math.max(...speeds);
1725
+ const avgAccel = accelerations.length > 0 ? accelerations.reduce((a, b) => a + b, 0) / accelerations.length : 0;
1726
+ return {
1727
+ totalMovements: this.mousePoints.length,
1728
+ averageSpeed: Math.round(avgSpeed * 1e3) / 1e3,
1729
+ maxSpeed: Math.round(maxSpeed * 1e3) / 1e3,
1730
+ averageAcceleration: Math.round(avgAccel * 1e3) / 1e3,
1731
+ straightLineRatio: totalSegments > 0 ? straightSegments / totalSegments : 0,
1732
+ jitter: this.mousePoints.length > 0 ? jitterSum / this.mousePoints.length : 0,
1733
+ hasMovement: true
1734
+ };
1735
+ }
1736
+ analyzeKeyboard() {
1737
+ if (this.keyIntervals.length < 2) {
1738
+ return {
1739
+ totalKeystrokes: this.keyIntervals.length,
1740
+ averageInterval: 0,
1741
+ intervalVariance: 0,
1742
+ holdTimeAverage: 0,
1743
+ holdTimeVariance: 0,
1744
+ hasKeystrokes: false
1745
+ };
1746
+ }
1747
+ const avgInterval = this.keyIntervals.reduce((a, b) => a + b, 0) / this.keyIntervals.length;
1748
+ const intervalVar = this.variance(this.keyIntervals);
1749
+ const avgHold = this.keyHoldTimes.length > 0 ? this.keyHoldTimes.reduce((a, b) => a + b, 0) / this.keyHoldTimes.length : 0;
1750
+ const holdVar = this.keyHoldTimes.length > 0 ? this.variance(this.keyHoldTimes) : 0;
1751
+ return {
1752
+ totalKeystrokes: this.keyIntervals.length + 1,
1753
+ averageInterval: Math.round(avgInterval),
1754
+ intervalVariance: Math.round(intervalVar),
1755
+ holdTimeAverage: Math.round(avgHold),
1756
+ holdTimeVariance: Math.round(holdVar),
1757
+ hasKeystrokes: true
1758
+ };
1759
+ }
1760
+ analyzeScroll() {
1761
+ if (this.scrollEvents.length < 2) {
1762
+ return { totalScrolls: this.scrollEvents.length, averageSpeed: 0, smoothness: 0, hasScroll: false };
1763
+ }
1764
+ const speeds = [];
1765
+ for (let i = 1; i < this.scrollEvents.length; i++) {
1766
+ const dt = this.scrollEvents[i].t - this.scrollEvents[i - 1].t || 1;
1767
+ const dy = Math.abs(this.scrollEvents[i].deltaY - this.scrollEvents[i - 1].deltaY);
1768
+ speeds.push(dy / dt);
1769
+ }
1770
+ const avgSpeed = speeds.reduce((a, b) => a + b, 0) / speeds.length;
1771
+ const speedVar = this.variance(speeds);
1772
+ const smoothness = avgSpeed > 0 ? Math.min(1, avgSpeed / (speedVar + avgSpeed)) : 0;
1773
+ return {
1774
+ totalScrolls: this.scrollEvents.length,
1775
+ averageSpeed: Math.round(avgSpeed * 100) / 100,
1776
+ smoothness: Math.round(smoothness * 100) / 100,
1777
+ hasScroll: true
1778
+ };
1779
+ }
1780
+ analyzeClicks() {
1781
+ if (this.clickTimes.length < 2) {
1782
+ return {
1783
+ totalClicks: this.clickTimes.length,
1784
+ averageInterval: 0,
1785
+ intervalVariance: 0,
1786
+ doubleClickCount: 0,
1787
+ hasClicks: this.clickTimes.length > 0
1788
+ };
1789
+ }
1790
+ const intervals = [];
1791
+ let doubleClicks = 0;
1792
+ for (let i = 1; i < this.clickTimes.length; i++) {
1793
+ const interval = this.clickTimes[i] - this.clickTimes[i - 1];
1794
+ intervals.push(interval);
1795
+ if (interval < 500) doubleClicks++;
1796
+ }
1797
+ const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;
1798
+ const intervalVar = this.variance(intervals);
1799
+ return {
1800
+ totalClicks: this.clickTimes.length,
1801
+ averageInterval: Math.round(avgInterval),
1802
+ intervalVariance: Math.round(intervalVar),
1803
+ doubleClickCount: doubleClicks,
1804
+ hasClicks: true
1805
+ };
1806
+ }
1807
+ calculateHumanScore(mouse, keyboard, scroll, clicks) {
1808
+ let score = 0.5;
1809
+ let factors = 0;
1810
+ if (mouse.hasMovement) {
1811
+ factors++;
1812
+ let mouseScore = 0.5;
1813
+ if (mouse.jitter > 0.05) mouseScore += 0.15;
1814
+ if (mouse.straightLineRatio < 0.5) mouseScore += 0.15;
1815
+ else if (mouse.straightLineRatio > 0.9) mouseScore -= 0.2;
1816
+ if (mouse.averageSpeed > 0 && mouse.maxSpeed / mouse.averageSpeed > 3) {
1817
+ mouseScore += 0.1;
1818
+ }
1819
+ if (mouse.averageAcceleration > 0) mouseScore += 0.1;
1820
+ score += Math.max(0, Math.min(1, mouseScore)) - 0.5;
1821
+ }
1822
+ if (keyboard.hasKeystrokes) {
1823
+ factors++;
1824
+ let keyScore = 0.5;
1825
+ if (keyboard.intervalVariance > 1e3) keyScore += 0.2;
1826
+ else if (keyboard.intervalVariance < 100) keyScore -= 0.2;
1827
+ const cv = keyboard.averageInterval > 0 ? Math.sqrt(keyboard.intervalVariance) / keyboard.averageInterval : 0;
1828
+ if (cv > 0.3) keyScore += 0.15;
1829
+ if (keyboard.holdTimeVariance > 500) keyScore += 0.1;
1830
+ score += Math.max(0, Math.min(1, keyScore)) - 0.5;
1831
+ }
1832
+ if (scroll.hasScroll) {
1833
+ factors++;
1834
+ let scrollScore = 0.5;
1835
+ if (scroll.smoothness > 0.3 && scroll.smoothness < 0.9) {
1836
+ scrollScore += 0.2;
1837
+ }
1838
+ score += Math.max(0, Math.min(1, scrollScore)) - 0.5;
1839
+ }
1840
+ if (clicks.hasClicks) {
1841
+ factors++;
1842
+ let clickScore = 0.5;
1843
+ if (clicks.intervalVariance > 5e4) clickScore += 0.15;
1844
+ else if (clicks.intervalVariance < 100) clickScore -= 0.2;
1845
+ score += Math.max(0, Math.min(1, clickScore)) - 0.5;
1846
+ }
1847
+ if (factors === 0) return 0.3;
1848
+ return Math.max(0, Math.min(1, score));
1849
+ }
1850
+ variance(values) {
1851
+ if (values.length < 2) return 0;
1852
+ const mean = values.reduce((a, b) => a + b, 0) / values.length;
1853
+ return values.reduce((sum, v) => sum + (v - mean) ** 2, 0) / (values.length - 1);
1854
+ }
1855
+ };
1856
+
1857
+ // src/fingerprint/vpn-detection.ts
1858
+ async function detectVPN() {
1859
+ const signals = {
1860
+ webrtcLeak: null,
1861
+ timezoneIPMismatch: false,
1862
+ dnsLeak: null,
1863
+ connectionType: null,
1864
+ multipleIPs: false,
1865
+ torBrowser: false,
1866
+ knownVPNExtension: false
1867
+ };
1868
+ let confidence = 0;
1869
+ let vpnDetected = false;
1870
+ let proxyDetected = false;
1871
+ let torDetected = false;
1872
+ signals.torBrowser = detectTorBrowser();
1873
+ if (signals.torBrowser) {
1874
+ torDetected = true;
1875
+ confidence += 0.9;
1876
+ }
1877
+ try {
1878
+ signals.webrtcLeak = await detectWebRTCLeak();
1879
+ if (signals.webrtcLeak) {
1880
+ vpnDetected = true;
1881
+ confidence += 0.3;
1882
+ }
1883
+ } catch {
1884
+ signals.webrtcLeak = null;
1885
+ }
1886
+ signals.connectionType = getConnectionType2();
1887
+ if (signals.connectionType === "unknown" || signals.connectionType === null) {
1888
+ confidence += 0.05;
1889
+ }
1890
+ signals.knownVPNExtension = detectVPNExtensions();
1891
+ if (signals.knownVPNExtension) {
1892
+ vpnDetected = true;
1893
+ confidence += 0.4;
1894
+ }
1895
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
1896
+ const lang = navigator.language;
1897
+ if (tz && lang) {
1898
+ const tzCountry = getTimezoneCountry(tz);
1899
+ const langCountry = lang.split("-")[1]?.toUpperCase();
1900
+ if (tzCountry && langCountry && tzCountry !== langCountry) {
1901
+ signals.timezoneIPMismatch = true;
1902
+ confidence += 0.1;
1903
+ }
1904
+ }
1905
+ confidence = Math.min(confidence, 1);
1906
+ if (confidence < 0.3) {
1907
+ vpnDetected = false;
1908
+ proxyDetected = false;
1909
+ }
1910
+ return {
1911
+ vpnDetected,
1912
+ proxyDetected,
1913
+ torDetected,
1914
+ confidence,
1915
+ signals
1916
+ };
1917
+ }
1918
+ function detectTorBrowser() {
1919
+ if (typeof window === "undefined") return false;
1920
+ const checks = [];
1921
+ checks.push(window.screen.width === 1e3 && window.screen.height === 1e3);
1922
+ checks.push(!window.MediaDevices);
1923
+ checks.push(navigator.plugins.length === 0);
1924
+ checks.push((/* @__PURE__ */ new Date()).getTimezoneOffset() === 0);
1925
+ const ua = navigator.userAgent;
1926
+ checks.push(ua.includes("Firefox") && !ua.includes("Chrome"));
1927
+ const score = checks.filter(Boolean).length;
1928
+ return score >= 3;
1929
+ }
1930
+ async function detectWebRTCLeak() {
1931
+ if (typeof window === "undefined" || !window.RTCPeerConnection) {
1932
+ return false;
1933
+ }
1934
+ return new Promise((resolve) => {
1935
+ const timeout = setTimeout(() => resolve(false), 3e3);
1936
+ const localIPs = /* @__PURE__ */ new Set();
1937
+ try {
1938
+ const pc = new RTCPeerConnection({
1939
+ iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
1940
+ });
1941
+ pc.createDataChannel("");
1942
+ pc.onicecandidate = (event) => {
1943
+ if (!event.candidate) {
1944
+ clearTimeout(timeout);
1945
+ pc.close();
1946
+ resolve(localIPs.size > 1);
1947
+ return;
1948
+ }
1949
+ const candidate = event.candidate.candidate;
1950
+ const ipMatch = candidate.match(/(\d{1,3}\.){3}\d{1,3}/);
1951
+ if (ipMatch) {
1952
+ localIPs.add(ipMatch[0]);
1953
+ }
1954
+ };
1955
+ pc.createOffer().then((offer) => pc.setLocalDescription(offer)).catch(() => {
1956
+ clearTimeout(timeout);
1957
+ resolve(false);
1958
+ });
1959
+ } catch {
1960
+ clearTimeout(timeout);
1961
+ resolve(false);
1962
+ }
1963
+ });
1964
+ }
1965
+ function getConnectionType2() {
1966
+ if (typeof navigator === "undefined") return null;
1967
+ const nav = navigator;
1968
+ if (nav.connection) {
1969
+ return nav.connection.type || nav.connection.effectiveType || null;
1970
+ }
1971
+ return null;
1972
+ }
1973
+ function detectVPNExtensions() {
1974
+ if (typeof document === "undefined") return false;
1975
+ const body = document.body;
1976
+ if (!body) return false;
1977
+ const vpnIndicators = [
1978
+ "[data-windscribe]",
1979
+ "[data-nord]",
1980
+ ".surfshark-extension",
1981
+ "#vpn-indicator",
1982
+ "[data-expressvpn]"
1983
+ ];
1984
+ for (const selector of vpnIndicators) {
1985
+ try {
1986
+ if (document.querySelector(selector)) return true;
1987
+ } catch {
1988
+ }
1989
+ }
1990
+ return false;
1991
+ }
1992
+ function getTimezoneCountry(tz) {
1993
+ const tzMap = {
1994
+ "America/New_York": "US",
1995
+ "America/Chicago": "US",
1996
+ "America/Denver": "US",
1997
+ "America/Los_Angeles": "US",
1998
+ "Europe/London": "GB",
1999
+ "Europe/Paris": "FR",
2000
+ "Europe/Berlin": "DE",
2001
+ "Europe/Madrid": "ES",
2002
+ "Europe/Rome": "IT",
2003
+ "Asia/Tokyo": "JP",
2004
+ "Asia/Shanghai": "CN",
2005
+ "Asia/Kolkata": "IN",
2006
+ "Asia/Seoul": "KR",
2007
+ "Australia/Sydney": "AU",
2008
+ "America/Toronto": "CA",
2009
+ "America/Sao_Paulo": "BR",
2010
+ "America/Mexico_City": "MX"
2011
+ };
2012
+ return tzMap[tz] || null;
2013
+ }
2014
+
2015
+ // src/fingerprint/manager.ts
2016
+ var DEFAULT_ENDPOINT3 = "https://ingest.sitepong.com";
2017
+ var CACHE_KEY = "sitepong_visitor";
2018
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
2019
+ var FingerprintManager = class {
2020
+ constructor(config) {
2021
+ this.cachedVisitor = null;
2022
+ this.pendingRequest = null;
2023
+ this.config = {
2024
+ endpoint: DEFAULT_ENDPOINT3,
2025
+ enabled: true,
2026
+ debug: false,
2027
+ ...config
2028
+ };
2029
+ this.behaviorAnalyzer = new BehaviorAnalyzer();
2030
+ if (this.config.enabled && typeof window !== "undefined") {
2031
+ this.behaviorAnalyzer.start();
2032
+ }
2033
+ this.loadFromStorage();
2034
+ }
2035
+ async getVisitorId() {
2036
+ if (!this.config.enabled) {
2037
+ throw new Error("Fingerprint is not enabled");
2038
+ }
2039
+ if (this.cachedVisitor && !this.isCacheExpired()) {
2040
+ this.log("Returning cached visitor ID:", this.cachedVisitor.result.visitorId);
2041
+ return this.cachedVisitor.result;
2042
+ }
2043
+ if (this.pendingRequest) {
2044
+ return this.pendingRequest;
2045
+ }
2046
+ this.pendingRequest = this.fetchVisitorId();
2047
+ try {
2048
+ const result = await this.pendingRequest;
2049
+ return result;
2050
+ } finally {
2051
+ this.pendingRequest = null;
2052
+ }
2053
+ }
2054
+ async getDeviceSignals() {
2055
+ return collectDeviceSignals(this.config.extendedSignals ?? false);
2056
+ }
2057
+ async getFraudCheck() {
2058
+ const [visitorResult, signals, vpnResult] = await Promise.all([
2059
+ this.getVisitorId(),
2060
+ this.getDeviceSignals(),
2061
+ detectVPN().catch(() => null)
2062
+ ]);
2063
+ const behaviorMetrics = this.behaviorAnalyzer.getMetrics();
2064
+ signals.behavior = {
2065
+ score: behaviorMetrics.score,
2066
+ mouseMovements: behaviorMetrics.mouse.totalMovements,
2067
+ keystrokes: behaviorMetrics.keyboard.totalKeystrokes,
2068
+ scrolls: behaviorMetrics.scroll.totalScrolls,
2069
+ clicks: behaviorMetrics.clicks.totalClicks,
2070
+ isHuman: behaviorMetrics.score > 0.6
2071
+ };
2072
+ const vpnDetected = vpnResult?.vpnDetected || vpnResult?.torDetected || false;
2073
+ return {
2074
+ visitorId: visitorResult.visitorId,
2075
+ riskScore: this.calculateRiskScore(signals, vpnDetected),
2076
+ signals,
2077
+ flags: {
2078
+ incognito: signals.smart?.incognito || signals.incognito,
2079
+ bot: signals.smart?.bot || signals.bot || !signals.behavior.isHuman,
2080
+ vpn: vpnDetected || signals.smart?.timezoneMismatch,
2081
+ tampered: signals.smart?.privacyBrowser
2082
+ }
2083
+ };
2084
+ }
2085
+ stopBehaviorTracking() {
2086
+ this.behaviorAnalyzer.stop();
2087
+ }
2088
+ async fetchVisitorId() {
2089
+ const signals = await this.getDeviceSignals();
2090
+ const endpoint = this.config.visitorEndpoint || `${this.config.endpoint}/api/visitors`;
2091
+ try {
2092
+ const response = await fetch(endpoint, {
2093
+ method: "POST",
2094
+ headers: {
2095
+ "Content-Type": "application/json",
2096
+ "X-API-Key": this.config.apiKey
2097
+ },
2098
+ body: JSON.stringify({ signals })
2099
+ });
2100
+ if (!response.ok) {
2101
+ throw new Error(`HTTP ${response.status}`);
2102
+ }
2103
+ const result = await response.json();
2104
+ this.cachedVisitor = {
2105
+ result,
2106
+ signals,
2107
+ cachedAt: Date.now()
2108
+ };
2109
+ this.saveToStorage();
2110
+ this.log("Visitor ID fetched:", result.visitorId);
2111
+ return result;
2112
+ } catch (err) {
2113
+ this.log("Failed to fetch visitor ID:", err);
2114
+ throw err;
2115
+ }
2116
+ }
2117
+ calculateRiskScore(signals, vpnDetected = false) {
2118
+ let score = 0;
2119
+ const smart = signals.smart;
2120
+ if (smart?.bot || signals.bot) score += 0.8;
2121
+ if (signals.behavior) {
2122
+ if (signals.behavior.score < 0.3) score += 0.4;
2123
+ else if (signals.behavior.score < 0.5) score += 0.2;
2124
+ }
2125
+ if (vpnDetected) score += 0.25;
2126
+ if (smart?.incognito || signals.incognito) score += 0.2;
2127
+ if (smart?.devToolsOpen) score += 0.1;
2128
+ if (smart?.privacyBrowser) score += 0.1;
2129
+ if (signals.hardwareConcurrency === 0) score += 0.1;
2130
+ if (signals.maxTouchPoints === 0 && /mobile|android|iphone/i.test(signals.userAgent)) {
2131
+ score += 0.15;
2132
+ }
2133
+ if (!signals.cookieEnabled) score += 0.1;
2134
+ if (smart?.doNotTrack) score += 0.05;
2135
+ if (smart?.deviceVisitCount === 1) score += 0.05;
2136
+ return Math.round(Math.min(score, 1) * 100) / 100;
2137
+ }
2138
+ isCacheExpired() {
2139
+ if (!this.cachedVisitor) return true;
2140
+ return Date.now() - this.cachedVisitor.cachedAt > CACHE_TTL_MS;
2141
+ }
2142
+ loadFromStorage() {
2143
+ if (typeof window === "undefined" || typeof localStorage === "undefined") return;
2144
+ try {
2145
+ const stored = localStorage.getItem(CACHE_KEY);
2146
+ if (!stored) return;
2147
+ const parsed = JSON.parse(stored);
2148
+ if (!this.isCacheExpiredAt(parsed.cachedAt)) {
2149
+ this.cachedVisitor = parsed;
2150
+ this.log("Loaded cached visitor from storage");
2151
+ } else {
2152
+ localStorage.removeItem(CACHE_KEY);
2153
+ }
2154
+ } catch {
2155
+ }
2156
+ }
2157
+ saveToStorage() {
2158
+ if (typeof window === "undefined" || typeof localStorage === "undefined") return;
2159
+ if (!this.cachedVisitor) return;
2160
+ try {
2161
+ localStorage.setItem(CACHE_KEY, JSON.stringify(this.cachedVisitor));
2162
+ } catch {
2163
+ }
2164
+ }
2165
+ isCacheExpiredAt(cachedAt) {
2166
+ return Date.now() - cachedAt > CACHE_TTL_MS;
2167
+ }
2168
+ log(...args) {
2169
+ if (this.config.debug) {
2170
+ console.log("[SitePong Fingerprint]", ...args);
2171
+ }
2172
+ }
2173
+ };
2174
+
2175
+ // src/replay/serializer.ts
2176
+ var nodeIdCounter = 0;
2177
+ var nodeIdMap = /* @__PURE__ */ new WeakMap();
2178
+ function resetNodeIds() {
2179
+ nodeIdCounter = 0;
2180
+ }
2181
+ function getNodeId(node) {
2182
+ let id = nodeIdMap.get(node);
2183
+ if (id === void 0) {
2184
+ id = ++nodeIdCounter;
2185
+ nodeIdMap.set(node, id);
2186
+ }
2187
+ return id;
2188
+ }
2189
+ function serializeNode(node, options) {
2190
+ const id = getNodeId(node);
2191
+ if (node.nodeType === Node.TEXT_NODE) {
2192
+ const parent = node.parentElement;
2193
+ let textContent = node.textContent || "";
2194
+ if (parent && options.maskSelector && parent.matches(options.maskSelector)) {
2195
+ textContent = textContent.replace(/\S/g, "*");
2196
+ }
2197
+ return {
2198
+ id,
2199
+ type: "text",
2200
+ textContent
2201
+ };
2202
+ }
2203
+ if (node.nodeType === Node.COMMENT_NODE) {
2204
+ return { id, type: "comment", textContent: "" };
2205
+ }
2206
+ if (node.nodeType === Node.ELEMENT_NODE) {
2207
+ const el = node;
2208
+ if (options.blockSelector && el.matches(options.blockSelector)) {
2209
+ return {
2210
+ id,
2211
+ type: "element",
2212
+ tagName: "div",
2213
+ attributes: {
2214
+ "data-blocked": "true",
2215
+ style: `width:${el.offsetWidth}px;height:${el.offsetHeight}px;background:#f0f0f0;`
2216
+ }
2217
+ };
2218
+ }
2219
+ const tagName = el.tagName.toLowerCase();
2220
+ const attributes = {};
2221
+ if (tagName === "script" || tagName === "noscript") {
2222
+ return { id, type: "element", tagName, attributes: {} };
2223
+ }
2224
+ for (const attr of Array.from(el.attributes)) {
2225
+ if (attr.name.startsWith("on")) continue;
2226
+ if (attr.name === "value" && options.maskInputs && isInputElement(el)) {
2227
+ attributes[attr.name] = "***";
2228
+ continue;
2229
+ }
2230
+ attributes[attr.name] = attr.value;
2231
+ }
2232
+ if (options.maskInputs && isInputElement(el)) {
2233
+ const input = el;
2234
+ if (input.type !== "checkbox" && input.type !== "radio" && input.type !== "submit" && input.type !== "button") {
2235
+ attributes["value"] = "***";
2236
+ }
2237
+ }
2238
+ const children = [];
2239
+ if (tagName !== "style") {
2240
+ for (const child of Array.from(el.childNodes)) {
2241
+ const serialized = serializeNode(child, options);
2242
+ if (serialized) {
2243
+ children.push(serialized);
2244
+ }
2245
+ }
2246
+ } else {
2247
+ const textContent = el.textContent || "";
2248
+ if (textContent) {
2249
+ children.push({
2250
+ id: getNodeId(el.firstChild || el),
2251
+ type: "text",
2252
+ textContent
2253
+ });
2254
+ }
2255
+ }
2256
+ return {
2257
+ id,
2258
+ type: "element",
2259
+ tagName,
2260
+ attributes,
2261
+ children,
2262
+ isSVG: el instanceof SVGElement
2263
+ };
2264
+ }
2265
+ return null;
2266
+ }
2267
+ function takeSnapshot(options) {
2268
+ if (typeof document === "undefined") return null;
2269
+ resetNodeIds();
2270
+ const html = serializeNode(document.documentElement, options);
2271
+ if (!html) return null;
2272
+ return {
2273
+ doctype: document.doctype ? `<!DOCTYPE ${document.doctype.name}>` : void 0,
2274
+ html,
2275
+ width: window.innerWidth,
2276
+ height: window.innerHeight
2277
+ };
2278
+ }
2279
+ function isInputElement(el) {
2280
+ const tag = el.tagName.toLowerCase();
2281
+ return tag === "input" || tag === "textarea" || tag === "select";
2282
+ }
2283
+
2284
+ // src/replay/network.ts
2285
+ var REDACTED_HEADERS = [
2286
+ "authorization",
2287
+ "cookie",
2288
+ "set-cookie",
2289
+ "x-api-key",
2290
+ "x-auth-token"
2291
+ ];
2292
+ var NetworkRecorder = class {
2293
+ constructor(callback, options) {
2294
+ this.originalFetch = null;
2295
+ this.originalXhrOpen = null;
2296
+ this.originalXhrSend = null;
2297
+ this.active = false;
2298
+ this.callback = callback;
2299
+ this.captureHeaders = options?.captureHeaders ?? false;
2300
+ this.urlFilter = options?.urlFilter;
2301
+ }
2302
+ start() {
2303
+ if (this.active || typeof window === "undefined") return;
2304
+ this.active = true;
2305
+ this.interceptFetch();
2306
+ this.interceptXHR();
2307
+ }
2308
+ stop() {
2309
+ if (!this.active) return;
2310
+ this.active = false;
2311
+ if (this.originalFetch) {
2312
+ window.fetch = this.originalFetch;
2313
+ this.originalFetch = null;
2314
+ }
2315
+ if (this.originalXhrOpen) {
2316
+ XMLHttpRequest.prototype.open = this.originalXhrOpen;
2317
+ this.originalXhrOpen = null;
2318
+ }
2319
+ if (this.originalXhrSend) {
2320
+ XMLHttpRequest.prototype.send = this.originalXhrSend;
2321
+ this.originalXhrSend = null;
2322
+ }
2323
+ }
2324
+ interceptFetch() {
2325
+ if (typeof window.fetch !== "function") return;
2326
+ this.originalFetch = window.fetch;
2327
+ const self = this;
2328
+ window.fetch = async function(input, init2) {
2329
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
2330
+ if (self.urlFilter && !self.urlFilter(url)) {
2331
+ return self.originalFetch.call(window, input, init2);
2332
+ }
2333
+ const startTime = Date.now();
2334
+ const method = init2?.method || (input instanceof Request ? input.method : "GET");
2335
+ let requestSize;
2336
+ if (init2?.body) {
2337
+ if (typeof init2.body === "string") {
2338
+ requestSize = init2.body.length;
2339
+ } else if (init2.body instanceof Blob) {
2340
+ requestSize = init2.body.size;
2341
+ }
2342
+ }
2343
+ try {
2344
+ const response = await self.originalFetch.call(window, input, init2);
2345
+ const duration = Date.now() - startTime;
2346
+ const data = {
2347
+ method: method.toUpperCase(),
2348
+ url: self.truncateUrl(url),
2349
+ status: response.status,
2350
+ statusText: response.statusText,
2351
+ duration,
2352
+ requestSize,
2353
+ initiator: "fetch"
2354
+ };
2355
+ if (self.captureHeaders) {
2356
+ data.requestHeaders = self.safeHeaders(init2?.headers);
2357
+ data.responseHeaders = self.headersToObject(response.headers);
2358
+ }
2359
+ self.emit(data);
2360
+ return response;
2361
+ } catch (err) {
2362
+ const duration = Date.now() - startTime;
2363
+ self.emit({
2364
+ method: method.toUpperCase(),
2365
+ url: self.truncateUrl(url),
2366
+ duration,
2367
+ requestSize,
2368
+ error: err instanceof Error ? err.message : "Network error",
2369
+ initiator: "fetch"
2370
+ });
2371
+ throw err;
2372
+ }
2373
+ };
2374
+ }
2375
+ interceptXHR() {
2376
+ if (typeof XMLHttpRequest === "undefined") return;
2377
+ this.originalXhrOpen = XMLHttpRequest.prototype.open;
2378
+ this.originalXhrSend = XMLHttpRequest.prototype.send;
2379
+ const self = this;
2380
+ XMLHttpRequest.prototype.open = function(method, url, ...args) {
2381
+ this._spMethod = method;
2382
+ this._spUrl = typeof url === "string" ? url : url.toString();
2383
+ return self.originalXhrOpen.apply(this, [method, url, ...args]);
2384
+ };
2385
+ XMLHttpRequest.prototype.send = function(body) {
2386
+ const xhr = this;
2387
+ const url = xhr._spUrl;
2388
+ if (self.urlFilter && !self.urlFilter(url)) {
2389
+ return self.originalXhrSend.call(this, body);
2390
+ }
2391
+ const startTime = Date.now();
2392
+ let requestSize;
2393
+ if (typeof body === "string") {
2394
+ requestSize = body.length;
2395
+ }
2396
+ const onLoadEnd = () => {
2397
+ const duration = Date.now() - startTime;
2398
+ const data = {
2399
+ method: (xhr._spMethod || "GET").toUpperCase(),
2400
+ url: self.truncateUrl(url),
2401
+ status: xhr.status,
2402
+ statusText: xhr.statusText,
2403
+ duration,
2404
+ requestSize,
2405
+ responseSize: xhr.responseText?.length,
2406
+ initiator: "xhr"
2407
+ };
2408
+ if (xhr.status === 0) {
2409
+ data.error = "Network error";
2410
+ }
2411
+ self.emit(data);
2412
+ xhr.removeEventListener("loadend", onLoadEnd);
2413
+ };
2414
+ xhr.addEventListener("loadend", onLoadEnd);
2415
+ return self.originalXhrSend.call(this, body);
2416
+ };
2417
+ }
2418
+ emit(data) {
2419
+ if (!this.active) return;
2420
+ this.callback({
2421
+ type: "network",
2422
+ timestamp: Date.now(),
2423
+ data
2424
+ });
2425
+ }
2426
+ truncateUrl(url) {
2427
+ try {
2428
+ const parsed = new URL(url, window.location.origin);
2429
+ const path = parsed.pathname + parsed.search;
2430
+ return path.length > 500 ? path.slice(0, 500) + "..." : parsed.origin + path;
2431
+ } catch {
2432
+ return url.length > 500 ? url.slice(0, 500) + "..." : url;
2433
+ }
2434
+ }
2435
+ safeHeaders(headers) {
2436
+ if (!headers) return void 0;
2437
+ const result = {};
2438
+ if (headers instanceof Headers) {
2439
+ headers.forEach((value, key) => {
2440
+ result[key] = REDACTED_HEADERS.includes(key.toLowerCase()) ? "[REDACTED]" : value;
2441
+ });
2442
+ } else if (Array.isArray(headers)) {
2443
+ for (const [key, value] of headers) {
2444
+ result[key] = REDACTED_HEADERS.includes(key.toLowerCase()) ? "[REDACTED]" : value;
2445
+ }
2446
+ } else {
2447
+ for (const [key, value] of Object.entries(headers)) {
2448
+ result[key] = REDACTED_HEADERS.includes(key.toLowerCase()) ? "[REDACTED]" : value;
2449
+ }
2450
+ }
2451
+ return result;
2452
+ }
2453
+ headersToObject(headers) {
2454
+ const result = {};
2455
+ headers.forEach((value, key) => {
2456
+ result[key] = REDACTED_HEADERS.includes(key.toLowerCase()) ? "[REDACTED]" : value;
2457
+ });
2458
+ return result;
2459
+ }
2460
+ };
2461
+
2462
+ // src/replay/console.ts
2463
+ var MAX_ARG_LENGTH = 1e3;
2464
+ var MAX_ARGS = 10;
2465
+ var MAX_TRACE_LENGTH = 2e3;
2466
+ var ConsoleRecorder = class {
2467
+ constructor(callback, options) {
2468
+ this.originals = {};
2469
+ this.active = false;
2470
+ this.callback = callback;
2471
+ this.levels = options?.levels || ["log", "info", "warn", "error", "debug"];
2472
+ }
2473
+ start() {
2474
+ if (this.active || typeof console === "undefined") return;
2475
+ this.active = true;
2476
+ for (const level of this.levels) {
2477
+ this.originals[level] = console[level].bind(console);
2478
+ console[level] = (...args) => {
2479
+ this.originals[level](...args);
2480
+ this.record(level, args);
2481
+ };
2482
+ }
2483
+ }
2484
+ stop() {
2485
+ if (!this.active) return;
2486
+ this.active = false;
2487
+ for (const level of this.levels) {
2488
+ if (this.originals[level]) {
2489
+ console[level] = this.originals[level];
2490
+ }
2491
+ }
2492
+ this.originals = {};
2493
+ }
2494
+ record(level, args) {
2495
+ if (!this.active) return;
2496
+ if (args.length > 0 && typeof args[0] === "string" && args[0].startsWith("[SitePong")) {
2497
+ return;
2498
+ }
2499
+ const serializedArgs = args.slice(0, MAX_ARGS).map((arg) => this.serialize(arg));
2500
+ const data = {
2501
+ level,
2502
+ args: serializedArgs
2503
+ };
2504
+ if (level === "error") {
2505
+ const trace = this.getStackTrace();
2506
+ if (trace) {
2507
+ data.trace = trace.slice(0, MAX_TRACE_LENGTH);
2508
+ }
2509
+ }
2510
+ this.callback({
2511
+ type: "console",
2512
+ timestamp: Date.now(),
2513
+ data
2514
+ });
2515
+ }
2516
+ serialize(value) {
2517
+ if (value === null) return "null";
2518
+ if (value === void 0) return "undefined";
2519
+ if (value instanceof Error) {
2520
+ return `Error: ${value.message}${value.stack ? "\n" + value.stack : ""}`.slice(0, MAX_ARG_LENGTH);
2521
+ }
2522
+ if (typeof value === "string") {
2523
+ return value.slice(0, MAX_ARG_LENGTH);
2524
+ }
2525
+ if (typeof value === "number" || typeof value === "boolean") {
2526
+ return String(value);
2527
+ }
2528
+ if (typeof value === "function") {
2529
+ return `[Function: ${value.name || "anonymous"}]`;
2530
+ }
2531
+ if (typeof value === "symbol") {
2532
+ return value.toString();
2533
+ }
2534
+ if (value instanceof HTMLElement) {
2535
+ return `<${value.tagName.toLowerCase()}${value.id ? "#" + value.id : ""}${value.className ? "." + value.className.split(" ").join(".") : ""}>`;
2536
+ }
2537
+ try {
2538
+ const str = JSON.stringify(value, this.getReplacer(), 0);
2539
+ return str ? str.slice(0, MAX_ARG_LENGTH) : String(value);
2540
+ } catch {
2541
+ return String(value).slice(0, MAX_ARG_LENGTH);
2542
+ }
2543
+ }
2544
+ getReplacer() {
2545
+ const seen = /* @__PURE__ */ new WeakSet();
2546
+ return (_key, value) => {
2547
+ if (typeof value === "object" && value !== null) {
2548
+ if (seen.has(value)) return "[Circular]";
2549
+ seen.add(value);
2550
+ }
2551
+ if (typeof value === "function") return "[Function]";
2552
+ if (value instanceof RegExp) return value.toString();
2553
+ return value;
2554
+ };
2555
+ }
2556
+ getStackTrace() {
2557
+ try {
2558
+ const err = new Error();
2559
+ if (!err.stack) return void 0;
2560
+ const lines = err.stack.split("\n");
2561
+ return lines.slice(4).join("\n") || void 0;
2562
+ } catch {
2563
+ return void 0;
2564
+ }
2565
+ }
2566
+ };
2567
+
2568
+ // src/replay/manager.ts
2569
+ var DEFAULT_ENDPOINT4 = "https://ingest.sitepong.com";
2570
+ var DEFAULT_FLUSH_INTERVAL2 = 5e3;
2571
+ var DEFAULT_MAX_BATCH_SIZE = 100;
2572
+ var DEFAULT_MAX_SESSION_DURATION = 60 * 60 * 1e3;
2573
+ var ReplayManager = class {
2574
+ constructor(config) {
2575
+ this.recording = false;
2576
+ this.sessionId = null;
2577
+ this.events = [];
2578
+ this.flushTimer = null;
2579
+ this.sessionStartTime = 0;
2580
+ this.observer = null;
2581
+ this.mouseMoveBuffer = [];
2582
+ this.mouseMoveFlushTimer = null;
2583
+ this.cleanupFns = [];
2584
+ this.networkRecorder = null;
2585
+ this.consoleRecorder = null;
2586
+ this.lastSnapshotTime = 0;
2587
+ this.snapshotCooldown = 500;
2588
+ // min ms between interaction snapshots
2589
+ this.userContext = null;
2590
+ this.config = {
2591
+ endpoint: DEFAULT_ENDPOINT4,
2592
+ enabled: true,
2593
+ maskInputs: true,
2594
+ maxSessionDuration: DEFAULT_MAX_SESSION_DURATION,
2595
+ flushInterval: DEFAULT_FLUSH_INTERVAL2,
2596
+ maxBatchSize: DEFAULT_MAX_BATCH_SIZE,
2597
+ sampleRate: 1,
2598
+ debug: false,
2599
+ ...config
2600
+ };
2601
+ }
2602
+ start() {
2603
+ if (typeof window === "undefined") return false;
2604
+ if (this.recording) return true;
2605
+ if (!this.config.enabled) return false;
2606
+ if (Math.random() > this.config.sampleRate) {
2607
+ this.log("Session not sampled");
2608
+ return false;
2609
+ }
2610
+ this.sessionId = this.getOrCreateSessionId();
2611
+ this.sessionStartTime = this.getOrCreateStartTime();
2612
+ this.recording = true;
2613
+ this.events = [];
2614
+ const snapshot = takeSnapshot({
2615
+ maskInputs: this.config.maskInputs,
2616
+ blockSelector: this.config.blockSelector,
2617
+ maskSelector: this.config.maskSelector
2618
+ });
2619
+ if (snapshot) {
2620
+ this.addEvent("snapshot", snapshot);
2621
+ }
2622
+ this.setupMutationObserver();
2623
+ this.setupEventListeners();
2624
+ this.setupNetworkRecording();
2625
+ this.setupConsoleRecording();
2626
+ this.startFlushTimer();
2627
+ this.log("Recording started:", this.sessionId);
2628
+ return true;
2629
+ }
2630
+ stop() {
2631
+ if (!this.recording) return;
2632
+ this.recording = false;
2633
+ this.flush();
2634
+ if (this.observer) {
2635
+ this.observer.disconnect();
2636
+ this.observer = null;
2637
+ }
2638
+ if (this.flushTimer) {
2639
+ clearInterval(this.flushTimer);
2640
+ this.flushTimer = null;
2641
+ }
2642
+ if (this.mouseMoveFlushTimer) {
2643
+ clearTimeout(this.mouseMoveFlushTimer);
2644
+ this.mouseMoveFlushTimer = null;
2645
+ }
2646
+ if (this.networkRecorder) {
2647
+ this.networkRecorder.stop();
2648
+ this.networkRecorder = null;
2649
+ }
2650
+ if (this.consoleRecorder) {
2651
+ this.consoleRecorder.stop();
2652
+ this.consoleRecorder = null;
2653
+ }
2654
+ for (const cleanup of this.cleanupFns) {
2655
+ cleanup();
2656
+ }
2657
+ this.cleanupFns = [];
2658
+ this.log("Recording stopped:", this.sessionId);
2659
+ this.sessionId = null;
2660
+ }
2661
+ isRecording() {
2662
+ return this.recording;
2663
+ }
2664
+ getSessionId() {
2665
+ return this.sessionId;
2666
+ }
2667
+ setUser(user) {
2668
+ this.userContext = user;
2669
+ }
2670
+ addEvent(type, data) {
2671
+ if (!this.recording) return;
2672
+ if (Date.now() - this.sessionStartTime > this.config.maxSessionDuration) {
2673
+ this.stop();
2674
+ return;
2675
+ }
2676
+ this.events.push({
2677
+ type,
2678
+ timestamp: Date.now() - this.sessionStartTime,
2679
+ data
2680
+ });
2681
+ if (this.events.length >= this.config.maxBatchSize) {
2682
+ this.flush();
2683
+ }
2684
+ }
2685
+ setupMutationObserver() {
2686
+ if (typeof MutationObserver === "undefined") return;
2687
+ this.observer = new MutationObserver((mutations) => {
2688
+ const mutationData = {
2689
+ adds: [],
2690
+ removes: [],
2691
+ attributes: [],
2692
+ texts: []
2693
+ };
2694
+ for (const mutation of mutations) {
2695
+ switch (mutation.type) {
2696
+ case "childList": {
2697
+ for (const node of Array.from(mutation.addedNodes)) {
2698
+ const serialized = serializeNode(node, {
2699
+ maskInputs: this.config.maskInputs,
2700
+ blockSelector: this.config.blockSelector,
2701
+ maskSelector: this.config.maskSelector
2702
+ });
2703
+ if (serialized) {
2704
+ mutationData.adds.push({
2705
+ parentId: getNodeId(mutation.target),
2706
+ nextId: mutation.nextSibling ? getNodeId(mutation.nextSibling) : null,
2707
+ node: serialized
2708
+ });
2709
+ }
2710
+ }
2711
+ for (const node of Array.from(mutation.removedNodes)) {
2712
+ mutationData.removes.push({
2713
+ parentId: getNodeId(mutation.target),
2714
+ id: getNodeId(node)
2715
+ });
2716
+ }
2717
+ break;
2718
+ }
2719
+ case "attributes": {
2720
+ const el = mutation.target;
2721
+ const value = el.getAttribute(mutation.attributeName || "");
2722
+ mutationData.attributes.push({
2723
+ id: getNodeId(mutation.target),
2724
+ attributes: { [mutation.attributeName || ""]: value }
2725
+ });
2726
+ break;
2727
+ }
2728
+ case "characterData": {
2729
+ let textValue = mutation.target.textContent || "";
2730
+ const parent = mutation.target.parentElement;
2731
+ if (parent && this.config.maskSelector && parent.matches(this.config.maskSelector)) {
2732
+ textValue = textValue.replace(/\S/g, "*");
2733
+ }
2734
+ mutationData.texts.push({
2735
+ id: getNodeId(mutation.target),
2736
+ value: textValue
2737
+ });
2738
+ break;
2739
+ }
2740
+ }
2741
+ }
2742
+ if (mutationData.adds.length || mutationData.removes.length || mutationData.attributes.length || mutationData.texts.length) {
2743
+ this.addEvent("mutation", mutationData);
2744
+ }
2745
+ });
2746
+ this.observer.observe(document.documentElement, {
2747
+ childList: true,
2748
+ subtree: true,
2749
+ attributes: true,
2750
+ characterData: true,
2751
+ attributeOldValue: false
2752
+ });
2753
+ }
2754
+ setupEventListeners() {
2755
+ const onMouseMove = (e) => {
2756
+ this.mouseMoveBuffer.push({
2757
+ x: e.clientX,
2758
+ y: e.clientY,
2759
+ t: Date.now() - this.sessionStartTime
2760
+ });
2761
+ if (!this.mouseMoveFlushTimer) {
2762
+ this.mouseMoveFlushTimer = setTimeout(() => {
2763
+ if (this.mouseMoveBuffer.length > 0) {
2764
+ const data = { positions: [...this.mouseMoveBuffer] };
2765
+ this.addEvent("mouse_move", data);
2766
+ this.mouseMoveBuffer = [];
2767
+ }
2768
+ this.mouseMoveFlushTimer = null;
2769
+ }, 50);
2770
+ }
2771
+ };
2772
+ const onClick = (e) => {
2773
+ const target = e.target;
2774
+ this.addEvent("mouse_click", {
2775
+ x: e.clientX,
2776
+ y: e.clientY,
2777
+ targetId: getNodeId(target),
2778
+ button: e.button
2779
+ });
2780
+ this.takeInteractionSnapshot();
2781
+ };
2782
+ const onContextMenu = (e) => {
2783
+ const target = e.target;
2784
+ this.addEvent("mouse_click", {
2785
+ x: e.clientX,
2786
+ y: e.clientY,
2787
+ targetId: getNodeId(target),
2788
+ button: 2
2789
+ });
2790
+ this.takeInteractionSnapshot();
2791
+ };
2792
+ const onMouseLeave = () => {
2793
+ this.addEvent("mouse_leave", {});
2794
+ };
2795
+ const onTouchStart = (e) => {
2796
+ const touch = e.touches[0];
2797
+ if (!touch) return;
2798
+ const target = e.target;
2799
+ this.addEvent("touch", {
2800
+ x: touch.clientX,
2801
+ y: touch.clientY,
2802
+ targetId: getNodeId(target)
2803
+ });
2804
+ this.takeInteractionSnapshot();
2805
+ };
2806
+ let scrollSnapshotTimer = null;
2807
+ const onScroll = () => {
2808
+ const data = {
2809
+ id: 0,
2810
+ // document scroll
2811
+ x: window.scrollX,
2812
+ y: window.scrollY
2813
+ };
2814
+ this.addEvent("scroll", data);
2815
+ if (scrollSnapshotTimer) clearTimeout(scrollSnapshotTimer);
2816
+ scrollSnapshotTimer = setTimeout(() => {
2817
+ this.takeInteractionSnapshot();
2818
+ }, 250);
2819
+ };
2820
+ const onInput = (e) => {
2821
+ const target = e.target;
2822
+ if (!target) return;
2823
+ const data = {
2824
+ id: getNodeId(target),
2825
+ value: this.config.maskInputs ? "***" : target.value
2826
+ };
2827
+ if ("checked" in target) {
2828
+ data.isChecked = target.checked;
2829
+ }
2830
+ this.addEvent("input", data);
2831
+ };
2832
+ const onResize = () => {
2833
+ this.addEvent("resize", {
2834
+ width: window.innerWidth,
2835
+ height: window.innerHeight
2836
+ });
2837
+ };
2838
+ const onFocus = (e) => {
2839
+ if (e.target && e.target !== document) {
2840
+ this.addEvent("focus", { id: getNodeId(e.target) });
2841
+ }
2842
+ };
2843
+ const onBlur = (e) => {
2844
+ if (e.target && e.target !== document) {
2845
+ this.addEvent("blur", { id: getNodeId(e.target) });
2846
+ }
2847
+ };
2848
+ const onVisibilityChange = () => {
2849
+ if (document.visibilityState === "hidden") {
2850
+ this.flush();
2851
+ }
2852
+ };
2853
+ document.addEventListener("mousemove", onMouseMove, { passive: true });
2854
+ document.addEventListener("click", onClick, { passive: true });
2855
+ document.addEventListener("contextmenu", onContextMenu, { passive: true });
2856
+ document.documentElement.addEventListener("mouseleave", onMouseLeave, { passive: true });
2857
+ document.addEventListener("touchstart", onTouchStart, { passive: true });
2858
+ window.addEventListener("scroll", onScroll, { passive: true });
2859
+ document.addEventListener("input", onInput, { passive: true });
2860
+ window.addEventListener("resize", onResize, { passive: true });
2861
+ document.addEventListener("focus", onFocus, { passive: true, capture: true });
2862
+ document.addEventListener("blur", onBlur, { passive: true, capture: true });
2863
+ document.addEventListener("visibilitychange", onVisibilityChange);
2864
+ this.cleanupFns.push(
2865
+ () => document.removeEventListener("mousemove", onMouseMove),
2866
+ () => document.removeEventListener("click", onClick),
2867
+ () => document.removeEventListener("contextmenu", onContextMenu),
2868
+ () => document.documentElement.removeEventListener("mouseleave", onMouseLeave),
2869
+ () => document.removeEventListener("touchstart", onTouchStart),
2870
+ () => window.removeEventListener("scroll", onScroll),
2871
+ () => document.removeEventListener("input", onInput),
2872
+ () => window.removeEventListener("resize", onResize),
2873
+ () => document.removeEventListener("focus", onFocus, { capture: true }),
2874
+ () => document.removeEventListener("blur", onBlur, { capture: true }),
2875
+ () => document.removeEventListener("visibilitychange", onVisibilityChange)
2876
+ );
2877
+ }
2878
+ setupNetworkRecording() {
2879
+ if (!this.config.recordNetwork) return;
2880
+ const sdkEndpoint = this.config.replayEndpoint || this.config.endpoint;
2881
+ this.networkRecorder = new NetworkRecorder(
2882
+ (event) => {
2883
+ this.addEvent("network", event.data);
2884
+ },
2885
+ {
2886
+ captureHeaders: this.config.captureNetworkHeaders,
2887
+ // Skip recording our own SDK API calls
2888
+ urlFilter: (url) => !url.startsWith(sdkEndpoint)
2889
+ }
2890
+ );
2891
+ this.networkRecorder.start();
2892
+ }
2893
+ setupConsoleRecording() {
2894
+ if (!this.config.recordConsole) return;
2895
+ this.consoleRecorder = new ConsoleRecorder(
2896
+ (event) => {
2897
+ this.addEvent("console", event.data);
2898
+ },
2899
+ {
2900
+ levels: this.config.consoleLevels
2901
+ }
2902
+ );
2903
+ this.consoleRecorder.start();
2904
+ }
2905
+ startFlushTimer() {
2906
+ this.flushTimer = setInterval(() => {
2907
+ this.flush();
2908
+ }, this.config.flushInterval);
2909
+ }
2910
+ async flush() {
2911
+ if (this.events.length === 0 || !this.sessionId) return;
2912
+ const events = [...this.events];
2913
+ this.events = [];
2914
+ const endpoint = this.config.replayEndpoint || `${this.config.endpoint}/api/replays`;
2915
+ try {
2916
+ const response = await fetch(endpoint, {
2917
+ method: "POST",
2918
+ headers: {
2919
+ "Content-Type": "application/json",
2920
+ "X-API-Key": this.config.apiKey
2921
+ },
2922
+ body: JSON.stringify({
2923
+ sessionId: this.sessionId,
2924
+ events,
2925
+ startedAt: new Date(this.sessionStartTime).toISOString(),
2926
+ pageUrl: typeof location !== "undefined" ? location.href : void 0,
2927
+ pageTitle: typeof document !== "undefined" ? document.title : void 0,
2928
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : void 0,
2929
+ userId: this.userContext?.id
2930
+ })
2931
+ });
2932
+ if (!response.ok) {
2933
+ throw new Error(`HTTP ${response.status}`);
2934
+ }
2935
+ this.log(`Flushed ${events.length} replay events`);
2936
+ } catch (err) {
2937
+ this.events.unshift(...events);
2938
+ this.log("Failed to flush replay events:", err);
2939
+ }
2940
+ }
2941
+ takeInteractionSnapshot() {
2942
+ const now = Date.now();
2943
+ if (now - this.lastSnapshotTime < this.snapshotCooldown) return;
2944
+ this.lastSnapshotTime = now;
2945
+ const snapshot = takeSnapshot({
2946
+ maskInputs: this.config.maskInputs,
2947
+ blockSelector: this.config.blockSelector,
2948
+ maskSelector: this.config.maskSelector
2949
+ });
2950
+ if (snapshot) {
2951
+ this.addEvent("snapshot", snapshot);
2952
+ }
2953
+ }
2954
+ getOrCreateSessionId() {
2955
+ const storageKey = "__sp_replay_session";
2956
+ try {
2957
+ const existing = sessionStorage.getItem(storageKey);
2958
+ if (existing) return existing;
2959
+ } catch {
2960
+ }
2961
+ const id = `rep_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
2962
+ try {
2963
+ sessionStorage.setItem(storageKey, id);
2964
+ } catch {
2965
+ }
2966
+ return id;
2967
+ }
2968
+ getOrCreateStartTime() {
2969
+ const storageKey = "__sp_replay_start";
2970
+ try {
2971
+ const existing = sessionStorage.getItem(storageKey);
2972
+ if (existing) return parseInt(existing, 10);
2973
+ } catch {
2974
+ }
2975
+ const now = Date.now();
2976
+ try {
2977
+ sessionStorage.setItem(storageKey, String(now));
2978
+ } catch {
2979
+ }
2980
+ return now;
2981
+ }
2982
+ log(...args) {
2983
+ if (this.config.debug) {
2984
+ console.log("[SitePong Replay]", ...args);
2985
+ }
2986
+ }
2987
+ };
2988
+
2989
+ // src/performance/manager.ts
2990
+ var DEFAULT_ENDPOINT5 = "https://ingest.sitepong.com";
2991
+ var DEFAULT_FLUSH_INTERVAL3 = 1e4;
2992
+ var PerformanceManager = class {
2993
+ constructor(config) {
2994
+ this.metrics = [];
2995
+ this.flushTimer = null;
2996
+ this.vitals = {};
2997
+ this.activeTransactions = /* @__PURE__ */ new Map();
2998
+ this.config = {
2999
+ endpoint: DEFAULT_ENDPOINT5,
3000
+ enabled: true,
3001
+ webVitals: true,
3002
+ navigationTiming: true,
3003
+ resourceTiming: false,
3004
+ flushInterval: DEFAULT_FLUSH_INTERVAL3,
3005
+ sampleRate: 1,
3006
+ debug: false,
3007
+ ...config
3008
+ };
3009
+ this.sampled = Math.random() <= this.config.sampleRate;
3010
+ }
3011
+ init() {
3012
+ if (!this.config.enabled || !this.sampled) return;
3013
+ if (typeof window === "undefined") return;
3014
+ if (this.config.webVitals) {
3015
+ this.observeWebVitals();
3016
+ }
3017
+ if (this.config.navigationTiming) {
3018
+ this.collectNavigationTiming();
3019
+ }
3020
+ this.startFlushTimer();
3021
+ this.log("Performance monitoring initialized");
3022
+ }
3023
+ /**
3024
+ * Start a custom transaction for measuring operations
3025
+ */
3026
+ startTransaction(name, data) {
3027
+ const id = `txn_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
3028
+ const transaction = {
3029
+ id,
3030
+ name,
3031
+ startTime: performance.now(),
3032
+ status: "running",
3033
+ spans: [],
3034
+ data
3035
+ };
3036
+ this.activeTransactions.set(id, transaction);
3037
+ this.log("Transaction started:", name, id);
3038
+ return id;
3039
+ }
3040
+ /**
3041
+ * End a transaction and report it
3042
+ */
3043
+ endTransaction(id, status = "ok") {
3044
+ const transaction = this.activeTransactions.get(id);
3045
+ if (!transaction) {
3046
+ this.log("Transaction not found:", id);
3047
+ return;
3048
+ }
3049
+ transaction.endTime = performance.now();
3050
+ transaction.duration = transaction.endTime - transaction.startTime;
3051
+ transaction.status = status;
3052
+ for (const span of transaction.spans) {
3053
+ if (span.status === "running") {
3054
+ span.endTime = transaction.endTime;
3055
+ span.duration = span.endTime - span.startTime;
3056
+ span.status = status;
3057
+ }
3058
+ }
3059
+ this.activeTransactions.delete(id);
3060
+ this.addMetric({
3061
+ type: "transaction",
3062
+ name: transaction.name,
3063
+ value: transaction.duration,
3064
+ unit: "ms",
3065
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3066
+ url: typeof window !== "undefined" ? window.location.href : void 0,
3067
+ transaction
3068
+ });
3069
+ this.log("Transaction ended:", transaction.name, `${transaction.duration.toFixed(1)}ms`);
3070
+ }
3071
+ /**
3072
+ * Start a span within a transaction
3073
+ */
3074
+ startSpan(transactionId, name, data) {
3075
+ const transaction = this.activeTransactions.get(transactionId);
3076
+ if (!transaction) {
3077
+ this.log("Transaction not found for span:", transactionId);
3078
+ return "";
3079
+ }
3080
+ const span = {
3081
+ id: `span_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`,
3082
+ name,
3083
+ startTime: performance.now(),
3084
+ status: "running",
3085
+ data
3086
+ };
3087
+ transaction.spans.push(span);
3088
+ return span.id;
3089
+ }
3090
+ /**
3091
+ * End a span
3092
+ */
3093
+ endSpan(transactionId, spanId, status = "ok") {
3094
+ const transaction = this.activeTransactions.get(transactionId);
3095
+ if (!transaction) return;
3096
+ const span = transaction.spans.find((s) => s.id === spanId);
3097
+ if (!span) return;
3098
+ span.endTime = performance.now();
3099
+ span.duration = span.endTime - span.startTime;
3100
+ span.status = status;
3101
+ }
3102
+ /**
3103
+ * Get current Web Vitals
3104
+ */
3105
+ getVitals() {
3106
+ return { ...this.vitals };
3107
+ }
3108
+ destroy() {
3109
+ if (this.flushTimer) {
3110
+ clearInterval(this.flushTimer);
3111
+ this.flushTimer = null;
3112
+ }
3113
+ this.flush();
3114
+ }
3115
+ observeWebVitals() {
3116
+ if (typeof PerformanceObserver === "undefined") return;
3117
+ try {
3118
+ const lcpObserver = new PerformanceObserver((list) => {
3119
+ const entries = list.getEntries();
3120
+ const lastEntry = entries[entries.length - 1];
3121
+ if (lastEntry) {
3122
+ this.vitals.lcp = lastEntry.startTime;
3123
+ this.reportVital("LCP", lastEntry.startTime);
3124
+ }
3125
+ });
3126
+ lcpObserver.observe({ type: "largest-contentful-paint", buffered: true });
3127
+ } catch (e) {
3128
+ }
3129
+ try {
3130
+ const fidObserver = new PerformanceObserver((list) => {
3131
+ const entry = list.getEntries()[0];
3132
+ if (entry) {
3133
+ const fid = entry.processingStart - entry.startTime;
3134
+ this.vitals.fid = fid;
3135
+ this.reportVital("FID", fid);
3136
+ }
3137
+ });
3138
+ fidObserver.observe({ type: "first-input", buffered: true });
3139
+ } catch (e) {
3140
+ }
3141
+ try {
3142
+ let clsValue = 0;
3143
+ const clsObserver = new PerformanceObserver((list) => {
3144
+ for (const entry of list.getEntries()) {
3145
+ const layoutShift = entry;
3146
+ if (!layoutShift.hadRecentInput) {
3147
+ clsValue += layoutShift.value;
3148
+ }
3149
+ }
3150
+ this.vitals.cls = clsValue;
3151
+ });
3152
+ clsObserver.observe({ type: "layout-shift", buffered: true });
3153
+ if (typeof document !== "undefined") {
3154
+ document.addEventListener("visibilitychange", () => {
3155
+ if (document.visibilityState === "hidden" && clsValue > 0) {
3156
+ this.reportVital("CLS", clsValue, "score");
3157
+ }
3158
+ });
3159
+ }
3160
+ } catch (e) {
3161
+ }
3162
+ try {
3163
+ const fcpObserver = new PerformanceObserver((list) => {
3164
+ const entry = list.getEntries().find(
3165
+ (e) => e.name === "first-contentful-paint"
3166
+ );
3167
+ if (entry) {
3168
+ this.vitals.fcp = entry.startTime;
3169
+ this.reportVital("FCP", entry.startTime);
3170
+ }
3171
+ });
3172
+ fcpObserver.observe({ type: "paint", buffered: true });
3173
+ } catch (e) {
3174
+ }
3175
+ try {
3176
+ let maxINP = 0;
3177
+ const inpObserver = new PerformanceObserver((list) => {
3178
+ for (const entry of list.getEntries()) {
3179
+ const duration = entry.duration;
3180
+ if (duration > maxINP) {
3181
+ maxINP = duration;
3182
+ this.vitals.inp = maxINP;
3183
+ }
3184
+ }
3185
+ });
3186
+ inpObserver.observe({ type: "event", buffered: true });
3187
+ } catch (e) {
3188
+ }
3189
+ }
3190
+ collectNavigationTiming() {
3191
+ if (typeof window === "undefined" || !window.performance) return;
3192
+ const collect = () => {
3193
+ const nav = performance.getEntriesByType("navigation")[0];
3194
+ if (!nav) return;
3195
+ const ttfb = nav.responseStart - nav.requestStart;
3196
+ this.vitals.ttfb = ttfb;
3197
+ this.reportVital("TTFB", ttfb);
3198
+ this.addMetric({
3199
+ type: "navigation",
3200
+ name: "page_load",
3201
+ value: nav.loadEventEnd - nav.startTime,
3202
+ unit: "ms",
3203
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3204
+ url: window.location.href,
3205
+ data: {
3206
+ dns: nav.domainLookupEnd - nav.domainLookupStart,
3207
+ tcp: nav.connectEnd - nav.connectStart,
3208
+ ttfb,
3209
+ domContentLoaded: nav.domContentLoadedEventEnd - nav.startTime,
3210
+ domComplete: nav.domComplete - nav.startTime,
3211
+ transferSize: nav.transferSize,
3212
+ encodedBodySize: nav.encodedBodySize,
3213
+ decodedBodySize: nav.decodedBodySize
3214
+ }
3215
+ });
3216
+ };
3217
+ if (document.readyState === "complete") {
3218
+ setTimeout(collect, 0);
3219
+ } else {
3220
+ window.addEventListener("load", () => setTimeout(collect, 0));
3221
+ }
3222
+ }
3223
+ reportVital(name, value, unit = "ms") {
3224
+ this.addMetric({
3225
+ type: "web_vital",
3226
+ name,
3227
+ value,
3228
+ unit,
3229
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3230
+ url: typeof window !== "undefined" ? window.location.href : void 0
3231
+ });
3232
+ }
3233
+ addMetric(metric) {
3234
+ this.metrics.push(metric);
3235
+ }
3236
+ startFlushTimer() {
3237
+ this.flushTimer = setInterval(() => {
3238
+ this.flush();
3239
+ }, this.config.flushInterval);
3240
+ if (typeof document !== "undefined") {
3241
+ document.addEventListener("visibilitychange", () => {
3242
+ if (document.visibilityState === "hidden") {
3243
+ this.flush();
3244
+ }
3245
+ });
3246
+ }
3247
+ }
3248
+ async flush() {
3249
+ if (this.metrics.length === 0) return;
3250
+ const metrics = [...this.metrics];
3251
+ this.metrics = [];
3252
+ const endpoint = this.config.performanceEndpoint || `${this.config.endpoint}/api/performance`;
3253
+ try {
3254
+ const response = await fetch(endpoint, {
3255
+ method: "POST",
3256
+ headers: {
3257
+ "Content-Type": "application/json",
3258
+ "X-API-Key": this.config.apiKey
3259
+ },
3260
+ body: JSON.stringify({ metrics })
3261
+ });
3262
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
3263
+ this.log(`Flushed ${metrics.length} performance metrics`);
3264
+ } catch (err) {
3265
+ this.metrics.unshift(...metrics);
3266
+ this.log("Failed to flush metrics:", err);
3267
+ }
3268
+ }
3269
+ log(...args) {
3270
+ if (this.config.debug) {
3271
+ console.log("[SitePong Performance]", ...args);
3272
+ }
3273
+ }
3274
+ };
3275
+
3276
+ // src/performance/tracing.ts
3277
+ function randomHex(byteLength) {
3278
+ try {
3279
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
3280
+ const bytes = new Uint8Array(byteLength);
3281
+ crypto.getRandomValues(bytes);
3282
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
3283
+ }
3284
+ } catch {
3285
+ }
3286
+ let hex = "";
3287
+ for (let i = 0; i < byteLength; i++) {
3288
+ hex += Math.floor(Math.random() * 256).toString(16).padStart(2, "0");
3289
+ }
3290
+ return hex;
3291
+ }
3292
+ function generateTraceId() {
3293
+ return randomHex(16);
3294
+ }
3295
+ function generateSpanId() {
3296
+ return randomHex(8);
3297
+ }
3298
+ function createTraceContext() {
3299
+ return {
3300
+ traceId: generateTraceId(),
3301
+ spanId: generateSpanId(),
3302
+ parentSpanId: null,
3303
+ sampled: true
3304
+ };
3305
+ }
3306
+ function propagateTrace(context) {
3307
+ const sampledFlag = context.sampled ? "01" : "00";
3308
+ return {
3309
+ traceparent: `00-${context.traceId}-${context.spanId}-${sampledFlag}`,
3310
+ tracestate: `sitepong=${context.spanId}`
3311
+ };
3312
+ }
3313
+ function extractTrace(headers) {
3314
+ const traceparent = headers["traceparent"] || headers["Traceparent"];
3315
+ if (!traceparent) {
3316
+ return null;
3317
+ }
3318
+ const parts = traceparent.split("-");
3319
+ if (parts.length !== 4) {
3320
+ return null;
3321
+ }
3322
+ const [version, traceId, parentSpanId, flags] = parts;
3323
+ if (version !== "00") {
3324
+ return null;
3325
+ }
3326
+ if (traceId.length !== 32 || !/^[0-9a-f]{32}$/.test(traceId)) {
3327
+ return null;
3328
+ }
3329
+ if (parentSpanId.length !== 16 || !/^[0-9a-f]{16}$/.test(parentSpanId)) {
3330
+ return null;
3331
+ }
3332
+ if (flags.length !== 2 || !/^[0-9a-f]{2}$/.test(flags)) {
3333
+ return null;
3334
+ }
3335
+ const sampled = (parseInt(flags, 16) & 1) === 1;
3336
+ return {
3337
+ traceId,
3338
+ spanId: generateSpanId(),
3339
+ parentSpanId,
3340
+ sampled
3341
+ };
3342
+ }
3343
+ var TracePropagator = class {
3344
+ constructor(targets) {
3345
+ this.originalFetch = null;
3346
+ this.targets = targets;
3347
+ }
3348
+ shouldPropagate(url) {
3349
+ return this.targets.some((pattern) => {
3350
+ if (pattern.includes("*")) {
3351
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
3352
+ const regex = new RegExp(`^${escaped}$`);
3353
+ return regex.test(url);
3354
+ }
3355
+ return url.startsWith(pattern);
3356
+ });
3357
+ }
3358
+ injectHeaders(url, headers, context) {
3359
+ if (!this.shouldPropagate(url)) {
3360
+ return headers;
3361
+ }
3362
+ const traceHeaders = propagateTrace(context);
3363
+ return {
3364
+ ...headers,
3365
+ ...traceHeaders
3366
+ };
3367
+ }
3368
+ start() {
3369
+ if (typeof window === "undefined" || !window.fetch) {
3370
+ return;
3371
+ }
3372
+ if (this.originalFetch) {
3373
+ return;
3374
+ }
3375
+ this.originalFetch = window.fetch;
3376
+ const self = this;
3377
+ window.fetch = function(input, init2) {
3378
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
3379
+ if (self.shouldPropagate(url)) {
3380
+ const context = createTraceContext();
3381
+ const traceHeaders = propagateTrace(context);
3382
+ const existingHeaders = {};
3383
+ if (init2?.headers) {
3384
+ if (init2.headers instanceof Headers) {
3385
+ init2.headers.forEach((value, key) => {
3386
+ existingHeaders[key] = value;
3387
+ });
3388
+ } else if (Array.isArray(init2.headers)) {
3389
+ for (const [key, value] of init2.headers) {
3390
+ existingHeaders[key] = value;
3391
+ }
3392
+ } else {
3393
+ Object.assign(existingHeaders, init2.headers);
3394
+ }
3395
+ }
3396
+ const mergedHeaders = {
3397
+ ...existingHeaders,
3398
+ ...traceHeaders
3399
+ };
3400
+ const patchedInit = {
3401
+ ...init2,
3402
+ headers: mergedHeaders
3403
+ };
3404
+ return self.originalFetch.call(window, input, patchedInit);
3405
+ }
3406
+ return self.originalFetch.call(window, input, init2);
3407
+ };
3408
+ }
3409
+ stop() {
3410
+ if (typeof window === "undefined") {
3411
+ return;
3412
+ }
3413
+ if (this.originalFetch) {
3414
+ window.fetch = this.originalFetch;
3415
+ this.originalFetch = null;
3416
+ }
3417
+ }
3418
+ };
3419
+
3420
+ // src/crons.ts
3421
+ var CronMonitorManager = class {
3422
+ constructor(config) {
3423
+ this.apiKey = config.apiKey;
3424
+ this.endpoint = config.endpoint;
3425
+ this.debug = config.debug || false;
3426
+ }
3427
+ /**
3428
+ * Send a check-in for a cron monitor
3429
+ */
3430
+ async checkin(slug, options = {}) {
3431
+ try {
3432
+ const response = await fetch(`${this.endpoint}/api/sdk/crons`, {
3433
+ method: "POST",
3434
+ headers: {
3435
+ "Content-Type": "application/json",
3436
+ "X-API-Key": this.apiKey
3437
+ },
3438
+ body: JSON.stringify({
3439
+ slug,
3440
+ status: options.status || "ok",
3441
+ duration_ms: options.duration_ms,
3442
+ message: options.message,
3443
+ environment: options.environment
3444
+ })
3445
+ });
3446
+ if (!response.ok) {
3447
+ throw new Error(`HTTP ${response.status}`);
3448
+ }
3449
+ this.log(`Check-in sent for "${slug}" (${options.status || "ok"})`);
3450
+ } catch (err) {
3451
+ this.log("Failed to send check-in:", err);
3452
+ }
3453
+ }
3454
+ /**
3455
+ * Start tracking a cron job execution.
3456
+ * Returns a handle with ok() and error() methods to complete the check-in.
3457
+ */
3458
+ start(slug, environment) {
3459
+ const startTime = Date.now();
3460
+ this.checkin(slug, { status: "in_progress", environment });
3461
+ return {
3462
+ ok: async (message) => {
3463
+ const duration_ms = Date.now() - startTime;
3464
+ await this.checkin(slug, { status: "ok", duration_ms, message, environment });
3465
+ },
3466
+ error: async (message) => {
3467
+ const duration_ms = Date.now() - startTime;
3468
+ await this.checkin(slug, { status: "error", duration_ms, message, environment });
3469
+ }
3470
+ };
3471
+ }
3472
+ /**
3473
+ * Wrap an async function with automatic cron monitoring.
3474
+ * Sends 'in_progress' on start, 'ok' on success, 'error' on failure.
3475
+ */
3476
+ wrap(slug, fn, environment) {
3477
+ const handle = this.start(slug, environment);
3478
+ return fn().then(
3479
+ async (result) => {
3480
+ await handle.ok();
3481
+ return result;
3482
+ },
3483
+ async (err) => {
3484
+ await handle.error(err?.message || String(err));
3485
+ throw err;
3486
+ }
3487
+ );
3488
+ }
3489
+ log(...args) {
3490
+ if (this.debug) {
3491
+ console.log("[SitePong:Cron]", ...args);
3492
+ }
3493
+ }
3494
+ };
3495
+
3496
+ // src/metrics.ts
3497
+ var MetricsManager = class {
3498
+ constructor(config) {
3499
+ this.queue = [];
3500
+ this.flushTimer = null;
3501
+ this.apiKey = config.apiKey;
3502
+ this.endpoint = config.endpoint;
3503
+ this.debug = config.debug || false;
3504
+ this.flushInterval = config.flushInterval || 1e4;
3505
+ this.maxBatchSize = config.maxBatchSize || 100;
3506
+ this.startFlushTimer();
3507
+ }
3508
+ /**
3509
+ * Increment a counter metric
3510
+ */
3511
+ increment(name, value = 1, options) {
3512
+ this.enqueue(name, value, "counter", options);
3513
+ }
3514
+ /**
3515
+ * Decrement a counter metric
3516
+ */
3517
+ decrement(name, value = 1, options) {
3518
+ this.enqueue(name, -value, "counter", options);
3519
+ }
3520
+ /**
3521
+ * Set a gauge metric (instantaneous value)
3522
+ */
3523
+ gauge(name, value, options) {
3524
+ this.enqueue(name, value, "gauge", options);
3525
+ }
3526
+ /**
3527
+ * Record a histogram value (for aggregation server-side)
3528
+ */
3529
+ histogram(name, value, options) {
3530
+ this.enqueue(name, value, "histogram", options);
3531
+ }
3532
+ /**
3533
+ * Record a distribution value (for percentile calculations)
3534
+ */
3535
+ distribution(name, value, options) {
3536
+ this.enqueue(name, value, "distribution", options);
3537
+ }
3538
+ /**
3539
+ * Time a function execution and record as distribution
3540
+ */
3541
+ async time(name, fn, options) {
3542
+ const start = Date.now();
3543
+ try {
3544
+ const result = await fn();
3545
+ this.distribution(name, Date.now() - start, { ...options, unit: options?.unit || "ms" });
3546
+ return result;
3547
+ } catch (err) {
3548
+ this.distribution(name, Date.now() - start, {
3549
+ ...options,
3550
+ unit: options?.unit || "ms",
3551
+ tags: { ...options?.tags, error: "true" }
3552
+ });
3553
+ throw err;
3554
+ }
3555
+ }
3556
+ /**
3557
+ * Create a timer that records on stop
3558
+ */
3559
+ startTimer(name, options) {
3560
+ const start = Date.now();
3561
+ return {
3562
+ stop: () => {
3563
+ this.distribution(name, Date.now() - start, { ...options, unit: options?.unit || "ms" });
3564
+ }
3565
+ };
3566
+ }
3567
+ /**
3568
+ * Force flush all queued metrics
3569
+ */
3570
+ async flush() {
3571
+ if (this.queue.length === 0) return;
3572
+ const metrics = [...this.queue];
3573
+ this.queue = [];
3574
+ try {
3575
+ const response = await fetch(`${this.endpoint}/api/sdk/metrics`, {
3576
+ method: "POST",
3577
+ headers: {
3578
+ "Content-Type": "application/json",
3579
+ "X-API-Key": this.apiKey
3580
+ },
3581
+ body: JSON.stringify({ metrics })
3582
+ });
3583
+ if (!response.ok) {
3584
+ throw new Error(`HTTP ${response.status}`);
3585
+ }
3586
+ this.log(`Flushed ${metrics.length} metrics`);
3587
+ } catch (err) {
3588
+ this.queue.unshift(...metrics);
3589
+ this.log("Failed to flush metrics:", err);
3590
+ }
3591
+ }
3592
+ /**
3593
+ * Stop the flush timer and flush remaining metrics
3594
+ */
3595
+ async destroy() {
3596
+ if (this.flushTimer) {
3597
+ clearInterval(this.flushTimer);
3598
+ this.flushTimer = null;
3599
+ }
3600
+ await this.flush();
3601
+ }
3602
+ enqueue(name, value, type, options) {
3603
+ this.queue.push({
3604
+ name,
3605
+ value,
3606
+ type,
3607
+ unit: options?.unit,
3608
+ tags: options?.tags,
3609
+ timestamp: options?.timestamp || (/* @__PURE__ */ new Date()).toISOString()
3610
+ });
3611
+ if (this.queue.length >= this.maxBatchSize) {
3612
+ this.flush();
3613
+ }
3614
+ }
3615
+ startFlushTimer() {
3616
+ this.flushTimer = setInterval(() => {
3617
+ this.flush();
3618
+ }, this.flushInterval);
3619
+ if (typeof window !== "undefined") {
3620
+ window.addEventListener("visibilitychange", () => {
3621
+ if (document.visibilityState === "hidden") {
3622
+ this.flushWithBeacon();
3623
+ }
3624
+ });
3625
+ }
3626
+ }
3627
+ flushWithBeacon() {
3628
+ if (this.queue.length === 0 || typeof navigator?.sendBeacon !== "function") return;
3629
+ const metrics = [...this.queue];
3630
+ this.queue = [];
3631
+ const blob = new Blob(
3632
+ [JSON.stringify({ metrics, apiKey: this.apiKey })],
3633
+ { type: "application/json" }
3634
+ );
3635
+ navigator.sendBeacon(`${this.endpoint}/api/sdk/metrics`, blob);
3636
+ this.log(`Flushed ${metrics.length} metrics via beacon`);
3637
+ }
3638
+ log(...args) {
3639
+ if (this.debug) {
3640
+ console.log("[SitePong:Metrics]", ...args);
3641
+ }
3642
+ }
3643
+ };
3644
+
3645
+ // src/database.ts
3646
+ var DatabaseTracker = class {
3647
+ constructor(config) {
3648
+ this.queue = [];
3649
+ this.flushTimer = null;
3650
+ this.queryCounter = 0;
3651
+ this.nPlusOneDetector = /* @__PURE__ */ new Map();
3652
+ this.config = {
3653
+ slowQueryThreshold: 1e3,
3654
+ maxBatchSize: 50,
3655
+ flushInterval: 1e4,
3656
+ redactParams: true,
3657
+ ...config
3658
+ };
3659
+ this.startFlushTimer();
3660
+ }
3661
+ /**
3662
+ * Track a database query execution
3663
+ */
3664
+ async track(query, fn, source) {
3665
+ const start = Date.now();
3666
+ this.queryCounter++;
3667
+ try {
3668
+ const result = await fn();
3669
+ const duration_ms = Date.now() - start;
3670
+ this.recordQuery({
3671
+ query: this.config.redactParams ? this.redactQuery(query) : query,
3672
+ duration_ms,
3673
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3674
+ source,
3675
+ is_slow: duration_ms >= (this.config.slowQueryThreshold || 1e3)
3676
+ });
3677
+ this.detectNPlusOne(query);
3678
+ return result;
3679
+ } catch (err) {
3680
+ const duration_ms = Date.now() - start;
3681
+ this.recordQuery({
3682
+ query: this.config.redactParams ? this.redactQuery(query) : query,
3683
+ duration_ms,
3684
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3685
+ source,
3686
+ error: err instanceof Error ? err.message : String(err)
3687
+ });
3688
+ throw err;
3689
+ }
3690
+ }
3691
+ /**
3692
+ * Track a synchronous database query
3693
+ */
3694
+ trackSync(query, fn, source) {
3695
+ const start = Date.now();
3696
+ this.queryCounter++;
3697
+ try {
3698
+ const result = fn();
3699
+ const duration_ms = Date.now() - start;
3700
+ this.recordQuery({
3701
+ query: this.config.redactParams ? this.redactQuery(query) : query,
3702
+ duration_ms,
3703
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3704
+ source,
3705
+ is_slow: duration_ms >= (this.config.slowQueryThreshold || 1e3)
3706
+ });
3707
+ return result;
3708
+ } catch (err) {
3709
+ const duration_ms = Date.now() - start;
3710
+ this.recordQuery({
3711
+ query: this.config.redactParams ? this.redactQuery(query) : query,
3712
+ duration_ms,
3713
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3714
+ source,
3715
+ error: err instanceof Error ? err.message : String(err)
3716
+ });
3717
+ throw err;
3718
+ }
3719
+ }
3720
+ /**
3721
+ * Get current query count (useful for N+1 detection in tests)
3722
+ */
3723
+ getQueryCount() {
3724
+ return this.queryCounter;
3725
+ }
3726
+ /**
3727
+ * Reset the query counter
3728
+ */
3729
+ resetQueryCount() {
3730
+ this.queryCounter = 0;
3731
+ this.nPlusOneDetector.clear();
3732
+ }
3733
+ /**
3734
+ * Get detected N+1 patterns
3735
+ */
3736
+ getNPlusOnePatterns() {
3737
+ const patterns = [];
3738
+ for (const [query, { count }] of this.nPlusOneDetector.entries()) {
3739
+ if (count >= 5) {
3740
+ patterns.push({ query, count });
3741
+ }
3742
+ }
3743
+ return patterns;
3744
+ }
3745
+ /**
3746
+ * Force flush queued query events
3747
+ */
3748
+ async flush() {
3749
+ if (this.queue.length === 0) return;
3750
+ const queries = [...this.queue];
3751
+ this.queue = [];
3752
+ try {
3753
+ const response = await fetch(`${this.config.endpoint}/api/sdk/metrics`, {
3754
+ method: "POST",
3755
+ headers: {
3756
+ "Content-Type": "application/json",
3757
+ "X-API-Key": this.config.apiKey
3758
+ },
3759
+ body: JSON.stringify({
3760
+ metrics: queries.map((q) => ({
3761
+ name: "db.query",
3762
+ value: q.duration_ms,
3763
+ type: "distribution",
3764
+ unit: "ms",
3765
+ tags: {
3766
+ query: q.query.substring(0, 200),
3767
+ source: q.source || "unknown",
3768
+ is_slow: String(q.is_slow || false),
3769
+ ...q.error ? { error: "true" } : {}
3770
+ },
3771
+ timestamp: q.timestamp
3772
+ }))
3773
+ })
3774
+ });
3775
+ if (!response.ok) {
3776
+ throw new Error(`HTTP ${response.status}`);
3777
+ }
3778
+ this.log(`Flushed ${queries.length} query events`);
3779
+ } catch (err) {
3780
+ this.queue.unshift(...queries);
3781
+ this.log("Failed to flush query events:", err);
3782
+ }
3783
+ }
3784
+ /**
3785
+ * Destroy the tracker and flush remaining events
3786
+ */
3787
+ async destroy() {
3788
+ if (this.flushTimer) {
3789
+ clearInterval(this.flushTimer);
3790
+ this.flushTimer = null;
3791
+ }
3792
+ await this.flush();
3793
+ }
3794
+ recordQuery(event) {
3795
+ this.queue.push(event);
3796
+ if (this.queue.length >= (this.config.maxBatchSize || 50)) {
3797
+ this.flush();
3798
+ }
3799
+ if (event.is_slow) {
3800
+ this.log(`Slow query (${event.duration_ms}ms):`, event.query.substring(0, 100));
3801
+ }
3802
+ }
3803
+ detectNPlusOne(query) {
3804
+ const pattern = this.normalizeQuery(query);
3805
+ const now = Date.now();
3806
+ const existing = this.nPlusOneDetector.get(pattern);
3807
+ if (existing) {
3808
+ if (now - existing.since < 1e3) {
3809
+ existing.count++;
3810
+ if (existing.count === 5) {
3811
+ this.log(`N+1 query detected! Pattern repeated ${existing.count} times:`, pattern.substring(0, 100));
3812
+ }
3813
+ } else {
3814
+ this.nPlusOneDetector.set(pattern, { count: 1, since: now });
3815
+ }
3816
+ } else {
3817
+ this.nPlusOneDetector.set(pattern, { count: 1, since: now });
3818
+ }
3819
+ if (this.nPlusOneDetector.size > 100) {
3820
+ const entries = [...this.nPlusOneDetector.entries()];
3821
+ entries.sort((a, b) => a[1].since - b[1].since);
3822
+ for (let i = 0; i < 50; i++) {
3823
+ this.nPlusOneDetector.delete(entries[i][0]);
3824
+ }
3825
+ }
3826
+ }
3827
+ normalizeQuery(query) {
3828
+ return query.replace(/\s+/g, " ").replace(/'[^']*'/g, "?").replace(/\b\d+\b/g, "?").trim().substring(0, 300);
3829
+ }
3830
+ redactQuery(query) {
3831
+ return query.replace(/'[^']*'/g, "'***'").replace(/\b\d{4,}\b/g, "***");
3832
+ }
3833
+ startFlushTimer() {
3834
+ this.flushTimer = setInterval(() => {
3835
+ this.flush();
3836
+ }, this.config.flushInterval || 1e4);
3837
+ }
3838
+ log(...args) {
3839
+ if (this.config.debug) {
3840
+ console.log("[SitePong:DB]", ...args);
3841
+ }
3842
+ }
3843
+ };
3844
+
3845
+ // src/profiling.ts
3846
+ var Profiler = class {
3847
+ constructor(config) {
3848
+ this.profiles = [];
3849
+ this.activeProfile = null;
3850
+ this.frameStack = [];
3851
+ this.flushTimer = null;
3852
+ this.config = {
3853
+ sampleRate: 0.1,
3854
+ maxDuration: 3e4,
3855
+ flushInterval: 3e4,
3856
+ ...config
3857
+ };
3858
+ this.startFlushTimer();
3859
+ }
3860
+ /**
3861
+ * Profile an async function execution
3862
+ */
3863
+ async profile(name, fn, metadata) {
3864
+ if (Math.random() > (this.config.sampleRate || 0.1)) {
3865
+ return fn();
3866
+ }
3867
+ const profileId = this.generateId();
3868
+ const startTime = performance.now();
3869
+ this.activeProfile = {
3870
+ id: profileId,
3871
+ name,
3872
+ startTime,
3873
+ duration_ms: 0,
3874
+ frames: [],
3875
+ metadata
3876
+ };
3877
+ this.frameStack = [];
3878
+ const rootFrame = {
3879
+ name,
3880
+ start_ms: 0,
3881
+ duration_ms: 0,
3882
+ children: [],
3883
+ metadata
3884
+ };
3885
+ this.frameStack.push(rootFrame);
3886
+ try {
3887
+ const result = await fn();
3888
+ rootFrame.duration_ms = performance.now() - startTime;
3889
+ this.activeProfile.duration_ms = rootFrame.duration_ms;
3890
+ this.activeProfile.frames = [rootFrame];
3891
+ if (rootFrame.duration_ms <= (this.config.maxDuration || 3e4)) {
3892
+ this.profiles.push(this.activeProfile);
3893
+ }
3894
+ this.activeProfile = null;
3895
+ this.frameStack = [];
3896
+ return result;
3897
+ } catch (err) {
3898
+ rootFrame.duration_ms = performance.now() - startTime;
3899
+ rootFrame.metadata = { ...rootFrame.metadata, error: true };
3900
+ if (this.activeProfile) {
3901
+ this.activeProfile.duration_ms = rootFrame.duration_ms;
3902
+ this.activeProfile.frames = [rootFrame];
3903
+ this.profiles.push(this.activeProfile);
3904
+ }
3905
+ this.activeProfile = null;
3906
+ this.frameStack = [];
3907
+ throw err;
3908
+ }
3909
+ }
3910
+ /**
3911
+ * Start a profiling span within an active profile
3912
+ */
3913
+ startSpan(name, metadata) {
3914
+ if (!this.activeProfile) return () => {
3915
+ };
3916
+ const startMs = performance.now() - this.activeProfile.startTime;
3917
+ const frame = {
3918
+ name,
3919
+ start_ms: startMs,
3920
+ duration_ms: 0,
3921
+ children: [],
3922
+ metadata
3923
+ };
3924
+ const parent = this.frameStack[this.frameStack.length - 1];
3925
+ if (parent) {
3926
+ parent.children.push(frame);
3927
+ }
3928
+ this.frameStack.push(frame);
3929
+ return () => {
3930
+ frame.duration_ms = performance.now() - this.activeProfile.startTime - frame.start_ms;
3931
+ this.frameStack.pop();
3932
+ };
3933
+ }
3934
+ /**
3935
+ * Get all collected profiles
3936
+ */
3937
+ getProfiles() {
3938
+ return [...this.profiles];
3939
+ }
3940
+ /**
3941
+ * Get the latest profile
3942
+ */
3943
+ getLatestProfile() {
3944
+ return this.profiles.length > 0 ? this.profiles[this.profiles.length - 1] : null;
3945
+ }
3946
+ /**
3947
+ * Flush profiles to the server
3948
+ */
3949
+ async flush() {
3950
+ if (this.profiles.length === 0) return;
3951
+ const profiles = [...this.profiles];
3952
+ this.profiles = [];
3953
+ try {
3954
+ const response = await fetch(`${this.config.endpoint}/api/sdk/metrics`, {
3955
+ method: "POST",
3956
+ headers: {
3957
+ "Content-Type": "application/json",
3958
+ "X-API-Key": this.config.apiKey
3959
+ },
3960
+ body: JSON.stringify({
3961
+ metrics: profiles.map((p) => ({
3962
+ name: `profile.${p.name}`,
3963
+ value: p.duration_ms,
3964
+ type: "distribution",
3965
+ unit: "ms",
3966
+ tags: {
3967
+ profile_id: p.id,
3968
+ frame_count: String(countFrames(p.frames)),
3969
+ ...p.metadata || {}
3970
+ },
3971
+ timestamp: new Date(Date.now() - p.duration_ms).toISOString()
3972
+ }))
3973
+ })
3974
+ });
3975
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
3976
+ this.log(`Flushed ${profiles.length} profiles`);
3977
+ } catch (err) {
3978
+ this.profiles.unshift(...profiles);
3979
+ this.log("Failed to flush profiles:", err);
3980
+ }
3981
+ }
3982
+ /**
3983
+ * Destroy the profiler
3984
+ */
3985
+ async destroy() {
3986
+ if (this.flushTimer) {
3987
+ clearInterval(this.flushTimer);
3988
+ this.flushTimer = null;
3989
+ }
3990
+ await this.flush();
3991
+ }
3992
+ startFlushTimer() {
3993
+ this.flushTimer = setInterval(() => {
3994
+ this.flush();
3995
+ }, this.config.flushInterval || 3e4);
3996
+ }
3997
+ generateId() {
3998
+ return `prof_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 8)}`;
3999
+ }
4000
+ log(...args) {
4001
+ if (this.config.debug) {
4002
+ console.log("[SitePong:Profiler]", ...args);
4003
+ }
4004
+ }
4005
+ };
4006
+ function countFrames(frames) {
4007
+ let count = frames.length;
4008
+ for (const frame of frames) {
4009
+ count += countFrames(frame.children);
4010
+ }
4011
+ return count;
4012
+ }
4013
+
1
4014
  // src/index.ts
2
- var DEFAULT_ENDPOINT = "https://ingest.sitepong.com";
3
- var DEFAULT_BATCH_SIZE = 10;
4
- var DEFAULT_FLUSH_INTERVAL = 5e3;
4015
+ var DEFAULT_ENDPOINT6 = "https://ingest.sitepong.com";
4016
+ var DEFAULT_BATCH_SIZE2 = 10;
4017
+ var DEFAULT_FLUSH_INTERVAL4 = 5e3;
5
4018
  var SitePongClient = class {
6
4019
  constructor() {
7
4020
  this.errorQueue = [];
8
4021
  this.flushTimer = null;
9
4022
  this.context = {};
10
4023
  this.initialized = false;
4024
+ this.flagManager = null;
4025
+ this.analyticsManager = null;
4026
+ this.fingerprintManager = null;
4027
+ this.replayManager = null;
4028
+ this.performanceManager = null;
4029
+ this.cronManager = null;
4030
+ this.metricsManager = null;
4031
+ this.databaseTracker = null;
4032
+ this.profiler = null;
11
4033
  this.config = {
12
4034
  apiKey: "",
13
- endpoint: DEFAULT_ENDPOINT,
4035
+ endpoint: DEFAULT_ENDPOINT6,
14
4036
  environment: "production",
15
4037
  release: "",
16
4038
  autoCapture: true,
17
- maxBatchSize: DEFAULT_BATCH_SIZE,
18
- flushInterval: DEFAULT_FLUSH_INTERVAL,
4039
+ maxBatchSize: DEFAULT_BATCH_SIZE2,
4040
+ flushInterval: DEFAULT_FLUSH_INTERVAL4,
19
4041
  debug: false
20
4042
  };
21
4043
  }
@@ -34,12 +4056,389 @@ var SitePongClient = class {
34
4056
  }
35
4057
  this.startFlushTimer();
36
4058
  this.log("Initialized with endpoint:", this.config.endpoint);
4059
+ if (config.enableFlags) {
4060
+ this.flagManager = new FlagManager({
4061
+ apiKey: config.apiKey,
4062
+ endpoint: config.flagsEndpoint || config.endpoint || DEFAULT_ENDPOINT6,
4063
+ debug: config.debug
4064
+ });
4065
+ this.flagManager.init().catch((err) => {
4066
+ this.log("Failed to initialize flags:", err);
4067
+ });
4068
+ }
4069
+ if (config.analytics?.enabled) {
4070
+ if (this.analyticsManager) {
4071
+ this.analyticsManager.destroy();
4072
+ }
4073
+ this.analyticsManager = new AnalyticsManager({
4074
+ apiKey: config.apiKey,
4075
+ endpoint: config.endpoint || DEFAULT_ENDPOINT6,
4076
+ enabled: true,
4077
+ autocapturePageviews: config.analytics.autocapturePageviews,
4078
+ autocaptureClicks: config.analytics.autocaptureClicks,
4079
+ autocaptureForms: config.analytics.autocaptureForms,
4080
+ autocapture: config.analytics.autocapture,
4081
+ maxBatchSize: config.analytics.maxBatchSize,
4082
+ flushInterval: config.analytics.flushInterval,
4083
+ eventsEndpoint: config.analytics.eventsEndpoint,
4084
+ debug: config.debug
4085
+ });
4086
+ this.analyticsManager.init();
4087
+ }
4088
+ if (config.fingerprint?.enabled) {
4089
+ this.fingerprintManager = new FingerprintManager({
4090
+ apiKey: config.apiKey,
4091
+ endpoint: config.endpoint || DEFAULT_ENDPOINT6,
4092
+ enabled: true,
4093
+ extendedSignals: config.fingerprint.extendedSignals,
4094
+ visitorEndpoint: config.fingerprint.visitorEndpoint,
4095
+ debug: config.debug
4096
+ });
4097
+ }
4098
+ if (config.performance?.enabled) {
4099
+ if (this.performanceManager) {
4100
+ this.performanceManager.destroy();
4101
+ }
4102
+ this.performanceManager = new PerformanceManager({
4103
+ apiKey: config.apiKey,
4104
+ endpoint: config.endpoint || DEFAULT_ENDPOINT6,
4105
+ enabled: true,
4106
+ webVitals: config.performance.webVitals,
4107
+ navigationTiming: config.performance.navigationTiming,
4108
+ resourceTiming: config.performance.resourceTiming,
4109
+ sampleRate: config.performance.sampleRate,
4110
+ flushInterval: config.performance.flushInterval,
4111
+ performanceEndpoint: config.performance.performanceEndpoint,
4112
+ debug: config.debug
4113
+ });
4114
+ this.performanceManager.init();
4115
+ }
4116
+ if (config.replay?.enabled) {
4117
+ if (this.replayManager) {
4118
+ this.replayManager.stop();
4119
+ }
4120
+ this.replayManager = new ReplayManager({
4121
+ apiKey: config.apiKey,
4122
+ endpoint: config.endpoint || DEFAULT_ENDPOINT6,
4123
+ enabled: true,
4124
+ maskInputs: config.replay.maskInputs,
4125
+ blockSelector: config.replay.blockSelector,
4126
+ maskSelector: config.replay.maskSelector,
4127
+ sampleRate: config.replay.sampleRate,
4128
+ maxSessionDuration: config.replay.maxSessionDuration,
4129
+ flushInterval: config.replay.flushInterval,
4130
+ replayEndpoint: config.replay.replayEndpoint,
4131
+ recordNetwork: config.replay.recordNetwork,
4132
+ captureNetworkHeaders: config.replay.captureNetworkHeaders,
4133
+ recordConsole: config.replay.recordConsole,
4134
+ consoleLevels: config.replay.consoleLevels,
4135
+ debug: config.debug
4136
+ });
4137
+ this.replayManager.start();
4138
+ }
4139
+ if (config.crons?.enabled) {
4140
+ this.cronManager = new CronMonitorManager({
4141
+ apiKey: config.apiKey,
4142
+ endpoint: config.endpoint || DEFAULT_ENDPOINT6,
4143
+ debug: config.debug
4144
+ });
4145
+ }
4146
+ if (config.database?.enabled) {
4147
+ this.databaseTracker = new DatabaseTracker({
4148
+ apiKey: config.apiKey,
4149
+ endpoint: config.endpoint || DEFAULT_ENDPOINT6,
4150
+ debug: config.debug,
4151
+ slowQueryThreshold: config.database.slowQueryThreshold,
4152
+ redactParams: config.database.redactParams
4153
+ });
4154
+ }
4155
+ if (config.metrics?.enabled) {
4156
+ this.metricsManager = new MetricsManager({
4157
+ apiKey: config.apiKey,
4158
+ endpoint: config.endpoint || DEFAULT_ENDPOINT6,
4159
+ debug: config.debug,
4160
+ flushInterval: config.metrics.flushInterval,
4161
+ maxBatchSize: config.metrics.maxBatchSize
4162
+ });
4163
+ }
4164
+ if (config.profiling?.enabled) {
4165
+ this.profiler = new Profiler({
4166
+ apiKey: config.apiKey,
4167
+ endpoint: config.endpoint || DEFAULT_ENDPOINT6,
4168
+ debug: config.debug,
4169
+ sampleRate: config.profiling.sampleRate,
4170
+ maxDuration: config.profiling.maxDuration,
4171
+ flushInterval: config.profiling.flushInterval
4172
+ });
4173
+ }
4174
+ }
4175
+ /**
4176
+ * Get a feature flag value
4177
+ * Returns the cached evaluated result or default value
4178
+ */
4179
+ getFlag(key, defaultValue = false) {
4180
+ if (!this.flagManager) {
4181
+ this.log("Flags not enabled. Set enableFlags: true in init()");
4182
+ return defaultValue;
4183
+ }
4184
+ return this.flagManager.getFlag(key, defaultValue);
4185
+ }
4186
+ /**
4187
+ * Get all feature flag values
4188
+ */
4189
+ getAllFlags() {
4190
+ if (!this.flagManager) {
4191
+ return {};
4192
+ }
4193
+ return this.flagManager.getAllFlags();
4194
+ }
4195
+ /**
4196
+ * Wait for flags to be initialized
4197
+ */
4198
+ async waitForFlags() {
4199
+ if (!this.flagManager) {
4200
+ return;
4201
+ }
4202
+ await this.flagManager.waitForInit();
4203
+ }
4204
+ /**
4205
+ * Check if flags are ready
4206
+ */
4207
+ areFlagsReady() {
4208
+ return this.flagManager?.isInitialized() ?? false;
4209
+ }
4210
+ /**
4211
+ * Get a multivariate flag variant
4212
+ * Returns the assigned variant key or the default value
4213
+ */
4214
+ getVariant(key, defaultValue = null) {
4215
+ if (!this.flagManager) {
4216
+ this.log("Flags not enabled. Set enableFlags: true in init()");
4217
+ return defaultValue;
4218
+ }
4219
+ return this.flagManager.getVariant(key, defaultValue);
4220
+ }
4221
+ /**
4222
+ * Get the payload for a multivariate flag's assigned variant
4223
+ */
4224
+ getVariantPayload(key, defaultValue = null) {
4225
+ if (!this.flagManager) {
4226
+ return defaultValue;
4227
+ }
4228
+ return this.flagManager.getVariantPayload(key, defaultValue);
4229
+ }
4230
+ /**
4231
+ * Force refresh flags from the server
4232
+ */
4233
+ async refreshFlags() {
4234
+ if (!this.flagManager) {
4235
+ return;
4236
+ }
4237
+ await this.flagManager.refresh();
4238
+ }
4239
+ // --- Analytics (Tier 4) ---
4240
+ track(eventName, properties) {
4241
+ if (!this.analyticsManager) {
4242
+ this.log("Analytics not enabled. Set analytics.enabled: true in init()");
4243
+ return;
4244
+ }
4245
+ this.analyticsManager.track(eventName, properties);
4246
+ }
4247
+ trackPageView(url, properties) {
4248
+ if (!this.analyticsManager) {
4249
+ this.log("Analytics not enabled. Set analytics.enabled: true in init()");
4250
+ return;
4251
+ }
4252
+ this.analyticsManager.trackPageView(url, properties);
4253
+ }
4254
+ identify(userId, traits) {
4255
+ if (this.replayManager) {
4256
+ this.replayManager.setUser({ id: userId });
4257
+ }
4258
+ if (!this.analyticsManager) {
4259
+ this.log("Analytics not enabled. Set analytics.enabled: true in init()");
4260
+ return;
4261
+ }
4262
+ this.analyticsManager.identify(userId, traits);
4263
+ }
4264
+ group(groupId, traits) {
4265
+ if (!this.analyticsManager) {
4266
+ this.log("Analytics not enabled. Set analytics.enabled: true in init()");
4267
+ return;
4268
+ }
4269
+ this.analyticsManager.group(groupId, traits);
4270
+ }
4271
+ resetAnalytics() {
4272
+ if (!this.analyticsManager) {
4273
+ return;
4274
+ }
4275
+ this.analyticsManager.reset();
4276
+ }
4277
+ // --- Fingerprint (Tier 5) ---
4278
+ async getVisitorId() {
4279
+ if (!this.fingerprintManager) {
4280
+ throw new Error("Fingerprint not enabled. Set fingerprint.enabled: true in init()");
4281
+ }
4282
+ return this.fingerprintManager.getVisitorId();
4283
+ }
4284
+ async getDeviceSignals() {
4285
+ if (!this.fingerprintManager) {
4286
+ throw new Error("Fingerprint not enabled. Set fingerprint.enabled: true in init()");
4287
+ }
4288
+ return this.fingerprintManager.getDeviceSignals();
4289
+ }
4290
+ async getFraudCheck() {
4291
+ if (!this.fingerprintManager) {
4292
+ throw new Error("Fingerprint not enabled. Set fingerprint.enabled: true in init()");
4293
+ }
4294
+ return this.fingerprintManager.getFraudCheck();
4295
+ }
4296
+ // --- Session Replay (Tier 6) ---
4297
+ startReplay() {
4298
+ if (!this.replayManager) {
4299
+ this.log("Replay not enabled. Set replay.enabled: true in init()");
4300
+ return false;
4301
+ }
4302
+ return this.replayManager.start();
4303
+ }
4304
+ stopReplay() {
4305
+ if (!this.replayManager) return;
4306
+ this.replayManager.stop();
4307
+ }
4308
+ isReplayRecording() {
4309
+ return this.replayManager?.isRecording() ?? false;
4310
+ }
4311
+ getReplaySessionId() {
4312
+ return this.replayManager?.getSessionId() ?? null;
4313
+ }
4314
+ // --- Performance (Tier 7) ---
4315
+ startTransaction(name, data) {
4316
+ if (!this.performanceManager) {
4317
+ this.log("Performance not enabled. Set performance.enabled: true in init()");
4318
+ return "";
4319
+ }
4320
+ return this.performanceManager.startTransaction(name, data);
4321
+ }
4322
+ endTransaction(id, status = "ok") {
4323
+ if (!this.performanceManager) return;
4324
+ this.performanceManager.endTransaction(id, status);
4325
+ }
4326
+ startSpan(transactionId, name, data) {
4327
+ if (!this.performanceManager) return "";
4328
+ return this.performanceManager.startSpan(transactionId, name, data);
4329
+ }
4330
+ endSpan(transactionId, spanId, status = "ok") {
4331
+ if (!this.performanceManager) return;
4332
+ this.performanceManager.endSpan(transactionId, spanId, status);
4333
+ }
4334
+ getWebVitals() {
4335
+ if (!this.performanceManager) return {};
4336
+ return this.performanceManager.getVitals();
4337
+ }
4338
+ // --- Cron Monitor methods ---
4339
+ cronCheckin(slug, options) {
4340
+ if (!this.cronManager) {
4341
+ this.log("Cron monitoring not enabled. Set crons.enabled: true in init()");
4342
+ return Promise.resolve();
4343
+ }
4344
+ return this.cronManager.checkin(slug, options);
4345
+ }
4346
+ cronStart(slug, environment) {
4347
+ if (!this.cronManager) {
4348
+ this.log("Cron monitoring not enabled. Set crons.enabled: true in init()");
4349
+ return { ok: () => Promise.resolve(), error: () => Promise.resolve() };
4350
+ }
4351
+ return this.cronManager.start(slug, environment);
4352
+ }
4353
+ cronWrap(slug, fn, environment) {
4354
+ if (!this.cronManager) {
4355
+ return fn();
4356
+ }
4357
+ return this.cronManager.wrap(slug, fn, environment);
4358
+ }
4359
+ // --- Custom Metrics methods ---
4360
+ metricIncrement(name, value, options) {
4361
+ if (!this.metricsManager) {
4362
+ this.log("Metrics not enabled. Set metrics.enabled: true in init()");
4363
+ return;
4364
+ }
4365
+ this.metricsManager.increment(name, value, options);
4366
+ }
4367
+ metricGauge(name, value, options) {
4368
+ if (!this.metricsManager) return;
4369
+ this.metricsManager.gauge(name, value, options);
4370
+ }
4371
+ metricHistogram(name, value, options) {
4372
+ if (!this.metricsManager) return;
4373
+ this.metricsManager.histogram(name, value, options);
4374
+ }
4375
+ metricDistribution(name, value, options) {
4376
+ if (!this.metricsManager) return;
4377
+ this.metricsManager.distribution(name, value, options);
4378
+ }
4379
+ async metricTime(name, fn, options) {
4380
+ if (!this.metricsManager) return fn();
4381
+ return this.metricsManager.time(name, fn, options);
4382
+ }
4383
+ metricStartTimer(name, options) {
4384
+ if (!this.metricsManager) return { stop: () => {
4385
+ } };
4386
+ return this.metricsManager.startTimer(name, options);
4387
+ }
4388
+ async flushMetrics() {
4389
+ if (!this.metricsManager) return;
4390
+ return this.metricsManager.flush();
4391
+ }
4392
+ // --- Database Query Tracking methods ---
4393
+ async dbTrack(query, fn, source) {
4394
+ if (!this.databaseTracker) return fn();
4395
+ return this.databaseTracker.track(query, fn, source);
4396
+ }
4397
+ dbTrackSync(query, fn, source) {
4398
+ if (!this.databaseTracker) return fn();
4399
+ return this.databaseTracker.trackSync(query, fn, source);
4400
+ }
4401
+ getDbQueryCount() {
4402
+ if (!this.databaseTracker) return 0;
4403
+ return this.databaseTracker.getQueryCount();
4404
+ }
4405
+ resetDbQueryCount() {
4406
+ if (this.databaseTracker) this.databaseTracker.resetQueryCount();
4407
+ }
4408
+ getDbNPlusOnePatterns() {
4409
+ if (!this.databaseTracker) return [];
4410
+ return this.databaseTracker.getNPlusOnePatterns();
4411
+ }
4412
+ // --- Profiling methods ---
4413
+ async profile(name, fn, metadata) {
4414
+ if (!this.profiler) return fn();
4415
+ return this.profiler.profile(name, fn, metadata);
4416
+ }
4417
+ startProfileSpan(name, metadata) {
4418
+ if (!this.profiler) return () => {
4419
+ };
4420
+ return this.profiler.startSpan(name, metadata);
4421
+ }
4422
+ getProfiles() {
4423
+ if (!this.profiler) return [];
4424
+ return this.profiler.getProfiles();
4425
+ }
4426
+ getLatestProfile() {
4427
+ if (!this.profiler) return null;
4428
+ return this.profiler.getLatestProfile();
4429
+ }
4430
+ async flushProfiles() {
4431
+ if (!this.profiler) return;
4432
+ return this.profiler.flush();
37
4433
  }
38
4434
  setContext(context) {
39
4435
  this.context = { ...this.context, ...context };
40
4436
  }
41
4437
  setUser(user) {
42
4438
  this.context.user = user;
4439
+ if (this.replayManager) {
4440
+ this.replayManager.setUser(user ? { id: user.id } : null);
4441
+ }
43
4442
  }
44
4443
  setTags(tags) {
45
4444
  this.context.tags = { ...this.context.tags, ...tags };
@@ -69,7 +4468,8 @@ var SitePongClient = class {
69
4468
  userAgent: this.getUserAgent(),
70
4469
  environment: this.config.environment,
71
4470
  release: this.config.release,
72
- context: { ...this.context, ...additionalContext }
4471
+ context: { ...this.context, ...additionalContext },
4472
+ replaySessionId: this.replayManager?.isRecording() ? this.replayManager.getSessionId() || void 0 : void 0
73
4473
  };
74
4474
  this.errorQueue.push(capturedError);
75
4475
  this.log("Captured message:", message);
@@ -104,7 +4504,7 @@ var SitePongClient = class {
104
4504
  formatError(error, additionalContext) {
105
4505
  const isError = error instanceof Error;
106
4506
  const message = isError ? error.message : String(error);
107
- const stack = isError ? error.stack : void 0;
4507
+ const stack = isError ? error.stack : new Error(message).stack;
108
4508
  const type = isError ? error.name : "Error";
109
4509
  return {
110
4510
  message,
@@ -116,7 +4516,8 @@ var SitePongClient = class {
116
4516
  environment: this.config.environment,
117
4517
  release: this.config.release,
118
4518
  context: { ...this.context, ...additionalContext },
119
- fingerprint: this.generateFingerprint(message, stack)
4519
+ fingerprint: this.generateFingerprint(message, stack),
4520
+ replaySessionId: this.replayManager?.isRecording() ? this.replayManager.getSessionId() || void 0 : void 0
120
4521
  };
121
4522
  }
122
4523
  generateFingerprint(message, stack) {
@@ -207,8 +4608,52 @@ var setContext = sitepong.setContext.bind(sitepong);
207
4608
  var setUser = sitepong.setUser.bind(sitepong);
208
4609
  var setTags = sitepong.setTags.bind(sitepong);
209
4610
  var flush = sitepong.flush.bind(sitepong);
4611
+ var getFlag = sitepong.getFlag.bind(sitepong);
4612
+ var getAllFlags = sitepong.getAllFlags.bind(sitepong);
4613
+ var getVariant = sitepong.getVariant.bind(sitepong);
4614
+ var getVariantPayload2 = sitepong.getVariantPayload.bind(sitepong);
4615
+ var waitForFlags = sitepong.waitForFlags.bind(sitepong);
4616
+ var areFlagsReady = sitepong.areFlagsReady.bind(sitepong);
4617
+ var refreshFlags = sitepong.refreshFlags.bind(sitepong);
4618
+ var track = sitepong.track.bind(sitepong);
4619
+ var trackPageView = sitepong.trackPageView.bind(sitepong);
4620
+ var identify = sitepong.identify.bind(sitepong);
4621
+ var group = sitepong.group.bind(sitepong);
4622
+ var resetAnalytics = sitepong.resetAnalytics.bind(sitepong);
4623
+ var getVisitorId = sitepong.getVisitorId.bind(sitepong);
4624
+ var getDeviceSignals = sitepong.getDeviceSignals.bind(sitepong);
4625
+ var getFraudCheck = sitepong.getFraudCheck.bind(sitepong);
4626
+ var startReplay = sitepong.startReplay.bind(sitepong);
4627
+ var stopReplay = sitepong.stopReplay.bind(sitepong);
4628
+ var isReplayRecording = sitepong.isReplayRecording.bind(sitepong);
4629
+ var getReplaySessionId = sitepong.getReplaySessionId.bind(sitepong);
4630
+ var startTransaction = sitepong.startTransaction.bind(sitepong);
4631
+ var endTransaction = sitepong.endTransaction.bind(sitepong);
4632
+ var startSpan = sitepong.startSpan.bind(sitepong);
4633
+ var endSpan = sitepong.endSpan.bind(sitepong);
4634
+ var getWebVitals = sitepong.getWebVitals.bind(sitepong);
4635
+ var dbTrack = sitepong.dbTrack.bind(sitepong);
4636
+ var dbTrackSync = sitepong.dbTrackSync.bind(sitepong);
4637
+ var getDbQueryCount = sitepong.getDbQueryCount.bind(sitepong);
4638
+ var resetDbQueryCount = sitepong.resetDbQueryCount.bind(sitepong);
4639
+ var getDbNPlusOnePatterns = sitepong.getDbNPlusOnePatterns.bind(sitepong);
4640
+ var cronCheckin = sitepong.cronCheckin.bind(sitepong);
4641
+ var cronStart = sitepong.cronStart.bind(sitepong);
4642
+ var cronWrap = sitepong.cronWrap.bind(sitepong);
4643
+ var metricIncrement = sitepong.metricIncrement.bind(sitepong);
4644
+ var metricGauge = sitepong.metricGauge.bind(sitepong);
4645
+ var metricHistogram = sitepong.metricHistogram.bind(sitepong);
4646
+ var metricDistribution = sitepong.metricDistribution.bind(sitepong);
4647
+ var metricTime = sitepong.metricTime.bind(sitepong);
4648
+ var metricStartTimer = sitepong.metricStartTimer.bind(sitepong);
4649
+ var flushMetrics = sitepong.flushMetrics.bind(sitepong);
4650
+ var profile = sitepong.profile.bind(sitepong);
4651
+ var startProfileSpan = sitepong.startProfileSpan.bind(sitepong);
4652
+ var getProfiles = sitepong.getProfiles.bind(sitepong);
4653
+ var getLatestProfile = sitepong.getLatestProfile.bind(sitepong);
4654
+ var flushProfiles = sitepong.flushProfiles.bind(sitepong);
210
4655
  var index_default = sitepong;
211
4656
 
212
- export { captureError, captureMessage, index_default as default, flush, init, setContext, setTags, setUser };
4657
+ export { TracePropagator, areFlagsReady, captureError, captureMessage, clearAnonymousId, createTraceContext, cronCheckin, cronStart, cronWrap, dbTrack, dbTrackSync, index_default as default, endSpan, endTransaction, extractTrace, flush, flushMetrics, flushProfiles, generateSpanId, generateTraceId, getAllFlags, getAnonymousId, getDbNPlusOnePatterns, getDbQueryCount, getDeviceSignals, getFlag, getFraudCheck, getLatestProfile, getProfiles, getReplaySessionId, getVariant, getVariantPayload2 as getVariantPayload, getVisitorId, getWebVitals, group, identify, init, isReplayRecording, metricDistribution, metricGauge, metricHistogram, metricIncrement, metricStartTimer, metricTime, profile, propagateTrace, refreshFlags, resetAnalytics, resetDbQueryCount, setAnonymousId, setContext, setTags, setUser, startProfileSpan, startReplay, startSpan, startTransaction, stopReplay, track, trackPageView, waitForFlags };
213
4658
  //# sourceMappingURL=index.mjs.map
214
4659
  //# sourceMappingURL=index.mjs.map