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.
@@ -1,5 +1,4 @@
1
1
  import { createContext, useRef, useEffect, Component, useContext, useCallback, useState } from 'react';
2
- import 'web-vitals';
3
2
  import { jsx, jsxs } from 'react/jsx-runtime';
4
3
 
5
4
  // src/react/provider.tsx
@@ -522,6 +521,60 @@ function clearSession() {
522
521
  memorySessionTs = null;
523
522
  }
524
523
 
524
+ // src/analytics/utm.ts
525
+ var STORAGE_KEY2 = "sp_utm";
526
+ var UTM_KEYS = [
527
+ ["source", "utm_source"],
528
+ ["medium", "utm_medium"],
529
+ ["campaign", "utm_campaign"],
530
+ ["term", "utm_term"],
531
+ ["content", "utm_content"]
532
+ ];
533
+ function parseFromLocation() {
534
+ if (typeof window === "undefined" || !window.location || !window.location.search) return null;
535
+ let params;
536
+ try {
537
+ params = new URLSearchParams(window.location.search);
538
+ } catch {
539
+ return null;
540
+ }
541
+ const result = {};
542
+ let found = false;
543
+ for (const [key, queryKey] of UTM_KEYS) {
544
+ const value = params.get(queryKey);
545
+ if (value) {
546
+ result[key] = value;
547
+ found = true;
548
+ }
549
+ }
550
+ return found ? result : null;
551
+ }
552
+ function readStored() {
553
+ if (typeof sessionStorage === "undefined") return null;
554
+ try {
555
+ const raw = sessionStorage.getItem(STORAGE_KEY2);
556
+ if (!raw) return null;
557
+ const parsed = JSON.parse(raw);
558
+ return parsed && typeof parsed === "object" ? parsed : null;
559
+ } catch {
560
+ return null;
561
+ }
562
+ }
563
+ function writeStored(utm) {
564
+ if (typeof sessionStorage === "undefined") return;
565
+ try {
566
+ sessionStorage.setItem(STORAGE_KEY2, JSON.stringify(utm));
567
+ } catch {
568
+ }
569
+ }
570
+ function getSessionUtm() {
571
+ const stored = readStored();
572
+ if (stored) return stored;
573
+ const fresh = parseFromLocation();
574
+ if (fresh) writeStored(fresh);
575
+ return fresh;
576
+ }
577
+
525
578
  // src/analytics/autocapture.ts
