@shware/analytics 2.17.2 → 2.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/hooks/{use-screen-view-analytics.cjs → use-app-analytics.cjs} +30 -31
  2. package/dist/hooks/use-app-analytics.cjs.map +1 -0
  3. package/dist/hooks/use-app-analytics.d.cts +3 -0
  4. package/dist/hooks/use-app-analytics.d.ts +3 -0
  5. package/dist/hooks/use-app-analytics.mjs +50 -0
  6. package/dist/hooks/use-app-analytics.mjs.map +1 -0
  7. package/dist/hooks/use-outbound-click-analytics.cjs +58 -0
  8. package/dist/hooks/use-outbound-click-analytics.cjs.map +1 -0
  9. package/dist/hooks/use-outbound-click-analytics.d.cts +7 -0
  10. package/dist/hooks/use-outbound-click-analytics.d.ts +7 -0
  11. package/dist/hooks/use-outbound-click-analytics.mjs +33 -0
  12. package/dist/hooks/use-outbound-click-analytics.mjs.map +1 -0
  13. package/dist/hooks/use-web-analytics.cjs +128 -0
  14. package/dist/hooks/use-web-analytics.cjs.map +1 -0
  15. package/dist/hooks/{use-web-session-analytics.d.cts → use-web-analytics.d.cts} +2 -2
  16. package/dist/hooks/{use-web-session-analytics.d.ts → use-web-analytics.d.ts} +2 -2
  17. package/dist/hooks/use-web-analytics.mjs +103 -0
  18. package/dist/hooks/use-web-analytics.mjs.map +1 -0
  19. package/dist/native/index.cjs +3 -6
  20. package/dist/native/index.cjs.map +1 -1
  21. package/dist/native/index.d.cts +1 -2
  22. package/dist/native/index.d.ts +1 -2
  23. package/dist/native/index.mjs +2 -4
  24. package/dist/native/index.mjs.map +1 -1
  25. package/dist/next/index.cjs +4 -4
  26. package/dist/next/index.cjs.map +1 -1
  27. package/dist/next/index.mjs +4 -4
  28. package/dist/next/index.mjs.map +1 -1
  29. package/dist/react-router/index.cjs +4 -4
  30. package/dist/react-router/index.cjs.map +1 -1
  31. package/dist/react-router/index.mjs +4 -4
  32. package/dist/react-router/index.mjs.map +1 -1
  33. package/dist/setup/session.cjs +77 -22
  34. package/dist/setup/session.cjs.map +1 -1
  35. package/dist/setup/session.d.cts +27 -9
  36. package/dist/setup/session.d.ts +27 -9
  37. package/dist/setup/session.mjs +76 -18
  38. package/dist/setup/session.mjs.map +1 -1
  39. package/dist/track/gtag.cjs.map +1 -1
  40. package/dist/track/gtag.d.cts +1 -2
  41. package/dist/track/gtag.d.ts +1 -2
  42. package/dist/track/gtag.mjs.map +1 -1
  43. package/dist/track/index.cjs +6 -8
  44. package/dist/track/index.cjs.map +1 -1
  45. package/dist/track/index.mjs +7 -14
  46. package/dist/track/index.mjs.map +1 -1
  47. package/package.json +3 -3
  48. package/dist/hooks/use-app-session-analytics.cjs +0 -68
  49. package/dist/hooks/use-app-session-analytics.cjs.map +0 -1
  50. package/dist/hooks/use-app-session-analytics.d.cts +0 -3
  51. package/dist/hooks/use-app-session-analytics.d.ts +0 -3
  52. package/dist/hooks/use-app-session-analytics.mjs +0 -43
  53. package/dist/hooks/use-app-session-analytics.mjs.map +0 -1
  54. package/dist/hooks/use-page-view-analytics.cjs +0 -82
  55. package/dist/hooks/use-page-view-analytics.cjs.map +0 -1
  56. package/dist/hooks/use-page-view-analytics.d.cts +0 -3
  57. package/dist/hooks/use-page-view-analytics.d.ts +0 -3
  58. package/dist/hooks/use-page-view-analytics.mjs +0 -57
  59. package/dist/hooks/use-page-view-analytics.mjs.map +0 -1
  60. package/dist/hooks/use-screen-view-analytics.cjs.map +0 -1
  61. package/dist/hooks/use-screen-view-analytics.d.cts +0 -3
  62. package/dist/hooks/use-screen-view-analytics.d.ts +0 -3
  63. package/dist/hooks/use-screen-view-analytics.mjs +0 -51
  64. package/dist/hooks/use-screen-view-analytics.mjs.map +0 -1
  65. package/dist/hooks/use-web-session-analytics.cjs +0 -137
  66. package/dist/hooks/use-web-session-analytics.cjs.map +0 -1
  67. package/dist/hooks/use-web-session-analytics.mjs +0 -112
  68. package/dist/hooks/use-web-session-analytics.mjs.map +0 -1
@@ -17,60 +17,59 @@ var __copyProps = (to, from, except, desc) => {
17
17
  };
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
 
