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,9 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var react = require('react');
4
- require('web-vitals');
3
+ var React2 = require('react');
5
4
  var reactNative = require('react-native');
6
5
  var jsxRuntime = require('react/jsx-runtime');
6
+ var screenRecorder = require('@sitepong/screen-recorder');
7
+
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var React2__default = /*#__PURE__*/_interopDefault(React2);
7
11
 
8
12
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
13
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
@@ -610,6 +614,60 @@ function clearSession() {
610
614
  memorySessionTs = null;
611
615
  }
612
616
 
617
+ // src/analytics/utm.ts
618
+ var STORAGE_KEY2 = "sp_utm";
619
+ var UTM_KEYS = [
620
+ ["source", "utm_source"],
621
+ ["medium", "utm_medium"],
622
+ ["campaign", "utm_campaign"],
623
+ ["term", "utm_term"],
624
+ ["content", "utm_content"]
625
+ ];
626
+ function parseFromLocation() {
627
+ if (typeof window === "undefined" || !window.location || !window.location.search) return null;
628
+ let params;
629
+ try {
630
+ params = new URLSearchParams(window.location.search);
631
+ } catch {
632
+ return null;
633
+ }
634
+ const result = {};
635
+ let found = false;
636
+ for (const [key, queryKey] of UTM_KEYS) {
637
+ const value = params.get(queryKey);
638
+ if (value) {
639
+ result[key] = value;
640
+ found = true;
641
+ }
642
+ }
643
+ return found ? result : null;
644
+ }
645
+ function readStored() {
646
+ if (typeof sessionStorage === "undefined") return null;
647
+ try {
648
+ const raw = sessionStorage.getItem(STORAGE_KEY2);
649
+ if (!raw) return null;
650
+ const parsed = JSON.parse(raw);
651
+ return parsed && typeof parsed === "object" ? parsed : null;
652
+ } catch {
653
+ return null;
654
+ }
655
+ }
656
+ function writeStored(utm) {
657
+ if (typeof sessionStorage === "undefined") return;
658
+ try {
659
+ sessionStorage.setItem(STORAGE_KEY2, JSON.stringify(utm));
660
+ } catch {
661
+ }
662
+ }
663
+ function getSessionUtm() {
664
+ const stored = readStored();
665
+ if (stored) return stored;
666
+ const fresh = parseFromLocation();
667
+ if (fresh) writeStored(fresh);
668
+ return fresh;
669
+ }
670
+
613
671
  // src/analytics/autocapture.ts