526
579
  var DEFAULT_BLOCK_SELECTORS = [
527
580
  "[data-sp-no-capture]",
@@ -530,6 +583,7 @@ var DEFAULT_BLOCK_SELECTORS = [
530
583
  var MAX_TEXT_LENGTH = 255;
531
584
  var AutocaptureModule = class {
532
585
  constructor(config, callback) {
586
+ this.listeners = [];
533
587
  this.clickHandler = null;
534
588
  this.submitHandler = null;
535
589
  this.active = false;
@@ -544,6 +598,27 @@ var AutocaptureModule = class {
544
598
  };
545
599
  this.callback = callback;
546
600
  }
601
+ /**
602
+ * Register a secondary listener that receives the same events as the
603
+ * primary callback (e.g. Watchtower tap capture reusing the single click
604
+ * listener + selector resolution). Returns an unsubscribe function.
605
+ */
606
+ addListener(cb) {
607
+ this.listeners.push(cb);
608
+ return () => {
609
+ const idx = this.listeners.indexOf(cb);
610
+ if (idx >= 0) this.listeners.splice(idx, 1);
611
+ };
612
+ }
613
+ emit(event) {
614
+ this.callback(event);
615
+ for (const listener of this.listeners) {
616
+ try {
617
+ listener(event);
618
+ } catch {
619
+ }
620
+ }
621
+ }
547
622
  start() {
548
623
  if (this.active || typeof document === "undefined" || typeof document.addEventListener !== "function") return;
549
624
  this.active = true;
@@ -576,7 +651,7 @@ var AutocaptureModule = class {
576
651
  properties.$event_type = "click";
577
652
  properties.$x = e.clientX;
578
653
  properties.$y = e.clientY;
579
- this.callback({
654
+ this.emit({
580
655
  type: "click",
581
656
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
582
657
  properties
@@ -598,7 +673,7 @@ var AutocaptureModule = class {
598
673
  };
599
674
  const elemProps = this.getElementProperties(form);
600
675
  Object.assign(properties, elemProps);
601
- this.callback({
676
+ this.emit({
602
677
  type: "form_submit",
603
678
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
604
679
  properties
@@ -815,6 +890,14 @@ var AnalyticsManager = class {
815
890
  );
816
891
  this.autocaptureModule.start();
817
892
  }
893
+ /**
894
+ * The live AutocaptureModule (if click/form autocapture is enabled) —
895
+ * Watchtower tap capture attaches to it so there is only ONE document-level
896
+ * click listener and one selector/element resolution path.
897
+ */
898
+ getAutocaptureModule() {
899
+ return this.autocaptureModule;
900
+ }
818
901
  track(eventName, properties) {
819
902
  if (!this.config.enabled) return;
820
903
  const event = this.createEvent("track", { name: eventName, properties });
@@ -928,7 +1011,9 @@ var AnalyticsManager = class {
928
1011
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
929
1012
  url: typeof window !== "undefined" && window.location ? window.location.href : void 0,
930
1013
  referrer: typeof document !== "undefined" && typeof document.referrer === "string" ? document.referrer : void 0,
931
- userAgent: typeof navigator !== "undefined" ? navigator.userAgent : void 0
1014
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : void 0,
1015
+ utm: getSessionUtm() || void 0,
1016
+ appVersion: this.config.appVersion || void 0
932
1017
  };
933
1018
  }
934
1019
  enqueue(event) {
@@ -1626,7 +1711,7 @@ var DEFAULT_REMOTE_CONFIG = {
1626
1711
  };
1627
1712
 
1628
1713
  // src/remote-config/manager.ts
1629
- var STORAGE_KEY2 = "sitepong_remote_config";
1714
+ var STORAGE_KEY3 = "sitepong_remote_config";
1630
1715
  var STORAGE_TS_KEY = "sitepong_remote_config_ts";
1631
1716
  var RemoteConfigManager = class {
1632
1717
  constructor(options) {
@@ -1669,7 +1754,7 @@ var RemoteConfigManager = class {
1669
1754
  const storage = this.options.storage;
1670
1755
  if (!storage) return;
1671
1756
  try {
1672
- const cached = await storage.getItem(STORAGE_KEY2);
1757
+ const cached = await storage.getItem(STORAGE_KEY3);
1673
1758
  const cachedTs = await storage.getItem(STORAGE_TS_KEY);
1674
1759
  if (cached && cachedTs) {
1675
1760
  const age = Date.now() - parseInt(cachedTs, 10);
@@ -1730,7 +1815,7 @@ var RemoteConfigManager = class {
1730
1815
  const storage = this.options.storage;
1731
1816
  if (!storage) return;
1732
1817
  try {
1733
- await storage.setItem(STORAGE_KEY2, JSON.stringify(this.config));
1818
+ await storage.setItem(STORAGE_KEY3, JSON.stringify(this.config));
1734
1819
  await storage.setItem(STORAGE_TS_KEY, String(Date.now()));
1735
1820
  } catch {
1736
1821
  this.log("Failed to cache remote config");
@@ -1986,7 +2071,7 @@ function stripTrailingSlash(url) {
1986
2071
  var superlinkClient = new SuperLinkClient();
1987
2072
 
1988
2073
  // src/superlink/parse.ts
1989
- var UTM_KEYS = [
2074
+ var UTM_KEYS2 = [
1990
2075
  ["source", "utm_source"],
1991
2076
  ["medium", "utm_medium"],
1992
2077
  ["campaign", "utm_campaign"],
@@ -2002,7 +2087,7 @@ function parseUniversalLink(url) {
2002
2087
  }
2003
2088
  const params = parsed.searchParams;
2004
2089
  const utm = {};
2005
- for (const [key, queryKey] of UTM_KEYS) {
2090
+ for (const [key, queryKey] of UTM_KEYS2) {
2006
2091
  const value = params.get(queryKey);
2007
2092
  if (value) utm[key] = value;
2008
2093
  }
@@ -2069,13 +2154,13 @@ function getMatchedDeepLink() {
2069
2154
  }
2070
2155
 
2071
2156
  // src/superlink/web.ts
2072
- var STORAGE_KEY3 = "sp_superlink";
2157
+ var STORAGE_KEY4 = "sp_superlink";
2073
2158
  var captured = null;
2074
2159
  var didCapture = false;
2075
- function readStored() {
2160
+ function readStored2() {
2076
2161
  if (typeof sessionStorage === "undefined") return null;
2077
2162
  try {
2078
- const raw = sessionStorage.getItem(STORAGE_KEY3);
2163
+ const raw = sessionStorage.getItem(STORAGE_KEY4);
2079
2164
  if (!raw) return null;
2080
2165
  const parsed = JSON.parse(raw);
2081
2166
  return parsed && typeof parsed === "object" ? parsed : null;
@@ -2083,10 +2168,10 @@ function readStored() {
2083
2168
  return null;
2084
2169
  }
2085
2170
  }
2086
- function writeStored(link) {
2171
+ function writeStored2(link) {
2087
2172
  if (typeof sessionStorage === "undefined") return;
2088
2173
  try {
2089
- sessionStorage.setItem(STORAGE_KEY3, JSON.stringify(link));
2174
+ sessionStorage.setItem(STORAGE_KEY4, JSON.stringify(link));
2090
2175
  } catch {
2091
2176
  }
2092
2177
  }
@@ -2134,7 +2219,7 @@ function extractIdentityMetadata(url) {
2134
2219
  function captureWebDeepLink() {
2135
2220
  if (didCapture) return captured;
2136
2221
  didCapture = true;
2137
- const stored = readStored();
2222
+ const stored = readStored2();
2138
2223
  if (stored) {
2139
2224
  captured = stored;
2140
2225
  return captured;
@@ -2143,7 +2228,7 @@ function captureWebDeepLink() {
2143
2228
  const link = parseUniversalLink(window.location.href);
2144
2229
  if (link && hasPayload(link)) {
2145
2230
  captured = link;
2146
- writeStored(link);
2231
+ writeStored2(link);
2147
2232
  void superlinkClient.reportEvent({
2148
2233
  type: "opened",
2149
2234
  click_id: link.click_id,
@@ -2324,6 +2409,8 @@ var SitePongClient = class {
2324
2409
  this.databaseTracker = null;
2325
2410
  this.profiler = null;
2326
2411
  this.remoteConfigManager = null;
2412
+ this.watchtowerCapture = null;
2413
+ this.watchtowerOptions = void 0;
2327
2414
  this.envUnsubs = [];
2328
2415
  this.identifyHooks = [];
2329
2416
  this.config = {
@@ -2443,6 +2530,12 @@ var SitePongClient = class {
2443
2530
  } else if (config.replay?.enabled) {
2444
2531
  this.log("Replay requested but not available on this platform (web only).");
2445
2532
  }
2533
+ this.watchtowerOptions = config.watchtower;
2534
+ if (config.watchtower?.enabled && webManagerFactories.createWatchtowerCapture) {
2535
+ this.startWatchtowerCapture();
2536
+ } else if (config.watchtower?.enabled) {
2537
+ this.log("Watchtower capture requested but not available on this platform (web only).");
2538
+ }
2446
2539
  if (config.crons?.enabled) {
2447
2540
  this.cronManager = new CronMonitorManager({
2448
2541
  apiKey: config.apiKey,
@@ -2682,6 +2775,7 @@ var SitePongClient = class {
2682
2775
  this.log("identify hook failed:", err);
2683
2776
  }
2684
2777
  }
2778
+ this.watchtowerCapture?.setDistinctId(userId);
2685
2779
  if (!this.analyticsManager) {
2686
2780
  this.log("Analytics not enabled. Set analytics.enabled: true in init()");
2687
2781
  return;
@@ -2749,6 +2843,49 @@ var SitePongClient = class {
2749
2843
  getReplaySessionId() {
2750
2844
  return this.replayManager?.getSessionId() ?? null;
2751
2845
  }
2846
+ // --- Watchtower tap capture (web) ---
2847
+ /**
2848
+ * Start Watchtower web tap capture. Options merge over `watchtower` from
2849
+ * init(); `projectId` is required (either here or in init config). Reuses
2850
+ * the analytics AutocaptureModule's click listener when autocapture is on.
2851
+ */
2852
+ startWatchtowerCapture(options) {
2853
+ if (!webManagerFactories.createWatchtowerCapture) {
2854
+ this.log("Watchtower capture not available on this platform (web only).");
2855
+ return false;
2856
+ }
2857
+ if (!this.initialized || !this.config.apiKey) {
2858
+ this.log("Watchtower capture requires init() to be called first.");
2859
+ return false;
2860
+ }
2861
+ const opts = { ...this.watchtowerOptions, ...options };
2862
+ if (!opts.projectId) {
2863
+ this.log("Watchtower capture requires watchtower.projectId.");
2864
+ return false;
2865
+ }
2866
+ if (this.watchtowerCapture) {
2867
+ this.watchtowerCapture.stop();
2868
+ this.watchtowerCapture = null;
2869
+ }
2870
+ this.watchtowerCapture = webManagerFactories.createWatchtowerCapture({
2871
+ apiKey: this.config.apiKey,
2872
+ projectId: opts.projectId,
2873
+ endpoint: opts.endpoint || this.config.endpoint || DEFAULT_ENDPOINT3,
2874
+ appVersion: this.config.release || void 0,
2875
+ maxBatchSize: opts.maxBatchSize,
2876
+ flushInterval: opts.flushInterval,
2877
+ blockSelector: opts.blockSelector,
2878
+ maskSelector: opts.maskSelector,
2879
+ debug: this.config.debug
2880
+ });
2881
+ this.watchtowerCapture.start(this.analyticsManager?.getAutocaptureModule() ?? null);
2882
+ return true;
2883
+ }
2884
+ stopWatchtowerCapture() {
2885
+ if (!this.watchtowerCapture) return;
2886
+ this.watchtowerCapture.stop();
2887
+ this.watchtowerCapture = null;
2888
+ }
2752
2889
  // --- Performance (Tier 7) ---
2753
2890
  startTransaction(name, data) {
2754
2891
  if (!this.performanceManager) {
@@ -2900,6 +3037,7 @@ var SitePongClient = class {
2900
3037
  if (this.replayManager) {
2901
3038
  this.replayManager.setUser(null);
2902
3039
  }
3040
+ this.watchtowerCapture?.setDistinctId(null);
2903
3041
  }
2904
3042
  addBreadcrumb(breadcrumb) {
2905
3043
  this.log("Breadcrumb added:", breadcrumb);
@@ -3124,6 +3262,8 @@ var getDeviceSignals = sitepong.getDeviceSignals.bind(sitepong);
3124
3262
  var getFraudCheck = sitepong.getFraudCheck.bind(sitepong);
3125
3263
  var startReplay = sitepong.startReplay.bind(sitepong);
3126
3264
  var stopReplay = sitepong.stopReplay.bind(sitepong);
3265
+ var startWatchtowerCapture = sitepong.startWatchtowerCapture.bind(sitepong);
3266
+ var stopWatchtowerCapture = sitepong.stopWatchtowerCapture.bind(sitepong);
3127
3267
  var isReplayRecording = sitepong.isReplayRecording.bind(sitepong);
3128
3268
  var getReplaySessionId = sitepong.getReplaySessionId.bind(sitepong);
3129
3269
  var startTransaction = sitepong.startTransaction.bind(sitepong);
@@ -3524,6 +3664,6 @@ function useReplay() {
3524
3664
  return { recording, start, stop };
3525
3665
  }
3526
3666
 
3527
- export { DEFAULT_SUPERLINK_ENDPOINT, SitePongContext, SitePongErrorBoundary, SitePongProvider, SuperLinkClient, TracePropagator, addBreadcrumb, areFlagsReady, captureError, captureMessage, captureWebDeepLink, clearAnonymousId, clearUser, sitepong as client, completeFromScan, createTraceContext, cronCheckin, cronStart, cronWrap, dbTrack, dbTrackSync, endSpan, endTransaction, extractIdentityMetadata, extractTrace, flush, flushMetrics, flushProfiles, generateSpanId, generateTraceId, getAllFlags, getAnonymousId, getDbNPlusOnePatterns, getDbQueryCount, getDeepLink, getDeviceSignals, getFlag, getFraudCheck, getLatestProfile, getMatchedDeepLink, getProfiles, getRemoteConfig, getReplaySessionId, getVariant, getVariantPayload2 as getVariantPayload, getVisitorId, getWebVitals, group, identify2 as identify, init, initRN, initSuperLink, isInitialized, isRemoteConfigFeatureEnabled, isReplayRecording, metricDistribution, metricGauge, metricHistogram, metricIncrement, metricStartTimer, metricTime, onDeferredDeepLink, onRemoteConfigChange, parseUniversalLink, profile, propagateTrace, refreshFlags, registerIdentifyHook, registerWebManagerFactories, resetAnalytics, resetDbQueryCount, setAnonymousId, setContext, setCurrentScreen, setEnvironment, setRNGetCurrentScreen, setTags, setUser, startProfileSpan, startReplay, startSpan, startTransaction, stopReplay, superlinkClient, identify as superlinkIdentify, track, trackPageView, useAddBreadcrumb, useAllFlags, useCaptureException, useCaptureMessage, useErrorCapture, useExperiment, useFeatureFlag, useFeatureFlagPayload, useFraudCheck, useGroup, useIdentify, usePerformanceTransaction, useReplay, useSetUser, useSitePong, useTrack, useTrackPageView, useVisitorId, useWebVitals, waitForFlags, writeClipboardToken };
3667
+ export { DEFAULT_SUPERLINK_ENDPOINT, SitePongContext, SitePongErrorBoundary, SitePongProvider, SuperLinkClient, TracePropagator, addBreadcrumb, areFlagsReady, captureError, captureMessage, captureWebDeepLink, clearAnonymousId, clearUser, sitepong as client, completeFromScan, createTraceContext, cronCheckin, cronStart, cronWrap, dbTrack, dbTrackSync, endSpan, endTransaction, extractIdentityMetadata, extractTrace, flush, flushMetrics, flushProfiles, generateSpanId, generateTraceId, getAllFlags, getAnonymousId, getDbNPlusOnePatterns, getDbQueryCount, getDeepLink, getDeviceSignals, getFlag, getFraudCheck, getLatestProfile, getMatchedDeepLink, getProfiles, getRemoteConfig, getReplaySessionId, getVariant, getVariantPayload2 as getVariantPayload, getVisitorId, getWebVitals, group, identify2 as identify, init, initRN, initSuperLink, isInitialized, isRemoteConfigFeatureEnabled, isReplayRecording, metricDistribution, metricGauge, metricHistogram, metricIncrement, metricStartTimer, metricTime, onDeferredDeepLink, onRemoteConfigChange, parseUniversalLink, profile, propagateTrace, refreshFlags, registerIdentifyHook, registerWebManagerFactories, resetAnalytics, resetDbQueryCount, setAnonymousId, setContext, setCurrentScreen, setEnvironment, setRNGetCurrentScreen, setTags, setUser, startProfileSpan, startReplay, startSpan, startTransaction, startWatchtowerCapture, stopReplay, stopWatchtowerCapture, superlinkClient, identify as superlinkIdentify, track, trackPageView, useAddBreadcrumb, useAllFlags, useCaptureException, useCaptureMessage, useErrorCapture, useExperiment, useFeatureFlag, useFeatureFlagPayload, useFraudCheck, useGroup, useIdentify, usePerformanceTransaction, useReplay, useSetUser, useSitePong, useTrack, useTrackPageView, useVisitorId, useWebVitals, waitForFlags, writeClipboardToken };
3528
3668
  //# sourceMappingURL=index.mjs.map
3529
3669
  //# sourceMappingURL=index.mjs.map