20
- // src/hooks/use-screen-view-analytics.ts
21
- var use_screen_view_analytics_exports = {};
22
- __export(use_screen_view_analytics_exports, {
23
- useScreenViewAnalytics: () => useScreenViewAnalytics
20
+ // src/hooks/use-app-analytics.ts
21
+ var use_app_analytics_exports = {};
22
+ __export(use_app_analytics_exports, {
23
+ useAppAnalytics: () => useAppAnalytics
24
24
  });
25
- module.exports = __toCommonJS(use_screen_view_analytics_exports);
25
+ module.exports = __toCommonJS(use_app_analytics_exports);
26
26
  var import_expo_router = require("expo-router");
27
27
  var import_react = require("react");
28
28
  var import_react_native = require("react-native");
29
29
  var import_setup = require("../setup/index.cjs");
30
+ var import_session = require("../setup/session.cjs");
30
31
  var import_track = require("../track/index.cjs");
31
32
  var import_use_previous = require("./use-previous.cjs");
32
- function useScreenViewAnalytics() {
33
+ function sendFirstOpen(pathname) {
34
+ const key = "first_open_time";
35
+ if (import_setup.config.storage.getItem(key)) return;
36
+ const properties = { screen_name: pathname, screen_class: pathname };
37
+ (0, import_track.track)("first_open", properties, { enableThirdPartyTracking: false });
38
+ import_setup.config.storage.setItem(key, (/* @__PURE__ */ new Date()).toISOString());
39
+ }
40
+ function sendUserEngagement() {
41
+ const engagement_time_msec = import_session.session.flush();
42
+ if (engagement_time_msec <= 0) return;
43
+ (0, import_track.track)("user_engagement", { engagement_time_msec }, { enableThirdPartyTracking: false });
44
+ }
45
+ function useAppAnalytics() {
33
46
  const pathname = (0, import_expo_router.usePathname)();
34
47
  const prevPathname = (0, import_use_previous.usePrevious)(pathname);
35
- const session = (0, import_react.useRef)({ start: performance.now(), total: 0, isActive: true });
36
48
  (0, import_react.useEffect)(() => {
49
+ sendFirstOpen(pathname);
50
+ (0, import_track.track)("session_start", void 0, { enableThirdPartyTracking: false });
37
51
  const subscription = import_react_native.AppState.addEventListener("change", (state) => {
38
- if (state === "active" && !session.current.isActive) {
39
- session.current.start = performance.now();
40
- session.current.isActive = true;
41
- } else if (state !== "active" && session.current.isActive) {
42
- session.current.total += (performance.now() - session.current.start) / 1e3;
43
- session.current.isActive = false;
52
+ import_session.session.updateAccumulator();
53
+ if (state === "active" && !import_session.session.isActive()) {
54
+ import_session.session.updateActive(true);
55
+ } else if (state !== "active" && import_session.session.isActive()) {
56
+ import_session.session.updateActive(false);
57
+ sendUserEngagement();
44
58
  }
45
59
  });
46
60
  return () => subscription.remove();
47
61
  }, []);
48
62
  (0, import_react.useEffect)(() => {
49
- const key = "first_open_time";
50
- if (import_setup.config.storage.getItem(key)) return;
51
- const properties = { screen_name: pathname, screen_class: pathname };
52
- (0, import_track.track)("first_open", properties, { enableThirdPartyTracking: false });
53
- import_setup.config.storage.setItem(key, (/* @__PURE__ */ new Date()).toISOString());
54
- }, []);
55
- (0, import_react.useEffect)(() => {
56
- if (!prevPathname) {
57
- session.current = { start: performance.now(), total: 0, isActive: true };
58
- }
59
- let duration = session.current.total;
60
- if (session.current.isActive) {
61
- duration += (performance.now() - session.current.start) / 1e3;
62
- }
63
63
  (0, import_track.track)("screen_view", {
64
64
  screen_name: pathname,
65
65
  screen_class: pathname,
66
66
  previous_screen_class: prevPathname ?? void 0,
67
- previous_screen_class_duration: prevPathname ? Number(duration.toFixed(2)) : void 0
67
+ engagement_time_msec: prevPathname ? import_session.session.flush() : void 0
68
68
  });
69
- session.current = { start: performance.now(), total: 0, isActive: true };
70
69
  }, [pathname]);
71
70
  }
72
71
  // Annotate the CommonJS export names for ESM import in node:
73
72
  0 && (module.exports = {
74
- useScreenViewAnalytics
73
+ useAppAnalytics
75
74
  });
76
- //# sourceMappingURL=use-screen-view-analytics.cjs.map
75
+ //# sourceMappingURL=use-app-analytics.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/use-app-analytics.ts"],"sourcesContent":["import { usePathname } from 'expo-router';\nimport { useEffect } from 'react';\nimport { AppState } from 'react-native';\nimport { config } from '../setup/index';\nimport { session } from '../setup/session';\nimport { track } from '../track/index';\nimport { usePrevious } from './use-previous';\n\nfunction sendFirstOpen(pathname: string) {\n const key = 'first_open_time';\n if (config.storage.getItem(key)) return;\n const properties = { screen_name: pathname, screen_class: pathname };\n track('first_open', properties, { enableThirdPartyTracking: false });\n config.storage.setItem(key, new Date().toISOString());\n}\n\nfunction sendUserEngagement() {\n const engagement_time_msec = session.flush();\n if (engagement_time_msec <= 0) return;\n track('user_engagement', { engagement_time_msec }, { enableThirdPartyTracking: false });\n}\n\nexport function useAppAnalytics() {\n const pathname = usePathname();\n const prevPathname = usePrevious(pathname);\n\n useEffect(() => {\n sendFirstOpen(pathname);\n track('session_start', undefined, { enableThirdPartyTracking: false });\n\n const subscription = AppState.addEventListener('change', (state) => {\n session.updateAccumulator();\n // when returning to the foreground from the background\n if (state === 'active' && !session.isActive()) {\n session.updateActive(true);\n }\n // when entering the background\n else if (state !== 'active' && session.isActive()) {\n session.updateActive(false);\n sendUserEngagement();\n }\n });\n\n return () => subscription.remove();\n }, []);\n\n // when the screen is switched, the engagement time of the previous screen is recorded\n useEffect(() => {\n track('screen_view', {\n screen_name: pathname,\n screen_class: pathname,\n previous_screen_class: prevPathname ?? undefined,\n engagement_time_msec: prevPathname ? session.flush() : undefined,\n });\n }, [pathname]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAA4B;AAC5B,mBAA0B;AAC1B,0BAAyB;AACzB,mBAAuB;AACvB,qBAAwB;AACxB,mBAAsB;AACtB,0BAA4B;AAE5B,SAAS,cAAc,UAAkB;AACvC,QAAM,MAAM;AACZ,MAAI,oBAAO,QAAQ,QAAQ,GAAG,EAAG;AACjC,QAAM,aAAa,EAAE,aAAa,UAAU,cAAc,SAAS;AACnE,0BAAM,cAAc,YAAY,EAAE,0BAA0B,MAAM,CAAC;AACnE,sBAAO,QAAQ,QAAQ,MAAK,oBAAI,KAAK,GAAE,YAAY,CAAC;AACtD;AAEA,SAAS,qBAAqB;AAC5B,QAAM,uBAAuB,uBAAQ,MAAM;AAC3C,MAAI,wBAAwB,EAAG;AAC/B,0BAAM,mBAAmB,EAAE,qBAAqB,GAAG,EAAE,0BAA0B,MAAM,CAAC;AACxF;AAEO,SAAS,kBAAkB;AAChC,QAAM,eAAW,gCAAY;AAC7B,QAAM,mBAAe,iCAAY,QAAQ;AAEzC,8BAAU,MAAM;AACd,kBAAc,QAAQ;AACtB,4BAAM,iBAAiB,QAAW,EAAE,0BAA0B,MAAM,CAAC;AAErE,UAAM,eAAe,6BAAS,iBAAiB,UAAU,CAAC,UAAU;AAClE,6BAAQ,kBAAkB;AAE1B,UAAI,UAAU,YAAY,CAAC,uBAAQ,SAAS,GAAG;AAC7C,+BAAQ,aAAa,IAAI;AAAA,MAC3B,WAES,UAAU,YAAY,uBAAQ,SAAS,GAAG;AACjD,+BAAQ,aAAa,KAAK;AAC1B,2BAAmB;AAAA,MACrB;AAAA,IACF,CAAC;AAED,WAAO,MAAM,aAAa,OAAO;AAAA,EACnC,GAAG,CAAC,CAAC;AAGL,8BAAU,MAAM;AACd,4BAAM,eAAe;AAAA,MACnB,aAAa;AAAA,MACb,cAAc;AAAA,MACd,uBAAuB,gBAAgB;AAAA,MACvC,sBAAsB,eAAe,uBAAQ,MAAM,IAAI;AAAA,IACzD,CAAC;AAAA,EACH,GAAG,CAAC,QAAQ,CAAC;AACf;","names":[]}
@@ -0,0 +1,3 @@
1
+ declare function useAppAnalytics(): void;
2
+
3
+ export { useAppAnalytics };
@@ -0,0 +1,3 @@
1
+ declare function useAppAnalytics(): void;
2
+
3
+ export { useAppAnalytics };
@@ -0,0 +1,50 @@
1
+ // src/hooks/use-app-analytics.ts
2
+ import { usePathname } from "expo-router";
3
+ import { useEffect } from "react";
4
+ import { AppState } from "react-native";
5
+ import { config } from "../setup/index.mjs";
6
+ import { session } from "../setup/session.mjs";
7
+ import { track } from "../track/index.mjs";
8
+ import { usePrevious } from "./use-previous.mjs";
9
+ function sendFirstOpen(pathname) {
10
+ const key = "first_open_time";
11
+ if (config.storage.getItem(key)) return;
12
+ const properties = { screen_name: pathname, screen_class: pathname };
13
+ track("first_open", properties, { enableThirdPartyTracking: false });
14
+ config.storage.setItem(key, (/* @__PURE__ */ new Date()).toISOString());
15
+ }
16
+ function sendUserEngagement() {
17
+ const engagement_time_msec = session.flush();
18
+ if (engagement_time_msec <= 0) return;
19
+ track("user_engagement", { engagement_time_msec }, { enableThirdPartyTracking: false });
20
+ }
21
+ function useAppAnalytics() {
22
+ const pathname = usePathname();
23
+ const prevPathname = usePrevious(pathname);
24
+ useEffect(() => {
25
+ sendFirstOpen(pathname);
26
+ track("session_start", void 0, { enableThirdPartyTracking: false });
27
+ const subscription = AppState.addEventListener("change", (state) => {
28
+ session.updateAccumulator();
29
+ if (state === "active" && !session.isActive()) {
30
+ session.updateActive(true);
31
+ } else if (state !== "active" && session.isActive()) {
32
+ session.updateActive(false);
33
+ sendUserEngagement();
34
+ }
35
+ });
36
+ return () => subscription.remove();
37
+ }, []);
38
+ useEffect(() => {
39
+ track("screen_view", {
40
+ screen_name: pathname,
41
+ screen_class: pathname,
42
+ previous_screen_class: prevPathname ?? void 0,
43
+ engagement_time_msec: prevPathname ? session.flush() : void 0
44
+ });
45
+ }, [pathname]);
46
+ }
47
+ export {
48
+ useAppAnalytics
49
+ };
50
+ //# sourceMappingURL=use-app-analytics.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/use-app-analytics.ts"],"sourcesContent":["import { usePathname } from 'expo-router';\nimport { useEffect } from 'react';\nimport { AppState } from 'react-native';\nimport { config } from '../setup/index';\nimport { session } from '../setup/session';\nimport { track } from '../track/index';\nimport { usePrevious } from './use-previous';\n\nfunction sendFirstOpen(pathname: string) {\n const key = 'first_open_time';\n if (config.storage.getItem(key)) return;\n const properties = { screen_name: pathname, screen_class: pathname };\n track('first_open', properties, { enableThirdPartyTracking: false });\n config.storage.setItem(key, new Date().toISOString());\n}\n\nfunction sendUserEngagement() {\n const engagement_time_msec = session.flush();\n if (engagement_time_msec <= 0) return;\n track('user_engagement', { engagement_time_msec }, { enableThirdPartyTracking: false });\n}\n\nexport function useAppAnalytics() {\n const pathname = usePathname();\n const prevPathname = usePrevious(pathname);\n\n useEffect(() => {\n sendFirstOpen(pathname);\n track('session_start', undefined, { enableThirdPartyTracking: false });\n\n const subscription = AppState.addEventListener('change', (state) => {\n session.updateAccumulator();\n // when returning to the foreground from the background\n if (state === 'active' && !session.isActive()) {\n session.updateActive(true);\n }\n // when entering the background\n else if (state !== 'active' && session.isActive()) {\n session.updateActive(false);\n sendUserEngagement();\n }\n });\n\n return () => subscription.remove();\n }, []);\n\n // when the screen is switched, the engagement time of the previous screen is recorded\n useEffect(() => {\n track('screen_view', {\n screen_name: pathname,\n screen_class: pathname,\n previous_screen_class: prevPathname ?? undefined,\n engagement_time_msec: prevPathname ? session.flush() : undefined,\n });\n }, [pathname]);\n}\n"],"mappings":";AAAA,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB;AAC1B,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,mBAAmB;AAE5B,SAAS,cAAc,UAAkB;AACvC,QAAM,MAAM;AACZ,MAAI,OAAO,QAAQ,QAAQ,GAAG,EAAG;AACjC,QAAM,aAAa,EAAE,aAAa,UAAU,cAAc,SAAS;AACnE,QAAM,cAAc,YAAY,EAAE,0BAA0B,MAAM,CAAC;AACnE,SAAO,QAAQ,QAAQ,MAAK,oBAAI,KAAK,GAAE,YAAY,CAAC;AACtD;AAEA,SAAS,qBAAqB;AAC5B,QAAM,uBAAuB,QAAQ,MAAM;AAC3C,MAAI,wBAAwB,EAAG;AAC/B,QAAM,mBAAmB,EAAE,qBAAqB,GAAG,EAAE,0BAA0B,MAAM,CAAC;AACxF;AAEO,SAAS,kBAAkB;AAChC,QAAM,WAAW,YAAY;AAC7B,QAAM,eAAe,YAAY,QAAQ;AAEzC,YAAU,MAAM;AACd,kBAAc,QAAQ;AACtB,UAAM,iBAAiB,QAAW,EAAE,0BAA0B,MAAM,CAAC;AAErE,UAAM,eAAe,SAAS,iBAAiB,UAAU,CAAC,UAAU;AAClE,cAAQ,kBAAkB;AAE1B,UAAI,UAAU,YAAY,CAAC,QAAQ,SAAS,GAAG;AAC7C,gBAAQ,aAAa,IAAI;AAAA,MAC3B,WAES,UAAU,YAAY,QAAQ,SAAS,GAAG;AACjD,gBAAQ,aAAa,KAAK;AAC1B,2BAAmB;AAAA,MACrB;AAAA,IACF,CAAC;AAED,WAAO,MAAM,aAAa,OAAO;AAAA,EACnC,GAAG,CAAC,CAAC;AAGL,YAAU,MAAM;AACd,UAAM,eAAe;AAAA,MACnB,aAAa;AAAA,MACb,cAAc;AAAA,MACd,uBAAuB,gBAAgB;AAAA,MACvC,sBAAsB,eAAe,QAAQ,MAAM,IAAI;AAAA,IACzD,CAAC;AAAA,EACH,GAAG,CAAC,QAAQ,CAAC;AACf;","names":[]}
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/hooks/use-outbound-click-analytics.ts
21
+ var use_outbound_click_analytics_exports = {};
22
+ __export(use_outbound_click_analytics_exports, {
23
+ useOutboundClickAnalytics: () => useOutboundClickAnalytics
24
+ });
25
+ module.exports = __toCommonJS(use_outbound_click_analytics_exports);
26
+ var import_react = require("react");
27
+ var import_track = require("../track/index.cjs");
28
+ function useOutboundClickAnalytics() {
29
+ (0, import_react.useEffect)(() => {
30
+ const onClick = (event) => {
31
+ var _a;
32
+ const target = event.target;
33
+ const anchor = target.closest("a");
34
+ if (!anchor || !anchor.href) return;
35
+ try {
36
+ const url = new URL(anchor.href, window.location.origin);
37
+ if (url.hostname !== window.location.hostname) {
38
+ (0, import_track.track)("click", {
39
+ outbound: true,
40
+ link_id: anchor.id || "",
41
+ link_url: anchor.href,
42
+ link_text: ((_a = anchor.textContent) == null ? void 0 : _a.trim()) || "",
43
+ link_domain: url.hostname,
44
+ link_classes: anchor.className || ""
45
+ });
46
+ }
47
+ } catch {
48
+ }
49
+ };
50
+ document.addEventListener("click", onClick, { passive: true, capture: true });
51
+ return () => document.removeEventListener("click", onClick, { capture: true });
52
+ }, []);
53
+ }
54
+ // Annotate the CommonJS export names for ESM import in node:
55
+ 0 && (module.exports = {
56
+ useOutboundClickAnalytics
57
+ });
58
+ //# sourceMappingURL=use-outbound-click-analytics.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/use-outbound-click-analytics.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport { track } from '../track/index';\n\n/**\n * Tracks outbound link clicks - when a user clicks a link that leads away\n * from the current domain to another website.\n */\nexport function useOutboundClickAnalytics() {\n useEffect(() => {\n const onClick = (event: MouseEvent) => {\n // Find the closest anchor element from the clicked target\n const target = event.target as HTMLElement;\n const anchor = target.closest('a');\n if (!anchor || !anchor.href) return;\n\n try {\n const url = new URL(anchor.href, window.location.origin);\n\n // Check if it's an external link (different hostname)\n if (url.hostname !== window.location.hostname) {\n track('click', {\n outbound: true,\n link_id: anchor.id || '',\n link_url: anchor.href,\n link_text: anchor.textContent?.trim() || '',\n link_domain: url.hostname,\n link_classes: anchor.className || '',\n });\n }\n } catch {\n // Invalid URL, ignore\n }\n };\n\n document.addEventListener('click', onClick, { passive: true, capture: true });\n return () => document.removeEventListener('click', onClick, { capture: true });\n }, []);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA0B;AAC1B,mBAAsB;AAMf,SAAS,4BAA4B;AAC1C,8BAAU,MAAM;AACd,UAAM,UAAU,CAAC,UAAsB;AAT3C;AAWM,YAAM,SAAS,MAAM;AACrB,YAAM,SAAS,OAAO,QAAQ,GAAG;AACjC,UAAI,CAAC,UAAU,CAAC,OAAO,KAAM;AAE7B,UAAI;AACF,cAAM,MAAM,IAAI,IAAI,OAAO,MAAM,OAAO,SAAS,MAAM;AAGvD,YAAI,IAAI,aAAa,OAAO,SAAS,UAAU;AAC7C,kCAAM,SAAS;AAAA,YACb,UAAU;AAAA,YACV,SAAS,OAAO,MAAM;AAAA,YACtB,UAAU,OAAO;AAAA,YACjB,aAAW,YAAO,gBAAP,mBAAoB,WAAU;AAAA,YACzC,aAAa,IAAI;AAAA,YACjB,cAAc,OAAO,aAAa;AAAA,UACpC,CAAC;AAAA,QACH;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,aAAS,iBAAiB,SAAS,SAAS,EAAE,SAAS,MAAM,SAAS,KAAK,CAAC;AAC5E,WAAO,MAAM,SAAS,oBAAoB,SAAS,SAAS,EAAE,SAAS,KAAK,CAAC;AAAA,EAC/E,GAAG,CAAC,CAAC;AACP;","names":[]}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Tracks outbound link clicks - when a user clicks a link that leads away
3
+ * from the current domain to another website.
4
+ */
5
+ declare function useOutboundClickAnalytics(): void;
6
+
7
+ export { useOutboundClickAnalytics };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Tracks outbound link clicks - when a user clicks a link that leads away
3
+ * from the current domain to another website.
4
+ */
5
+ declare function useOutboundClickAnalytics(): void;
6
+
7
+ export { useOutboundClickAnalytics };
@@ -0,0 +1,33 @@
1
+ // src/hooks/use-outbound-click-analytics.ts
2
+ import { useEffect } from "react";
3
+ import { track } from "../track/index.mjs";
4
+ function useOutboundClickAnalytics() {
5
+ useEffect(() => {
6
+ const onClick = (event) => {
7
+ var _a;
8
+ const target = event.target;
9
+ const anchor = target.closest("a");
10
+ if (!anchor || !anchor.href) return;
11
+ try {
12
+ const url = new URL(anchor.href, window.location.origin);
13
+ if (url.hostname !== window.location.hostname) {
14
+ track("click", {
15
+ outbound: true,
16
+ link_id: anchor.id || "",
17
+ link_url: anchor.href,
18
+ link_text: ((_a = anchor.textContent) == null ? void 0 : _a.trim()) || "",
19
+ link_domain: url.hostname,
20
+ link_classes: anchor.className || ""
21
+ });
22
+ }
23
+ } catch {
24
+ }
25
+ };
26
+ document.addEventListener("click", onClick, { passive: true, capture: true });
27
+ return () => document.removeEventListener("click", onClick, { capture: true });
28
+ }, []);
29
+ }
30
+ export {
31
+ useOutboundClickAnalytics
32
+ };
33
+ //# sourceMappingURL=use-outbound-click-analytics.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/use-outbound-click-analytics.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport { track } from '../track/index';\n\n/**\n * Tracks outbound link clicks - when a user clicks a link that leads away\n * from the current domain to another website.\n */\nexport function useOutboundClickAnalytics() {\n useEffect(() => {\n const onClick = (event: MouseEvent) => {\n // Find the closest anchor element from the clicked target\n const target = event.target as HTMLElement;\n const anchor = target.closest('a');\n if (!anchor || !anchor.href) return;\n\n try {\n const url = new URL(anchor.href, window.location.origin);\n\n // Check if it's an external link (different hostname)\n if (url.hostname !== window.location.hostname) {\n track('click', {\n outbound: true,\n link_id: anchor.id || '',\n link_url: anchor.href,\n link_text: anchor.textContent?.trim() || '',\n link_domain: url.hostname,\n link_classes: anchor.className || '',\n });\n }\n } catch {\n // Invalid URL, ignore\n }\n };\n\n document.addEventListener('click', onClick, { passive: true, capture: true });\n return () => document.removeEventListener('click', onClick, { capture: true });\n }, []);\n}\n"],"mappings":";AAAA,SAAS,iBAAiB;AAC1B,SAAS,aAAa;AAMf,SAAS,4BAA4B;AAC1C,YAAU,MAAM;AACd,UAAM,UAAU,CAAC,UAAsB;AAT3C;AAWM,YAAM,SAAS,MAAM;AACrB,YAAM,SAAS,OAAO,QAAQ,GAAG;AACjC,UAAI,CAAC,UAAU,CAAC,OAAO,KAAM;AAE7B,UAAI;AACF,cAAM,MAAM,IAAI,IAAI,OAAO,MAAM,OAAO,SAAS,MAAM;AAGvD,YAAI,IAAI,aAAa,OAAO,SAAS,UAAU;AAC7C,gBAAM,SAAS;AAAA,YACb,UAAU;AAAA,YACV,SAAS,OAAO,MAAM;AAAA,YACtB,UAAU,OAAO;AAAA,YACjB,aAAW,YAAO,gBAAP,mBAAoB,WAAU;AAAA,YACzC,aAAa,IAAI;AAAA,YACjB,cAAc,OAAO,aAAa;AAAA,UACpC,CAAC;AAAA,QACH;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,aAAS,iBAAiB,SAAS,SAAS,EAAE,SAAS,MAAM,SAAS,KAAK,CAAC;AAC5E,WAAO,MAAM,SAAS,oBAAoB,SAAS,SAAS,EAAE,SAAS,KAAK,CAAC;AAAA,EAC/E,GAAG,CAAC,CAAC;AACP;","names":[]}
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/hooks/use-web-analytics.ts
21
+ var use_web_analytics_exports = {};
22
+ __export(use_web_analytics_exports, {
23
+ useWebAnalytics: () => useWebAnalytics
24
+ });
25
+ module.exports = __toCommonJS(use_web_analytics_exports);
26
+ var import_utils = require("@shware/utils");
27
+ var import_react = require("react");
28
+ var import_setup = require("../setup/index.cjs");
29
+ var import_session = require("../setup/session.cjs");
30
+ var import_track = require("../track/index.cjs");
31
+ var import_use_previous = require("./use-previous.cjs");
32
+ function sendFirstVisit(pathname) {
33
+ const key = "first_visit_time";
34
+ if (import_setup.config.storage.getItem(key)) return;
35
+ const properties = {
36
+ page_path: pathname,
37
+ page_title: document.title,
38
+ page_referrer: document.referrer,
39
+ page_location: window.location.href
40
+ };
41
+ (0, import_track.track)("first_visit", properties, { enableThirdPartyTracking: false });
42
+ import_setup.config.storage.setItem(key, (/* @__PURE__ */ new Date()).toISOString());
43
+ }
44
+ function sendUserEngagement() {
45
+ const engagement_time_msec = import_session.session.flush();
46
+ if (engagement_time_msec <= 0) return;
47
+ (0, import_track.sendBeacon)("user_engagement", { engagement_time_msec });
48
+ }
49
+ function sendScroll() {
50
+ const engagement_time_msec = import_session.session.flush();
51
+ if (engagement_time_msec <= 0) return;
52
+ (0, import_track.track)("scroll", { engagement_time_msec }, { enableThirdPartyTracking: false });
53
+ }
54
+ function getScrollPercent() {
55
+ const scrollTop = window.scrollY || document.documentElement.scrollTop;
56
+ const windowHeight = window.innerHeight;
57
+ const docHeight = document.documentElement.scrollHeight;
58
+ if (docHeight === 0) return 0;
59
+ return (scrollTop + windowHeight) * 100 / docHeight;
60
+ }
61
+ function onPageHide() {
62
+ import_session.session.pagehide();
63
+ sendUserEngagement();
64
+ }
65
+ function onVisibilityChange() {
66
+ import_session.session.visibilitychange(document.visibilityState);
67
+ if (document.visibilityState === "hidden") {
68
+ sendUserEngagement();
69
+ }
70
+ }
71
+ function useWebAnalytics(pathname) {
72
+ const prevPathname = (0, import_use_previous.usePrevious)(pathname);
73
+ const hasSendScroll = (0, import_react.useRef)(false);
74
+ (0, import_react.useEffect)(() => {
75
+ hasSendScroll.current = false;
76
+ }, [pathname]);
77
+ (0, import_react.useEffect)(() => {
78
+ sendFirstVisit(pathname);
79
+ (0, import_track.track)("session_start", void 0, { enableThirdPartyTracking: false });
80
+ const onScroll = (0, import_utils.throttle)(() => {
81
+ import_session.session.updateAccumulator();
82
+ if (hasSendScroll.current) return;
83
+ if (getScrollPercent() < 90) return;
84
+ hasSendScroll.current = true;
85
+ sendScroll();
86
+ }, 500);
87
+ const checkpointEvents = ["mousedown", "keydown", "touchstart"];
88
+ const checkpoint = (0, import_utils.throttle)(import_session.session.updateAccumulator, 1e3);
89
+ window.addEventListener("focus", import_session.session.focus, { passive: true });
90
+ window.addEventListener("blur", import_session.session.blur, { passive: true });
91
+ window.addEventListener("pageshow", import_session.session.pageshow, { passive: true });
92
+ window.addEventListener("pagehide", onPageHide, { passive: true });
93
+ window.addEventListener("scroll", onScroll, { passive: true });
94
+ document.addEventListener("visibilitychange", onVisibilityChange, { passive: true });
95
+ checkpointEvents.forEach((event) => {
96
+ window.addEventListener(event, checkpoint, { passive: true, capture: true });
97
+ });
98
+ return () => {
99
+ window.removeEventListener("focus", import_session.session.focus);
100
+ window.removeEventListener("blur", import_session.session.blur);
101
+ window.removeEventListener("pageshow", import_session.session.pageshow);
102
+ window.removeEventListener("pagehide", onPageHide);
103
+ window.removeEventListener("scroll", onScroll);
104
+ document.removeEventListener("visibilitychange", onVisibilityChange);
105
+ checkpointEvents.forEach((event) => {
106
+ window.removeEventListener(event, checkpoint);
107
+ });
108
+ onScroll.cancel();
109
+ checkpoint.cancel();
110
+ };
111
+ }, []);
112
+ (0, import_react.useEffect)(() => {
113
+ const properties = {
114
+ page_path: pathname,
115
+ page_title: document.title,
116
+ page_referrer: document.referrer,
117
+ page_location: window.location.href,
118
+ previous_page_path: prevPathname ?? void 0,
119
+ engagement_time_msec: prevPathname ? import_session.session.flush() : void 0
120
+ };
121
+ (0, import_track.track)("page_view", properties, { enableThirdPartyTracking: false });
122
+ }, [pathname]);
123
+ }
124
+ // Annotate the CommonJS export names for ESM import in node:
125
+ 0 && (module.exports = {
126
+ useWebAnalytics
127
+ });
128
+ //# sourceMappingURL=use-web-analytics.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/use-web-analytics.ts"],"sourcesContent":["import { throttle } from '@shware/utils';\nimport { useEffect, useRef } from 'react';\nimport { config } from '../setup/index';\nimport { session } from '../setup/session';\nimport { sendBeacon, track } from '../track/index';\nimport { usePrevious } from './use-previous';\n\nfunction sendFirstVisit(pathname: string) {\n const key = 'first_visit_time';\n if (config.storage.getItem(key)) return;\n const properties = {\n page_path: pathname,\n page_title: document.title,\n page_referrer: document.referrer,\n page_location: window.location.href,\n };\n track('first_visit', properties, { enableThirdPartyTracking: false });\n config.storage.setItem(key, new Date().toISOString());\n}\n\nfunction sendUserEngagement() {\n const engagement_time_msec = session.flush();\n if (engagement_time_msec <= 0) return;\n sendBeacon('user_engagement', { engagement_time_msec });\n}\n\nfunction sendScroll() {\n const engagement_time_msec = session.flush();\n if (engagement_time_msec <= 0) return;\n track('scroll', { engagement_time_msec }, { enableThirdPartyTracking: false });\n}\n\nfunction getScrollPercent() {\n const scrollTop = window.scrollY || document.documentElement.scrollTop;\n const windowHeight = window.innerHeight;\n const docHeight = document.documentElement.scrollHeight;\n if (docHeight === 0) return 0;\n return ((scrollTop + windowHeight) * 100) / docHeight;\n}\n\nfunction onPageHide() {\n session.pagehide();\n sendUserEngagement();\n}\n\nfunction onVisibilityChange() {\n session.visibilitychange(document.visibilityState);\n if (document.visibilityState === 'hidden') {\n sendUserEngagement();\n }\n}\n\n/**\n * 1. send session_start event when the page is loaded\n * 2. send scroll event when the user scrolls more than 90% of the page\n * 3. send user_engagement event when the page is hidden or the user is not focused\n */\nexport function useWebAnalytics(pathname: string) {\n const prevPathname = usePrevious(pathname);\n\n // reset state when the pathname changes and send scroll when the user navigates to a new page\n const hasSendScroll = useRef(false);\n useEffect(() => {\n hasSendScroll.current = false;\n }, [pathname]);\n\n useEffect(() => {\n sendFirstVisit(pathname);\n track('session_start', undefined, { enableThirdPartyTracking: false });\n\n const onScroll = throttle(() => {\n session.updateAccumulator();\n if (hasSendScroll.current) return;\n // only send scroll when the user has scrolled more than 90% of the page\n if (getScrollPercent() < 90) return;\n hasSendScroll.current = true;\n sendScroll();\n }, 500);\n\n const checkpointEvents = ['mousedown', 'keydown', 'touchstart'];\n const checkpoint = throttle(session.updateAccumulator, 1000);\n\n window.addEventListener('focus', session.focus, { passive: true });\n window.addEventListener('blur', session.blur, { passive: true });\n window.addEventListener('pageshow', session.pageshow, { passive: true });\n window.addEventListener('pagehide', onPageHide, { passive: true });\n window.addEventListener('scroll', onScroll, { passive: true });\n document.addEventListener('visibilitychange', onVisibilityChange, { passive: true });\n\n // save checkpoint\n checkpointEvents.forEach((event) => {\n window.addEventListener(event, checkpoint, { passive: true, capture: true });\n });\n\n return () => {\n window.removeEventListener('focus', session.focus);\n window.removeEventListener('blur', session.blur);\n window.removeEventListener('pageshow', session.pageshow);\n window.removeEventListener('pagehide', onPageHide);\n window.removeEventListener('scroll', onScroll);\n document.removeEventListener('visibilitychange', onVisibilityChange);\n\n checkpointEvents.forEach((event) => {\n window.removeEventListener(event, checkpoint);\n });\n\n onScroll.cancel();\n checkpoint.cancel();\n };\n }, []);\n\n useEffect(() => {\n const properties = {\n page_path: pathname,\n page_title: document.title,\n page_referrer: document.referrer,\n page_location: window.location.href,\n previous_page_path: prevPathname ?? undefined,\n engagement_time_msec: prevPathname ? session.flush() : undefined,\n };\n\n track('page_view', properties, { enableThirdPartyTracking: false });\n }, [pathname]);\n}\n"],"mappings":"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","names":[]}
@@ -3,6 +3,6 @@
3
3
  * 2. send scroll event when the user scrolls more than 90% of the page
4
4
  * 3. send user_engagement event when the page is hidden or the user is not focused
5
5
  */
6
- declare function useWebSessionAnalytics(pathname: string): void;
6
+ declare function useWebAnalytics(pathname: string): void;
7
7
 
8
- export { useWebSessionAnalytics };
8
+ export { useWebAnalytics };
@@ -3,6 +3,6 @@
3
3
  * 2. send scroll event when the user scrolls more than 90% of the page
4
4
  * 3. send user_engagement event when the page is hidden or the user is not focused
5
5
  */
6
- declare function useWebSessionAnalytics(pathname: string): void;
6
+ declare function useWebAnalytics(pathname: string): void;
7
7
 
8
- export { useWebSessionAnalytics };
8
+ export { useWebAnalytics };
@@ -0,0 +1,103 @@
1
+ // src/hooks/use-web-analytics.ts
2
+ import { throttle } from "@shware/utils";
3
+ import { useEffect, useRef } from "react";
4
+ import { config } from "../setup/index.mjs";
5
+ import { session } from "../setup/session.mjs";
6
+ import { sendBeacon, track } from "../track/index.mjs";
7
+ import { usePrevious } from "./use-previous.mjs";
8
+ function sendFirstVisit(pathname) {
9
+ const key = "first_visit_time";
10
+ if (config.storage.getItem(key)) return;
11
+ const properties = {
12
+ page_path: pathname,
13
+ page_title: document.title,
14
+ page_referrer: document.referrer,
15
+ page_location: window.location.href
16
+ };
17
+ track("first_visit", properties, { enableThirdPartyTracking: false });
18
+ config.storage.setItem(key, (/* @__PURE__ */ new Date()).toISOString());
19
+ }
20
+ function sendUserEngagement() {
21
+ const engagement_time_msec = session.flush();
22
+ if (engagement_time_msec <= 0) return;
23
+ sendBeacon("user_engagement", { engagement_time_msec });
24
+ }
25
+ function sendScroll() {
26
+ const engagement_time_msec = session.flush();
27
+ if (engagement_time_msec <= 0) return;
28
+ track("scroll", { engagement_time_msec }, { enableThirdPartyTracking: false });
29
+ }
30
+ function getScrollPercent() {
31
+ const scrollTop = window.scrollY || document.documentElement.scrollTop;
32
+ const windowHeight = window.innerHeight;
33
+ const docHeight = document.documentElement.scrollHeight;
34
+ if (docHeight === 0) return 0;
35
+ return (scrollTop + windowHeight) * 100 / docHeight;
36
+ }
37
+ function onPageHide() {
38
+ session.pagehide();
39
+ sendUserEngagement();
40
+ }
41
+ function onVisibilityChange() {
42
+ session.visibilitychange(document.visibilityState);
43
+ if (document.visibilityState === "hidden") {
44
+ sendUserEngagement();
45
+ }
46
+ }
47
+ function useWebAnalytics(pathname) {
48
+ const prevPathname = usePrevious(pathname);
49
+ const hasSendScroll = useRef(false);
50
+ useEffect(() => {
51
+ hasSendScroll.current = false;
52
+ }, [pathname]);
53
+ useEffect(() => {
54
+ sendFirstVisit(pathname);
55
+ track("session_start", void 0, { enableThirdPartyTracking: false });
56
+ const onScroll = throttle(() => {
57
+ session.updateAccumulator();
58
+ if (hasSendScroll.current) return;
59
+ if (getScrollPercent() < 90) return;
60
+ hasSendScroll.current = true;
61
+ sendScroll();
62
+ }, 500);
63
+ const checkpointEvents = ["mousedown", "keydown", "touchstart"];
64
+ const checkpoint = throttle(session.updateAccumulator, 1e3);
65
+ window.addEventListener("focus", session.focus, { passive: true });
66
+ window.addEventListener("blur", session.blur, { passive: true });
67
+ window.addEventListener("pageshow", session.pageshow, { passive: true });
68
+ window.addEventListener("pagehide", onPageHide, { passive: true });
69
+ window.addEventListener("scroll", onScroll, { passive: true });
70
+ document.addEventListener("visibilitychange", onVisibilityChange, { passive: true });
71
+ checkpointEvents.forEach((event) => {
72
+ window.addEventListener(event, checkpoint, { passive: true, capture: true });
73
+ });
74
+ return () => {
75
+ window.removeEventListener("focus", session.focus);
76
+ window.removeEventListener("blur", session.blur);
77
+ window.removeEventListener("pageshow", session.pageshow);
78
+ window.removeEventListener("pagehide", onPageHide);
79
+ window.removeEventListener("scroll", onScroll);
80
+ document.removeEventListener("visibilitychange", onVisibilityChange);
81
+ checkpointEvents.forEach((event) => {
82
+ window.removeEventListener(event, checkpoint);
83
+ });
84
+ onScroll.cancel();
85
+ checkpoint.cancel();
86
+ };
87
+ }, []);
88
+ useEffect(() => {
89
+ const properties = {
90
+ page_path: pathname,
91
+ page_title: document.title,
92
+ page_referrer: document.referrer,
93
+ page_location: window.location.href,
94
+ previous_page_path: prevPathname ?? void 0,
95
+ engagement_time_msec: prevPathname ? session.flush() : void 0
96
+ };
97
+ track("page_view", properties, { enableThirdPartyTracking: false });
98
+ }, [pathname]);
99
+ }
100
+ export {
101
+ useWebAnalytics
102
+ };
103
+ //# sourceMappingURL=use-web-analytics.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/use-web-analytics.ts"],"sourcesContent":["import { throttle } from '@shware/utils';\nimport { useEffect, useRef } from 'react';\nimport { config } from '../setup/index';\nimport { session } from '../setup/session';\nimport { sendBeacon, track } from '../track/index';\nimport { usePrevious } from './use-previous';\n\nfunction sendFirstVisit(pathname: string) {\n const key = 'first_visit_time';\n if (config.storage.getItem(key)) return;\n const properties = {\n page_path: pathname,\n page_title: document.title,\n page_referrer: document.referrer,\n page_location: window.location.href,\n };\n track('first_visit', properties, { enableThirdPartyTracking: false });\n config.storage.setItem(key, new Date().toISOString());\n}\n\nfunction sendUserEngagement() {\n const engagement_time_msec = session.flush();\n if (engagement_time_msec <= 0) return;\n sendBeacon('user_engagement', { engagement_time_msec });\n}\n\nfunction sendScroll() {\n const engagement_time_msec = session.flush();\n if (engagement_time_msec <= 0) return;\n track('scroll', { engagement_time_msec }, { enableThirdPartyTracking: false });\n}\n\nfunction getScrollPercent() {\n const scrollTop = window.scrollY || document.documentElement.scrollTop;\n const windowHeight = window.innerHeight;\n const docHeight = document.documentElement.scrollHeight;\n if (docHeight === 0) return 0;\n return ((scrollTop + windowHeight) * 100) / docHeight;\n}\n\nfunction onPageHide() {\n session.pagehide();\n sendUserEngagement();\n}\n\nfunction onVisibilityChange() {\n session.visibilitychange(document.visibilityState);\n if (document.visibilityState === 'hidden') {\n sendUserEngagement();\n }\n}\n\n/**\n * 1. send session_start event when the page is loaded\n * 2. send scroll event when the user scrolls more than 90% of the page\n * 3. send user_engagement event when the page is hidden or the user is not focused\n */\nexport function useWebAnalytics(pathname: string) {\n const prevPathname = usePrevious(pathname);\n\n // reset state when the pathname changes and send scroll when the user navigates to a new page\n const hasSendScroll = useRef(false);\n useEffect(() => {\n hasSendScroll.current = false;\n }, [pathname]);\n\n useEffect(() => {\n sendFirstVisit(pathname);\n track('session_start', undefined, { enableThirdPartyTracking: false });\n\n const onScroll = throttle(() => {\n session.updateAccumulator();\n if (hasSendScroll.current) return;\n // only send scroll when the user has scrolled more than 90% of the page\n if (getScrollPercent() < 90) return;\n hasSendScroll.current = true;\n sendScroll();\n }, 500);\n\n const checkpointEvents = ['mousedown', 'keydown', 'touchstart'];\n const checkpoint = throttle(session.updateAccumulator, 1000);\n\n window.addEventListener('focus', session.focus, { passive: true });\n window.addEventListener('blur', session.blur, { passive: true });\n window.addEventListener('pageshow', session.pageshow, { passive: true });\n window.addEventListener('pagehide', onPageHide, { passive: true });\n window.addEventListener('scroll', onScroll, { passive: true });\n document.addEventListener('visibilitychange', onVisibilityChange, { passive: true });\n\n // save checkpoint\n checkpointEvents.forEach((event) => {\n window.addEventListener(event, checkpoint, { passive: true, capture: true });\n });\n\n return () => {\n window.removeEventListener('focus', session.focus);\n window.removeEventListener('blur', session.blur);\n window.removeEventListener('pageshow', session.pageshow);\n window.removeEventListener('pagehide', onPageHide);\n window.removeEventListener('scroll', onScroll);\n document.removeEventListener('visibilitychange', onVisibilityChange);\n\n checkpointEvents.forEach((event) => {\n window.removeEventListener(event, checkpoint);\n });\n\n onScroll.cancel();\n checkpoint.cancel();\n };\n }, []);\n\n useEffect(() => {\n const properties = {\n page_path: pathname,\n page_title: document.title,\n page_referrer: document.referrer,\n page_location: window.location.href,\n previous_page_path: prevPathname ?? undefined,\n engagement_time_msec: prevPathname ? session.flush() : undefined,\n };\n\n track('page_view', properties, { enableThirdPartyTracking: false });\n }, [pathname]);\n}\n"],"mappings":"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","names":[]}
@@ -26,13 +26,11 @@ __export(native_exports, {
26
26
  getProbabilisticFingerprint: () => import_fingerprint.getProbabilisticFingerprint,
27
27
  getTags: () => import_setup.getTags,
28
28
  storage: () => import_setup.storage,
29
- useAppSessionAnalytics: () => import_use_app_session_analytics.useAppSessionAnalytics,
30
- useScreenViewAnalytics: () => import_use_screen_view_analytics.useScreenViewAnalytics
29
+ useAppAnalytics: () => import_use_app_analytics.useAppAnalytics
31
30
  });
32
31
  module.exports = __toCommonJS(native_exports);
33
32
  var import_setup = require("./setup.cjs");
34
- var import_use_screen_view_analytics = require("../hooks/use-screen-view-analytics.cjs");
35
- var import_use_app_session_analytics = require("../hooks/use-app-session-analytics.cjs");
33
+ var import_use_app_analytics = require("../hooks/use-app-analytics.cjs");
36
34
  var import_fingerprint = require("./fingerprint.cjs");
37
35
  // Annotate the CommonJS export names for ESM import in node:
38
36
  0 && (module.exports = {
@@ -42,7 +40,6 @@ var import_fingerprint = require("./fingerprint.cjs");
42
40
  getProbabilisticFingerprint,
43
41
  getTags,
44
42
  storage,
45
- useAppSessionAnalytics,
46
- useScreenViewAnalytics
43
+ useAppAnalytics
47
44
  });
48
45
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/native/index.ts"],"sourcesContent":["export { getDeviceId, getDeviceType, getTags, storage } from './setup';\nexport { useScreenViewAnalytics } from '../hooks/use-screen-view-analytics';\nexport { useAppSessionAnalytics } from '../hooks/use-app-session-analytics';\nexport { getDeterministicFingerprint, getProbabilisticFingerprint } from './fingerprint';\n\nexport type { DeterministicFingerprint, ProbabilisticFingerprint } from './fingerprint';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA6D;AAC7D,uCAAuC;AACvC,uCAAuC;AACvC,yBAAyE;","names":[]}
1
+ {"version":3,"sources":["../../src/native/index.ts"],"sourcesContent":["export { getDeviceId, getDeviceType, getTags, storage } from './setup';\nexport { useAppAnalytics } from '../hooks/use-app-analytics';\nexport { getDeterministicFingerprint, getProbabilisticFingerprint } from './fingerprint';\n\nexport type { DeterministicFingerprint, ProbabilisticFingerprint } from './fingerprint';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA6D;AAC7D,+BAAgC;AAChC,yBAAyE;","names":[]}
@@ -1,6 +1,5 @@
1
1
  export { getDeviceId, getDeviceType, getTags, storage } from './setup.cjs';
2
- export { useScreenViewAnalytics } from '../hooks/use-screen-view-analytics.cjs';
3
- export { useAppSessionAnalytics } from '../hooks/use-app-session-analytics.cjs';
2
+ export { useAppAnalytics } from '../hooks/use-app-analytics.cjs';
4
3
  export { DeterministicFingerprint, ProbabilisticFingerprint, getDeterministicFingerprint, getProbabilisticFingerprint } from './fingerprint.cjs';
5
4
  import '../setup/index.cjs';
6
5
  import '../track/types.cjs';
@@ -1,6 +1,5 @@
1
1
  export { getDeviceId, getDeviceType, getTags, storage } from './setup.js';
2
- export { useScreenViewAnalytics } from '../hooks/use-screen-view-analytics.js';
3
- export { useAppSessionAnalytics } from '../hooks/use-app-session-analytics.js';
2
+ export { useAppAnalytics } from '../hooks/use-app-analytics.js';
4
3
  export { DeterministicFingerprint, ProbabilisticFingerprint, getDeterministicFingerprint, getProbabilisticFingerprint } from './fingerprint.js';
5
4
  import '../setup/index.js';
6
5
  import '../track/types.js';