sitepong 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- require('web-vitals');
6
-
7
5
  // src/flags/anonymous-id.ts
8
6
  var STORAGE_KEY = "sitepong_anonymous_id";
9
7
  function generateUUID() {
@@ -522,6 +520,60 @@ function clearSession() {
522
520
  memorySessionTs = null;
523
521
  }
524
522
 
523
+ // src/analytics/utm.ts
524
+ var STORAGE_KEY2 = "sp_utm";
525
+ var UTM_KEYS = [
526
+ ["source", "utm_source"],
527
+ ["medium", "utm_medium"],
528
+ ["campaign", "utm_campaign"],
529
+ ["term", "utm_term"],
530
+ ["content", "utm_content"]
531
+ ];
532
+ function parseFromLocation() {
533
+ if (typeof window === "undefined" || !window.location || !window.location.search) return null;
534
+ let params;
535
+ try {
536
+ params = new URLSearchParams(window.location.search);
537
+ } catch {
538
+ return null;
539
+ }
540
+ const result = {};
541
+ let found = false;
542
+ for (const [key, queryKey] of UTM_KEYS) {
543
+ const value = params.get(queryKey);
544
+ if (value) {
545
+ result[key] = value;
546
+ found = true;
547
+ }
548
+ }
549
+ return found ? result : null;
550
+ }
551
+ function readStored() {
552
+ if (typeof sessionStorage === "undefined") return null;
553
+ try {
554
+ const raw = sessionStorage.getItem(STORAGE_KEY2);
555
+ if (!raw) return null;
556
+ const parsed = JSON.parse(raw);
557
+ return parsed && typeof parsed === "object" ? parsed : null;
558
+ } catch {
559
+ return null;
560
+ }
561
+ }
562
+ function writeStored(utm) {
563
+ if (typeof sessionStorage === "undefined") return;
564
+ try {
565
+ sessionStorage.setItem(STORAGE_KEY2, JSON.stringify(utm));
566
+ } catch {
567
+ }
568
+ }
569
+ function getSessionUtm() {
570
+ const stored = readStored();
571
+ if (stored) return stored;
572
+ const fresh = parseFromLocation();
573
+ if (fresh) writeStored(fresh);
574
+ return fresh;
575
+ }
576
+
525
577
  // src/analytics/autocapture.ts
526
578
  var DEFAULT_BLOCK_SELECTORS = [
527
579
  "[data-sp-no-capture]",
@@ -530,6 +582,7 @@ var DEFAULT_BLOCK_SELECTORS = [
530
582
  var MAX_TEXT_LENGTH = 255;
531
583
  var AutocaptureModule = class {
532
584
  constructor(config, callback) {
585
+ this.listeners = [];
533
586
  this.clickHandler = null;
534
587
  this.submitHandler = null;
535
588
  this.active = false;
@@ -544,6 +597,27 @@ var AutocaptureModule = class {
544
597
  };
545
598
  this.callback = callback;
546
599
  }
600
+ /**
601
+ * Register a secondary listener that receives the same events as the
602
+ * primary callback (e.g. Watchtower tap capture reusing the single click
603
+ * listener + selector resolution). Returns an unsubscribe function.
604
+ */
605
+ addListener(cb) {
606
+ this.listeners.push(cb);
607
+ return () => {
608
+ const idx = this.listeners.indexOf(cb);
609
+ if (idx >= 0) this.listeners.splice(idx, 1);
610
+ };
611
+ }
612
+ emit(event) {
613
+ this.callback(event);
614
+ for (const listener of this.listeners) {
615
+ try {
616
+ listener(event);
617
+ } catch {
618
+ }
619
+ }
620
+ }
547
621
  start() {
548
622
  if (this.active || typeof document === "undefined" || typeof document.addEventListener !== "function") return;
549
623
  this.active = true;
@@ -576,7 +650,7 @@ var AutocaptureModule = class {
576
650
  properties.$event_type = "click";
577
651
  properties.$x = e.clientX;
578
652
  properties.$y = e.clientY;
579
- this.callback({
653
+ this.emit({
580
654
  type: "click",
581
655
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
582
656
  properties
@@ -598,7 +672,7 @@ var AutocaptureModule = class {
598
672
  };
599
673
  const elemProps = this.getElementProperties(form);
600
674
  Object.assign(properties, elemProps);
601
- this.callback({
675
+ this.emit({
602
676
  type: "form_submit",
603
677
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
604
678
  properties
@@ -815,6 +889,14 @@ var AnalyticsManager = class {
815
889
  );
816
890
  this.autocaptureModule.start();
817
891
  }
892
+ /**
893
+ * The live AutocaptureModule (if click/form autocapture is enabled) —
894
+ * Watchtower tap capture attaches to it so there is only ONE document-level
895
+ * click listener and one selector/element resolution path.
896
+ */
897
+ getAutocaptureModule() {
898
+ return this.autocaptureModule;
899
+ }
818
900
  track(eventName, properties) {
819
901
  if (!this.config.enabled) return;
820
902
  const event = this.createEvent("track", { name: eventName, properties });
@@ -928,7 +1010,9 @@ var AnalyticsManager = class {
928
1010
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
929
1011
  url: typeof window !== "undefined" && window.location ? window.location.href : void 0,
930
1012
  referrer: typeof document !== "undefined" && typeof document.referrer === "string" ? document.referrer : void 0,
931
- userAgent: typeof navigator !== "undefined" ? navigator.userAgent : void 0
1013
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : void 0,
1014
+ utm: getSessionUtm() || void 0,
1015
+ appVersion: this.config.appVersion || void 0
932
1016
  };
933
1017
  }
934
1018
  enqueue(event) {
@@ -1626,7 +1710,7 @@ var DEFAULT_REMOTE_CONFIG = {
1626
1710
  };
1627
1711
 
1628
1712
  // src/remote-config/manager.ts
1629
- var STORAGE_KEY2 = "sitepong_remote_config";
1713
+ var STORAGE_KEY3 = "sitepong_remote_config";
1630
1714
  var STORAGE_TS_KEY = "sitepong_remote_config_ts";
1631
1715
  var RemoteConfigManager = class {
1632
1716
  constructor(options) {
@@ -1669,7 +1753,7 @@ var RemoteConfigManager = class {
1669
1753
  const storage = this.options.storage;
1670
1754
  if (!storage) return;
1671
1755
  try {
1672
- const cached = await storage.getItem(STORAGE_KEY2);
1756
+ const cached = await storage.getItem(STORAGE_KEY3);
1673
1757
  const cachedTs = await storage.getItem(STORAGE_TS_KEY);
1674
1758
  if (cached && cachedTs) {
1675
1759
  const age = Date.now() - parseInt(cachedTs, 10);
@@ -1730,7 +1814,7 @@ var RemoteConfigManager = class {
1730
1814
  const storage = this.options.storage;
1731
1815
  if (!storage) return;
1732
1816
  try {
1733
- await storage.setItem(STORAGE_KEY2, JSON.stringify(this.config));
1817
+ await storage.setItem(STORAGE_KEY3, JSON.stringify(this.config));
1734
1818
  await storage.setItem(STORAGE_TS_KEY, String(Date.now()));
1735
1819
  } catch {
1736
1820
  this.log("Failed to cache remote config");
@@ -1986,7 +2070,7 @@ function stripTrailingSlash(url) {
1986
2070
  var superlinkClient = new SuperLinkClient();
1987
2071
 
1988
2072
  // src/superlink/parse.ts
1989
- var UTM_KEYS = [
2073
+ var UTM_KEYS2 = [
1990
2074
  ["source", "utm_source"],
1991
2075
  ["medium", "utm_medium"],
1992
2076
  ["campaign", "utm_campaign"],
@@ -2002,7 +2086,7 @@ function parseUniversalLink(url) {
2002
2086
  }
2003
2087
  const params = parsed.searchParams;
2004
2088
  const utm = {};
2005
- for (const [key, queryKey] of UTM_KEYS) {
2089
+ for (const [key, queryKey] of UTM_KEYS2) {
2006
2090
  const value = params.get(queryKey);
2007
2091
  if (value) utm[key] = value;
2008
2092
  }
@@ -2069,13 +2153,13 @@ function getMatchedDeepLink() {
2069
2153
  }
2070
2154
 
2071
2155
  // src/superlink/web.ts
2072
- var STORAGE_KEY3 = "sp_superlink";
2156
+ var STORAGE_KEY4 = "sp_superlink";
2073
2157
  var captured = null;
2074
2158
  var didCapture = false;
2075
- function readStored() {
2159
+ function readStored2() {
2076
2160
  if (typeof sessionStorage === "undefined") return null;
2077
2161
  try {
2078
- const raw = sessionStorage.getItem(STORAGE_KEY3);
2162
+ const raw = sessionStorage.getItem(STORAGE_KEY4);
2079
2163
  if (!raw) return null;
2080
2164
  const parsed = JSON.parse(raw);
2081
2165
  return parsed && typeof parsed === "object" ? parsed : null;
@@ -2083,10 +2167,10 @@ function readStored() {
2083
2167
  return null;
2084
2168
  }
2085
2169
  }
2086
- function writeStored(link) {
2170
+ function writeStored2(link) {
2087
2171
  if (typeof sessionStorage === "undefined") return;
2088
2172
  try {
2089
- sessionStorage.setItem(STORAGE_KEY3, JSON.stringify(link));
2173
+ sessionStorage.setItem(STORAGE_KEY4, JSON.stringify(link));
2090
2174
  } catch {
2091
2175
  }
2092
2176
  }
@@ -2134,7 +2218,7 @@ function extractIdentityMetadata(url) {
2134
2218
  function captureWebDeepLink() {
2135
2219
  if (didCapture) return captured;
2136
2220
  didCapture = true;
2137
- const stored = readStored();
2221
+ const stored = readStored2();
2138
2222
  if (stored) {
2139
2223
  captured = stored;
2140
2224
  return captured;
@@ -2143,7 +2227,7 @@ function captureWebDeepLink() {
2143
2227
  const link = parseUniversalLink(window.location.href);
2144
2228
  if (link && hasPayload(link)) {
2145
2229
  captured = link;
2146
- writeStored(link);
2230
+ writeStored2(link);
2147
2231
  void superlinkClient.reportEvent({
2148
2232
  type: "opened",
2149
2233
  click_id: link.click_id,
@@ -2324,6 +2408,8 @@ var SitePongClient = class {
2324
2408
  this.databaseTracker = null;
2325
2409
  this.profiler = null;
2326
2410
  this.remoteConfigManager = null;
2411
+ this.watchtowerCapture = null;
2412
+ this.watchtowerOptions = void 0;
2327
2413
  this.envUnsubs = [];
2328
2414
  this.identifyHooks = [];
2329
2415
  this.config = {
@@ -2443,6 +2529,12 @@ var SitePongClient = class {
2443
2529
  } else if (config.replay?.enabled) {
2444
2530
  this.log("Replay requested but not available on this platform (web only).");
2445
2531
  }
2532
+ this.watchtowerOptions = config.watchtower;
2533
+ if (config.watchtower?.enabled && webManagerFactories.createWatchtowerCapture) {
2534
+ this.startWatchtowerCapture();
2535
+ } else if (config.watchtower?.enabled) {
2536
+ this.log("Watchtower capture requested but not available on this platform (web only).");
2537
+ }
2446
2538
  if (config.crons?.enabled) {
2447
2539
  this.cronManager = new CronMonitorManager({
2448
2540
  apiKey: config.apiKey,
@@ -2682,6 +2774,7 @@ var SitePongClient = class {
2682
2774
  this.log("identify hook failed:", err);
2683
2775
  }
2684
2776
  }
2777
+ this.watchtowerCapture?.setDistinctId(userId);
2685
2778
  if (!this.analyticsManager) {
2686
2779
  this.log("Analytics not enabled. Set analytics.enabled: true in init()");
2687
2780
  return;
@@ -2749,6 +2842,49 @@ var SitePongClient = class {
2749
2842
  getReplaySessionId() {
2750
2843
  return this.replayManager?.getSessionId() ?? null;
2751
2844
  }
2845
+ // --- Watchtower tap capture (web) ---
2846
+ /**
2847
+ * Start Watchtower web tap capture. Options merge over `watchtower` from
2848
+ * init(); `projectId` is required (either here or in init config). Reuses
2849
+ * the analytics AutocaptureModule's click listener when autocapture is on.
2850
+ */
2851
+ startWatchtowerCapture(options) {
2852
+ if (!webManagerFactories.createWatchtowerCapture) {
2853
+ this.log("Watchtower capture not available on this platform (web only).");
2854
+ return false;
2855
+ }
2856
+ if (!this.initialized || !this.config.apiKey) {
2857
+ this.log("Watchtower capture requires init() to be called first.");
2858
+ return false;
2859
+ }
2860
+ const opts = { ...this.watchtowerOptions, ...options };
2861
+ if (!opts.projectId) {
2862
+ this.log("Watchtower capture requires watchtower.projectId.");
2863
+ return false;
2864
+ }
2865
+ if (this.watchtowerCapture) {
2866
+ this.watchtowerCapture.stop();
2867
+ this.watchtowerCapture = null;
2868
+ }
2869
+ this.watchtowerCapture = webManagerFactories.createWatchtowerCapture({
2870
+ apiKey: this.config.apiKey,
2871
+ projectId: opts.projectId,
2872
+ endpoint: opts.endpoint || this.config.endpoint || DEFAULT_ENDPOINT3,
2873
+ appVersion: this.config.release || void 0,
2874
+ maxBatchSize: opts.maxBatchSize,
2875
+ flushInterval: opts.flushInterval,
2876
+ blockSelector: opts.blockSelector,
2877
+ maskSelector: opts.maskSelector,
2878
+ debug: this.config.debug
2879
+ });
2880
+ this.watchtowerCapture.start(this.analyticsManager?.getAutocaptureModule() ?? null);
2881
+ return true;
2882
+ }
2883
+ stopWatchtowerCapture() {
2884
+ if (!this.watchtowerCapture) return;
2885
+ this.watchtowerCapture.stop();
2886
+ this.watchtowerCapture = null;
2887
+ }
2752
2888
  // --- Performance (Tier 7) ---
2753
2889
  startTransaction(name, data) {
2754
2890
  if (!this.performanceManager) {
@@ -2900,6 +3036,7 @@ var SitePongClient = class {
2900
3036
  if (this.replayManager) {
2901
3037
  this.replayManager.setUser(null);
2902
3038
  }
3039
+ this.watchtowerCapture?.setDistinctId(null);
2903
3040
  }
2904
3041
  addBreadcrumb(breadcrumb) {
2905
3042
  this.log("Breadcrumb added:", breadcrumb);
@@ -3124,6 +3261,8 @@ var getDeviceSignals = sitepong.getDeviceSignals.bind(sitepong);
3124
3261
  var getFraudCheck = sitepong.getFraudCheck.bind(sitepong);
3125
3262
  var startReplay = sitepong.startReplay.bind(sitepong);
3126
3263
  var stopReplay = sitepong.stopReplay.bind(sitepong);
3264
+ var startWatchtowerCapture = sitepong.startWatchtowerCapture.bind(sitepong);
3265
+ var stopWatchtowerCapture = sitepong.stopWatchtowerCapture.bind(sitepong);
3127
3266
  var isReplayRecording = sitepong.isReplayRecording.bind(sitepong);
3128
3267
  var getReplaySessionId = sitepong.getReplaySessionId.bind(sitepong);
3129
3268
  var startTransaction = sitepong.startTransaction.bind(sitepong);
@@ -3237,7 +3376,9 @@ exports.startProfileSpan = startProfileSpan;
3237
3376
  exports.startReplay = startReplay;
3238
3377
  exports.startSpan = startSpan;
3239
3378
  exports.startTransaction = startTransaction;
3379
+ exports.startWatchtowerCapture = startWatchtowerCapture;
3240
3380
  exports.stopReplay = stopReplay;
3381
+ exports.stopWatchtowerCapture = stopWatchtowerCapture;
3241
3382
  exports.superlinkClient = superlinkClient;
3242
3383
  exports.superlinkIdentify = identify;
3243
3384
  exports.track = track;