614
672
  var DEFAULT_BLOCK_SELECTORS = [
615
673
  "[data-sp-no-capture]",
@@ -618,6 +676,7 @@ var DEFAULT_BLOCK_SELECTORS = [
618
676
  var MAX_TEXT_LENGTH = 255;
619
677
  var AutocaptureModule = class {
620
678
  constructor(config, callback) {
679
+ this.listeners = [];
621
680
  this.clickHandler = null;
622
681
  this.submitHandler = null;
623
682
  this.active = false;
@@ -632,6 +691,27 @@ var AutocaptureModule = class {
632
691
  };
633
692
  this.callback = callback;
634
693
  }
694
+ /**
695
+ * Register a secondary listener that receives the same events as the
696
+ * primary callback (e.g. Watchtower tap capture reusing the single click
697
+ * listener + selector resolution). Returns an unsubscribe function.
698
+ */
699
+ addListener(cb) {
700
+ this.listeners.push(cb);
701
+ return () => {
702
+ const idx = this.listeners.indexOf(cb);
703
+ if (idx >= 0) this.listeners.splice(idx, 1);
704
+ };
705
+ }
706
+ emit(event) {
707
+ this.callback(event);
708
+ for (const listener of this.listeners) {
709
+ try {
710
+ listener(event);
711
+ } catch {
712
+ }
713
+ }
714
+ }
635
715
  start() {
636
716
  if (this.active || typeof document === "undefined" || typeof document.addEventListener !== "function") return;
637
717
  this.active = true;
@@ -664,7 +744,7 @@ var AutocaptureModule = class {
664
744
  properties.$event_type = "click";
665
745
  properties.$x = e.clientX;
666
746
  properties.$y = e.clientY;
667
- this.callback({
747
+ this.emit({
668
748
  type: "click",
669
749
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
670
750
  properties
@@ -686,7 +766,7 @@ var AutocaptureModule = class {
686
766
  };
687
767
  const elemProps = this.getElementProperties(form);
688
768
  Object.assign(properties, elemProps);
689
- this.callback({
769
+ this.emit({
690
770
  type: "form_submit",
691
771
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
692
772
  properties
@@ -903,6 +983,14 @@ var AnalyticsManager = class {
903
983
  );
904
984
  this.autocaptureModule.start();
905
985
  }
986
+ /**
987
+ * The live AutocaptureModule (if click/form autocapture is enabled) —
988
+ * Watchtower tap capture attaches to it so there is only ONE document-level
989
+ * click listener and one selector/element resolution path.
990
+ */
991
+ getAutocaptureModule() {
992
+ return this.autocaptureModule;
993
+ }
906
994
  track(eventName, properties) {
907
995
  if (!this.config.enabled) return;
908
996
  const event = this.createEvent("track", { name: eventName, properties });
@@ -1016,7 +1104,9 @@ var AnalyticsManager = class {
1016
1104
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1017
1105
  url: typeof window !== "undefined" && window.location ? window.location.href : void 0,
1018
1106
  referrer: typeof document !== "undefined" && typeof document.referrer === "string" ? document.referrer : void 0,
1019
- userAgent: typeof navigator !== "undefined" ? navigator.userAgent : void 0
1107
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : void 0,
1108
+ utm: getSessionUtm() || void 0,
1109
+ appVersion: this.config.appVersion || void 0
1020
1110
  };
1021
1111
  }
1022
1112
  enqueue(event) {
@@ -1714,7 +1804,7 @@ var DEFAULT_REMOTE_CONFIG = {
1714
1804
  };
1715
1805
 
1716
1806
  // src/remote-config/manager.ts
1717
- var STORAGE_KEY2 = "sitepong_remote_config";
1807
+ var STORAGE_KEY3 = "sitepong_remote_config";
1718
1808
  var STORAGE_TS_KEY = "sitepong_remote_config_ts";
1719
1809
  var RemoteConfigManager = class {
1720
1810
  constructor(options) {
@@ -1757,7 +1847,7 @@ var RemoteConfigManager = class {
1757
1847
  const storage = this.options.storage;
1758
1848
  if (!storage) return;
1759
1849
  try {
1760
- const cached = await storage.getItem(STORAGE_KEY2);
1850
+ const cached = await storage.getItem(STORAGE_KEY3);
1761
1851
  const cachedTs = await storage.getItem(STORAGE_TS_KEY);
1762
1852
  if (cached && cachedTs) {
1763
1853
  const age = Date.now() - parseInt(cachedTs, 10);
@@ -1818,7 +1908,7 @@ var RemoteConfigManager = class {
1818
1908
  const storage = this.options.storage;
1819
1909
  if (!storage) return;
1820
1910
  try {
1821
- await storage.setItem(STORAGE_KEY2, JSON.stringify(this.config));
1911
+ await storage.setItem(STORAGE_KEY3, JSON.stringify(this.config));
1822
1912
  await storage.setItem(STORAGE_TS_KEY, String(Date.now()));
1823
1913
  } catch {
1824
1914
  this.log("Failed to cache remote config");
@@ -1930,7 +2020,7 @@ function stripTrailingSlash(url) {
1930
2020
  var superlinkClient = new SuperLinkClient();
1931
2021
 
1932
2022
  // src/superlink/parse.ts
1933
- var UTM_KEYS = [
2023
+ var UTM_KEYS2 = [
1934
2024
  ["source", "utm_source"],
1935
2025
  ["medium", "utm_medium"],
1936
2026
  ["campaign", "utm_campaign"],
@@ -1946,7 +2036,7 @@ function parseUniversalLink(url) {
1946
2036
  }
1947
2037
  const params = parsed.searchParams;
1948
2038
  const utm = {};
1949
- for (const [key, queryKey] of UTM_KEYS) {
2039
+ for (const [key, queryKey] of UTM_KEYS2) {
1950
2040
  const value = params.get(queryKey);
1951
2041
  if (value) utm[key] = value;
1952
2042
  }
@@ -2170,6 +2260,8 @@ var SitePongClient = class {
2170
2260
  this.databaseTracker = null;
2171
2261
  this.profiler = null;
2172
2262
  this.remoteConfigManager = null;
2263
+ this.watchtowerCapture = null;
2264
+ this.watchtowerOptions = void 0;
2173
2265
  this.envUnsubs = [];
2174
2266
  this.identifyHooks = [];
2175
2267
  this.config = {
@@ -2289,6 +2381,12 @@ var SitePongClient = class {
2289
2381
  } else if (config.replay?.enabled) {
2290
2382
  this.log("Replay requested but not available on this platform (web only).");
2291
2383
  }
2384
+ this.watchtowerOptions = config.watchtower;
2385
+ if (config.watchtower?.enabled && webManagerFactories.createWatchtowerCapture) {
2386
+ this.startWatchtowerCapture();
2387
+ } else if (config.watchtower?.enabled) {
2388
+ this.log("Watchtower capture requested but not available on this platform (web only).");
2389
+ }
2292
2390
  if (config.crons?.enabled) {
2293
2391
  this.cronManager = new CronMonitorManager({
2294
2392
  apiKey: config.apiKey,
@@ -2528,6 +2626,7 @@ var SitePongClient = class {
2528
2626
  this.log("identify hook failed:", err);
2529
2627
  }
2530
2628
  }
2629
+ this.watchtowerCapture?.setDistinctId(userId);
2531
2630
  if (!this.analyticsManager) {
2532
2631
  this.log("Analytics not enabled. Set analytics.enabled: true in init()");
2533
2632
  return;
@@ -2595,6 +2694,49 @@ var SitePongClient = class {
2595
2694
  getReplaySessionId() {
2596
2695
  return this.replayManager?.getSessionId() ?? null;
2597
2696
  }
2697
+ // --- Watchtower tap capture (web) ---
2698
+ /**
2699
+ * Start Watchtower web tap capture. Options merge over `watchtower` from
2700
+ * init(); `projectId` is required (either here or in init config). Reuses
2701
+ * the analytics AutocaptureModule's click listener when autocapture is on.
2702
+ */
2703
+ startWatchtowerCapture(options) {
2704
+ if (!webManagerFactories.createWatchtowerCapture) {
2705
+ this.log("Watchtower capture not available on this platform (web only).");
2706
+ return false;
2707
+ }
2708
+ if (!this.initialized || !this.config.apiKey) {
2709
+ this.log("Watchtower capture requires init() to be called first.");
2710
+ return false;
2711
+ }
2712
+ const opts = { ...this.watchtowerOptions, ...options };
2713
+ if (!opts.projectId) {
2714
+ this.log("Watchtower capture requires watchtower.projectId.");
2715
+ return false;
2716
+ }
2717
+ if (this.watchtowerCapture) {
2718
+ this.watchtowerCapture.stop();
2719
+ this.watchtowerCapture = null;
2720
+ }
2721
+ this.watchtowerCapture = webManagerFactories.createWatchtowerCapture({
2722
+ apiKey: this.config.apiKey,
2723
+ projectId: opts.projectId,
2724
+ endpoint: opts.endpoint || this.config.endpoint || DEFAULT_ENDPOINT3,
2725
+ appVersion: this.config.release || void 0,
2726
+ maxBatchSize: opts.maxBatchSize,
2727
+ flushInterval: opts.flushInterval,
2728
+ blockSelector: opts.blockSelector,
2729
+ maskSelector: opts.maskSelector,
2730
+ debug: this.config.debug
2731
+ });
2732
+ this.watchtowerCapture.start(this.analyticsManager?.getAutocaptureModule() ?? null);
2733
+ return true;
2734
+ }
2735
+ stopWatchtowerCapture() {
2736
+ if (!this.watchtowerCapture) return;
2737
+ this.watchtowerCapture.stop();
2738
+ this.watchtowerCapture = null;
2739
+ }
2598
2740
  // --- Performance (Tier 7) ---
2599
2741
  startTransaction(name, data) {
2600
2742
  if (!this.performanceManager) {
@@ -2746,6 +2888,7 @@ var SitePongClient = class {
2746
2888
  if (this.replayManager) {
2747
2889
  this.replayManager.setUser(null);
2748
2890
  }
2891
+ this.watchtowerCapture?.setDistinctId(null);
2749
2892
  }
2750
2893
  addBreadcrumb(breadcrumb) {
2751
2894
  this.log("Breadcrumb added:", breadcrumb);
@@ -2970,6 +3113,8 @@ sitepong.getDeviceSignals.bind(sitepong);
2970
3113
  sitepong.getFraudCheck.bind(sitepong);
2971
3114
  sitepong.startReplay.bind(sitepong);
2972
3115
  sitepong.stopReplay.bind(sitepong);
3116
+ sitepong.startWatchtowerCapture.bind(sitepong);
3117
+ sitepong.stopWatchtowerCapture.bind(sitepong);
2973
3118
  sitepong.isReplayRecording.bind(sitepong);
2974
3119
  sitepong.getReplaySessionId.bind(sitepong);
2975
3120
  sitepong.startTransaction.bind(sitepong);
@@ -3480,7 +3625,7 @@ function endLiveActivity(activityType, activityId) {
3480
3625
  activity_id: activityId
3481
3626
  });
3482
3627
  }
3483
- var SitePongRNContext = react.createContext({
3628
+ var SitePongRNContext = React2.createContext({
3484
3629
  isInitialized: false,
3485
3630
  performanceManager: null
3486
3631
  });
@@ -3499,10 +3644,10 @@ function SitePongRNProvider({
3499
3644
  pushUserId,
3500
3645
  children
3501
3646
  }) {
3502
- const initialized = react.useRef(false);
3503
- const [sdkReady, setSdkReady] = react.useState(false);
3504
- const performanceManagerRef = react.useRef(null);
3505
- react.useEffect(() => {
3647
+ const initialized = React2.useRef(false);
3648
+ const [sdkReady, setSdkReady] = React2.useState(false);
3649
+ const performanceManagerRef = React2.useRef(null);
3650
+ React2.useEffect(() => {
3506
3651
  if (initialized.current) return;
3507
3652
  const teardowns = [];
3508
3653
  const deviceInfo = collectDeviceInfo();
@@ -3571,11 +3716,11 @@ function SitePongRNProvider({
3571
3716
  return /* @__PURE__ */ jsxRuntime.jsx(SitePongRNContext.Provider, { value, children });
3572
3717
  }
3573
3718
  function useSitePongRNContext() {
3574
- return react.useContext(SitePongRNContext);
3719
+ return React2.useContext(SitePongRNContext);
3575
3720
  }
3576
3721
  function useScreenTrack(screenName) {
3577
- const { performanceManager } = react.useContext(SitePongRNContext);
3578
- react.useEffect(() => {
3722
+ const { performanceManager } = React2.useContext(SitePongRNContext);
3723
+ React2.useEffect(() => {
3579
3724
  if (!performanceManager) return;
3580
3725
  performanceManager.startScreenRender(screenName);
3581
3726
  return () => {
@@ -3584,8 +3729,8 @@ function useScreenTrack(screenName) {
3584
3729
  }, [screenName, performanceManager]);
3585
3730
  }
3586
3731
  function useAppState() {
3587
- const [appState, setAppState] = react.useState("active");
3588
- react.useEffect(() => {
3732
+ const [appState, setAppState] = React2.useState("active");
3733
+ React2.useEffect(() => {
3589
3734
  setAppState(reactNative.AppState.currentState);
3590
3735
  const subscription = reactNative.AppState.addEventListener("change", (nextState) => {
3591
3736
  setAppState(nextState);
@@ -3595,8 +3740,8 @@ function useAppState() {
3595
3740
  return appState;
3596
3741
  }
3597
3742
  function useRemoteConfig() {
3598
- const [config, setConfig] = react.useState(() => getRemoteConfig());
3599
- react.useEffect(() => {
3743
+ const [config, setConfig] = React2.useState(() => getRemoteConfig());
3744
+ React2.useEffect(() => {
3600
3745
  setConfig(getRemoteConfig());
3601
3746
  const unsubscribe = onRemoteConfigChange((newConfig) => {
3602
3747
  setConfig(newConfig);
@@ -3606,10 +3751,10 @@ function useRemoteConfig() {
3606
3751
  return config;
3607
3752
  }
3608
3753
  function useRNPerformance(screenName) {
3609
- const { performanceManager } = react.useContext(SitePongRNContext);
3610
- const [duration, setDuration] = react.useState(null);
3611
- const startTimeRef = react.useRef(Date.now());
3612
- react.useEffect(() => {
3754
+ const { performanceManager } = React2.useContext(SitePongRNContext);
3755
+ const [duration, setDuration] = React2.useState(null);
3756
+ const startTimeRef = React2.useRef(Date.now());
3757
+ React2.useEffect(() => {
3613
3758
  startTimeRef.current = Date.now();
3614
3759
  const handle = requestAnimationFrame(() => {
3615
3760
  const d = Date.now() - startTimeRef.current;
@@ -3623,6 +3768,66 @@ function useRNPerformance(screenName) {
3623
3768
  }, [screenName, performanceManager]);
3624
3769
  return { duration };
3625
3770
  }
3771
+ var started = false;
3772
+ function startWatchtower(config) {
3773
+ if (started) return;
3774
+ screenRecorder.watchtowerStart({
3775
+ apiKey: config.apiKey,
3776
+ projectId: config.projectId,
3777
+ endpoint: config.endpoint,
3778
+ sampleRate: config.sampleRate ?? 0.1,
3779
+ platform: config.platform ?? "react-native-ios"
3780
+ });
3781
+ started = true;
3782
+ if (config.distinctId) setWatchtowerUser(config.distinctId);
3783
+ }
3784
+ function setWatchtowerUser(distinctId) {
3785
+ try {
3786
+ screenRecorder.watchtowerSetUser(distinctId);
3787
+ } catch {
3788
+ }
3789
+ }
3790
+ function getWatchtowerSessionId() {
3791
+ try {
3792
+ return screenRecorder.watchtowerGetSessionId();
3793
+ } catch {
3794
+ return null;
3795
+ }
3796
+ }
3797
+ function stopWatchtower() {
3798
+ if (!started) return;
3799
+ screenRecorder.watchtowerStop();
3800
+ started = false;
3801
+ }
3802
+ function subscribeNavigationToWatchtower(navigationRef) {
3803
+ const unsubscribe = createNavigationTracker(navigationRef, {
3804
+ screenNameFilter: (name) => {
3805
+ try {
3806
+ screenRecorder.watchtowerSetScreen(name);
3807
+ } catch {
3808
+ }
3809
+ return name;
3810
+ }
3811
+ });
3812
+ try {
3813
+ const route = navigationRef.getCurrentRoute?.();
3814
+ if (route?.name) screenRecorder.watchtowerSetScreen(route.name);
3815
+ } catch {
3816
+ }
3817
+ return unsubscribe;
3818
+ }
3819
+ function WatchtowerProvider(props) {
3820
+ const { config, navigationRef, children } = props;
3821
+ React2__default.default.useEffect(() => {
3822
+ startWatchtower(config);
3823
+ const unsubscribe = subscribeNavigationToWatchtower(navigationRef);
3824
+ return () => {
3825
+ unsubscribe();
3826
+ stopWatchtower();
3827
+ };
3828
+ }, []);
3829
+ return React2__default.default.createElement(React2__default.default.Fragment, null, children);
3830
+ }
3626
3831
 
3627
3832
  // src/react-native/sqlite-tracker.ts
3628
3833
  function now() {
@@ -3790,7 +3995,7 @@ function stopWidgetSync() {
3790
3995
  syncTimer = null;
3791
3996
  }
3792
3997
  }
3793
- var started = false;
3998
+ var started2 = false;
3794
3999
  function platform() {
3795
4000
  if (reactNative.Platform.OS === "ios") return "ios";
3796
4001
  if (reactNative.Platform.OS === "android") return "android";
@@ -3878,8 +4083,8 @@ function hasSuperLinkToken(value) {
3878
4083
  }
3879
4084
  async function initSuperLinkRN(config = {}, identityOverride) {
3880
4085
  superlinkClient.configure(config);
3881
- if (started) return getMatchedDeepLink();
3882
- started = true;
4086
+ if (started2) return getMatchedDeepLink();
4087
+ started2 = true;
3883
4088
  const fingerprint = collectFingerprint();
3884
4089
  const [referrer, clipboardToken] = await Promise.all([
3885
4090
  readPlayInstallReferrer(),
@@ -3975,6 +4180,7 @@ function setRNStorage(storage) {
3975
4180
  exports.RNAutocaptureModule = RNAutocaptureModule;
3976
4181
  exports.RNPerformanceManager = RNPerformanceManager;
3977
4182
  exports.SitePongRNProvider = SitePongRNProvider;
4183
+ exports.WatchtowerProvider = WatchtowerProvider;
3978
4184
  exports.addBreadcrumb = addBreadcrumb;
3979
4185
  exports.areFlagsReady = areFlagsReady;
3980
4186
  exports.captureError = captureError;
@@ -4000,6 +4206,7 @@ exports.getMatchedDeepLink = getMatchedDeepLink;
4000
4206
  exports.getRemoteConfig = getRemoteConfig;
4001
4207
  exports.getVariant = getVariant;
4002
4208
  exports.getVariantPayload = getVariantPayload2;
4209
+ exports.getWatchtowerSessionId = getWatchtowerSessionId;
4003
4210
  exports.group = group;
4004
4211
  exports.handleUniversalLink = handleUniversalLink;
4005
4212
  exports.identify = identify2;
@@ -4025,11 +4232,15 @@ exports.setCurrentScreen = setCurrentScreen;
4025
4232
  exports.setRNStorage = setRNStorage;
4026
4233
  exports.setTags = setTags;
4027
4234
  exports.setUser = setUser;
4235
+ exports.setWatchtowerUser = setWatchtowerUser;
4028
4236
  exports.setupNetworkInterception = setupNetworkInterception;
4029
4237
  exports.setupRNErrorHandler = setupRNErrorHandler;
4030
4238
  exports.startScreenRecording = startScreenRecording;
4239
+ exports.startWatchtower = startWatchtower;
4031
4240
  exports.stopScreenRecording = stopScreenRecording;
4241
+ exports.stopWatchtower = stopWatchtower;
4032
4242
  exports.stopWidgetSync = stopWidgetSync;
4243
+ exports.subscribeNavigationToWatchtower = subscribeNavigationToWatchtower;
4033
4244
  exports.superlinkIdentify = identify3;
4034
4245
  exports.syncWidgets = syncWidgets;
4035
4246
  exports.track = track;