@shware/analytics 5.1.0 → 5.1.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.
@@ -16,7 +16,7 @@ function sendFirstOpen(pathname) {
16
16
  require_setup_index.config.storage.setItem(require_constants_storage.keys.first_open_time, (/* @__PURE__ */ new Date()).toISOString());
17
17
  }
18
18
  function sendUserEngagement() {
19
- const engagement_time_msec = require_setup_session.session.flush();
19
+ const engagement_time_msec = require_setup_session.getSession().flush();
20
20
  if (engagement_time_msec <= 0) return;
21
21
  require_track_index.track("user_engagement", {
22
22
  engagement_time_msec,
@@ -26,13 +26,14 @@ function sendUserEngagement() {
26
26
  function useAppAnalytics(pathname) {
27
27
  const prevPathname = require_hooks_use_previous.usePrevious(pathname);
28
28
  (0, react.useEffect)(() => {
29
+ const session = require_setup_session.getSession();
29
30
  sendFirstOpen(pathname);
30
31
  require_track_index.track("session_start", void 0);
31
32
  const subscription = react_native.AppState.addEventListener("change", (state) => {
32
- require_setup_session.session.updateAccumulator();
33
- if (state === "active" && !require_setup_session.session.isActive()) require_setup_session.session.updateActive(true);
34
- else if (state !== "active" && require_setup_session.session.isActive()) {
35
- require_setup_session.session.updateActive(false);
33
+ session.updateAccumulator();
34
+ if (state === "active" && !session.isActive()) session.updateActive(true);
35
+ else if (state !== "active" && session.isActive()) {
36
+ session.updateActive(false);
36
37
  sendUserEngagement();
37
38
  }
38
39
  });
@@ -43,7 +44,7 @@ function useAppAnalytics(pathname) {
43
44
  screen_name: pathname,
44
45
  screen_class: pathname,
45
46
  previous_screen_class: prevPathname ?? void 0,
46
- engagement_time_msec: prevPathname ? require_setup_session.session.flush() : void 0
47
+ engagement_time_msec: prevPathname ? require_setup_session.getSession().flush() : void 0
47
48
  });
48
49
  }, [pathname]);
49
50
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-app-analytics.cjs","names":["config","keys","session","usePrevious","AppState"],"sources":["../../src/hooks/use-app-analytics.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport { AppState } from 'react-native';\nimport { keys } from '../constants/storage';\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 if (config.storage.getItem(keys.first_open_time)) return;\n track('first_open', { screen_name: pathname, screen_class: pathname });\n config.storage.setItem(keys.first_open_time, 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, trigger: 'background' });\n}\n\nexport function useAppAnalytics(pathname: string) {\n const prevPathname = usePrevious(pathname);\n\n useEffect(() => {\n sendFirstOpen(pathname);\n track('session_start', undefined);\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":";;;;;;;;;AAQA,SAAS,cAAc,UAAkB;CACvC,IAAIA,oBAAAA,OAAO,QAAQ,QAAQC,0BAAAA,KAAK,eAAe,GAAG;CAClD,oBAAA,MAAM,cAAc;EAAE,aAAa;EAAU,cAAc;CAAS,CAAC;CACrE,oBAAA,OAAO,QAAQ,QAAQA,0BAAAA,KAAK,kCAAiB,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC;AACvE;AAEA,SAAS,qBAAqB;CAC5B,MAAM,uBAAuBC,sBAAAA,QAAQ,MAAM;CAC3C,IAAI,wBAAwB,GAAG;CAC/B,oBAAA,MAAM,mBAAmB;EAAE;EAAsB,SAAS;CAAa,CAAC;AAC1E;AAEA,SAAgB,gBAAgB,UAAkB;CAChD,MAAM,eAAeC,2BAAAA,YAAY,QAAQ;CAEzC,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,cAAc,QAAQ;EACtB,oBAAA,MAAM,iBAAiB,KAAA,CAAS;EAEhC,MAAM,eAAeC,aAAAA,SAAS,iBAAiB,WAAW,UAAU;GAClE,sBAAA,QAAQ,kBAAkB;GAE1B,IAAI,UAAU,YAAY,CAACF,sBAAAA,QAAQ,SAAS,GAC1C,sBAAA,QAAQ,aAAa,IAAI;QAGtB,IAAI,UAAU,YAAYA,sBAAAA,QAAQ,SAAS,GAAG;IACjD,sBAAA,QAAQ,aAAa,KAAK;IAC1B,mBAAmB;GACrB;EACF,CAAC;EAED,aAAa,aAAa,OAAO;CACnC,GAAG,CAAC,CAAC;CAGL,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,oBAAA,MAAM,eAAe;GACnB,aAAa;GACb,cAAc;GACd,uBAAuB,gBAAgB,KAAA;GACvC,sBAAsB,eAAeA,sBAAAA,QAAQ,MAAM,IAAI,KAAA;EACzD,CAAC;CACH,GAAG,CAAC,QAAQ,CAAC;AACf"}
1
+ {"version":3,"file":"use-app-analytics.cjs","names":["config","keys","getSession","usePrevious","AppState"],"sources":["../../src/hooks/use-app-analytics.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport { AppState } from 'react-native';\nimport { keys } from '../constants/storage';\nimport { config } from '../setup/index';\nimport { getSession } from '../setup/session';\nimport { track } from '../track/index';\nimport { usePrevious } from './use-previous';\n\nfunction sendFirstOpen(pathname: string) {\n if (config.storage.getItem(keys.first_open_time)) return;\n track('first_open', { screen_name: pathname, screen_class: pathname });\n config.storage.setItem(keys.first_open_time, new Date().toISOString());\n}\n\nfunction sendUserEngagement() {\n const engagement_time_msec = getSession().flush();\n if (engagement_time_msec <= 0) return;\n track('user_engagement', { engagement_time_msec, trigger: 'background' });\n}\n\nexport function useAppAnalytics(pathname: string) {\n const prevPathname = usePrevious(pathname);\n\n useEffect(() => {\n const session = getSession();\n\n sendFirstOpen(pathname);\n track('session_start', undefined);\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 ? getSession().flush() : undefined,\n });\n }, [pathname]);\n}\n"],"mappings":";;;;;;;;;AAQA,SAAS,cAAc,UAAkB;CACvC,IAAIA,oBAAAA,OAAO,QAAQ,QAAQC,0BAAAA,KAAK,eAAe,GAAG;CAClD,oBAAA,MAAM,cAAc;EAAE,aAAa;EAAU,cAAc;CAAS,CAAC;CACrE,oBAAA,OAAO,QAAQ,QAAQA,0BAAAA,KAAK,kCAAiB,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC;AACvE;AAEA,SAAS,qBAAqB;CAC5B,MAAM,uBAAuBC,sBAAAA,WAAW,CAAC,CAAC,MAAM;CAChD,IAAI,wBAAwB,GAAG;CAC/B,oBAAA,MAAM,mBAAmB;EAAE;EAAsB,SAAS;CAAa,CAAC;AAC1E;AAEA,SAAgB,gBAAgB,UAAkB;CAChD,MAAM,eAAeC,2BAAAA,YAAY,QAAQ;CAEzC,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,MAAM,UAAUD,sBAAAA,WAAW;EAE3B,cAAc,QAAQ;EACtB,oBAAA,MAAM,iBAAiB,KAAA,CAAS;EAEhC,MAAM,eAAeE,aAAAA,SAAS,iBAAiB,WAAW,UAAU;GAClE,QAAQ,kBAAkB;GAE1B,IAAI,UAAU,YAAY,CAAC,QAAQ,SAAS,GAC1C,QAAQ,aAAa,IAAI;QAGtB,IAAI,UAAU,YAAY,QAAQ,SAAS,GAAG;IACjD,QAAQ,aAAa,KAAK;IAC1B,mBAAmB;GACrB;EACF,CAAC;EAED,aAAa,aAAa,OAAO;CACnC,GAAG,CAAC,CAAC;CAGL,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,oBAAA,MAAM,eAAe;GACnB,aAAa;GACb,cAAc;GACd,uBAAuB,gBAAgB,KAAA;GACvC,sBAAsB,eAAeF,sBAAAA,WAAW,CAAC,CAAC,MAAM,IAAI,KAAA;EAC9D,CAAC;CACH,GAAG,CAAC,QAAQ,CAAC;AACf"}
@@ -1,5 +1,5 @@
1
1
  import { config } from "../setup/index.mjs";
2
- import { session } from "../setup/session.mjs";
2
+ import { getSession } from "../setup/session.mjs";
3
3
  import { keys } from "../constants/storage.mjs";
4
4
  import { track } from "../track/index.mjs";
5
5
  import { usePrevious } from "./use-previous.mjs";
@@ -15,7 +15,7 @@ function sendFirstOpen(pathname) {
15
15
  config.storage.setItem(keys.first_open_time, (/* @__PURE__ */ new Date()).toISOString());
16
16
  }
17
17
  function sendUserEngagement() {
18
- const engagement_time_msec = session.flush();
18
+ const engagement_time_msec = getSession().flush();
19
19
  if (engagement_time_msec <= 0) return;
20
20
  track("user_engagement", {
21
21
  engagement_time_msec,
@@ -25,6 +25,7 @@ function sendUserEngagement() {
25
25
  function useAppAnalytics(pathname) {
26
26
  const prevPathname = usePrevious(pathname);
27
27
  useEffect(() => {
28
+ const session = getSession();
28
29
  sendFirstOpen(pathname);
29
30
  track("session_start", void 0);
30
31
  const subscription = AppState.addEventListener("change", (state) => {
@@ -42,7 +43,7 @@ function useAppAnalytics(pathname) {
42
43
  screen_name: pathname,
43
44
  screen_class: pathname,
44
45
  previous_screen_class: prevPathname ?? void 0,
45
- engagement_time_msec: prevPathname ? session.flush() : void 0
46
+ engagement_time_msec: prevPathname ? getSession().flush() : void 0
46
47
  });
47
48
  }, [pathname]);
48
49
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-app-analytics.mjs","names":[],"sources":["../../src/hooks/use-app-analytics.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport { AppState } from 'react-native';\nimport { keys } from '../constants/storage';\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 if (config.storage.getItem(keys.first_open_time)) return;\n track('first_open', { screen_name: pathname, screen_class: pathname });\n config.storage.setItem(keys.first_open_time, 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, trigger: 'background' });\n}\n\nexport function useAppAnalytics(pathname: string) {\n const prevPathname = usePrevious(pathname);\n\n useEffect(() => {\n sendFirstOpen(pathname);\n track('session_start', undefined);\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":";;;;;;;;AAQA,SAAS,cAAc,UAAkB;CACvC,IAAI,OAAO,QAAQ,QAAQ,KAAK,eAAe,GAAG;CAClD,MAAM,cAAc;EAAE,aAAa;EAAU,cAAc;CAAS,CAAC;CACrE,OAAO,QAAQ,QAAQ,KAAK,kCAAiB,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC;AACvE;AAEA,SAAS,qBAAqB;CAC5B,MAAM,uBAAuB,QAAQ,MAAM;CAC3C,IAAI,wBAAwB,GAAG;CAC/B,MAAM,mBAAmB;EAAE;EAAsB,SAAS;CAAa,CAAC;AAC1E;AAEA,SAAgB,gBAAgB,UAAkB;CAChD,MAAM,eAAe,YAAY,QAAQ;CAEzC,gBAAgB;EACd,cAAc,QAAQ;EACtB,MAAM,iBAAiB,KAAA,CAAS;EAEhC,MAAM,eAAe,SAAS,iBAAiB,WAAW,UAAU;GAClE,QAAQ,kBAAkB;GAE1B,IAAI,UAAU,YAAY,CAAC,QAAQ,SAAS,GAC1C,QAAQ,aAAa,IAAI;QAGtB,IAAI,UAAU,YAAY,QAAQ,SAAS,GAAG;IACjD,QAAQ,aAAa,KAAK;IAC1B,mBAAmB;GACrB;EACF,CAAC;EAED,aAAa,aAAa,OAAO;CACnC,GAAG,CAAC,CAAC;CAGL,gBAAgB;EACd,MAAM,eAAe;GACnB,aAAa;GACb,cAAc;GACd,uBAAuB,gBAAgB,KAAA;GACvC,sBAAsB,eAAe,QAAQ,MAAM,IAAI,KAAA;EACzD,CAAC;CACH,GAAG,CAAC,QAAQ,CAAC;AACf"}
1
+ {"version":3,"file":"use-app-analytics.mjs","names":[],"sources":["../../src/hooks/use-app-analytics.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport { AppState } from 'react-native';\nimport { keys } from '../constants/storage';\nimport { config } from '../setup/index';\nimport { getSession } from '../setup/session';\nimport { track } from '../track/index';\nimport { usePrevious } from './use-previous';\n\nfunction sendFirstOpen(pathname: string) {\n if (config.storage.getItem(keys.first_open_time)) return;\n track('first_open', { screen_name: pathname, screen_class: pathname });\n config.storage.setItem(keys.first_open_time, new Date().toISOString());\n}\n\nfunction sendUserEngagement() {\n const engagement_time_msec = getSession().flush();\n if (engagement_time_msec <= 0) return;\n track('user_engagement', { engagement_time_msec, trigger: 'background' });\n}\n\nexport function useAppAnalytics(pathname: string) {\n const prevPathname = usePrevious(pathname);\n\n useEffect(() => {\n const session = getSession();\n\n sendFirstOpen(pathname);\n track('session_start', undefined);\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 ? getSession().flush() : undefined,\n });\n }, [pathname]);\n}\n"],"mappings":";;;;;;;;AAQA,SAAS,cAAc,UAAkB;CACvC,IAAI,OAAO,QAAQ,QAAQ,KAAK,eAAe,GAAG;CAClD,MAAM,cAAc;EAAE,aAAa;EAAU,cAAc;CAAS,CAAC;CACrE,OAAO,QAAQ,QAAQ,KAAK,kCAAiB,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC;AACvE;AAEA,SAAS,qBAAqB;CAC5B,MAAM,uBAAuB,WAAW,CAAC,CAAC,MAAM;CAChD,IAAI,wBAAwB,GAAG;CAC/B,MAAM,mBAAmB;EAAE;EAAsB,SAAS;CAAa,CAAC;AAC1E;AAEA,SAAgB,gBAAgB,UAAkB;CAChD,MAAM,eAAe,YAAY,QAAQ;CAEzC,gBAAgB;EACd,MAAM,UAAU,WAAW;EAE3B,cAAc,QAAQ;EACtB,MAAM,iBAAiB,KAAA,CAAS;EAEhC,MAAM,eAAe,SAAS,iBAAiB,WAAW,UAAU;GAClE,QAAQ,kBAAkB;GAE1B,IAAI,UAAU,YAAY,CAAC,QAAQ,SAAS,GAC1C,QAAQ,aAAa,IAAI;QAGtB,IAAI,UAAU,YAAY,QAAQ,SAAS,GAAG;IACjD,QAAQ,aAAa,KAAK;IAC1B,mBAAmB;GACrB;EACF,CAAC;EAED,aAAa,aAAa,OAAO;CACnC,GAAG,CAAC,CAAC;CAGL,gBAAgB;EACd,MAAM,eAAe;GACnB,aAAa;GACb,cAAc;GACd,uBAAuB,gBAAgB,KAAA;GACvC,sBAAsB,eAAe,WAAW,CAAC,CAAC,MAAM,IAAI,KAAA;EAC9D,CAAC;CACH,GAAG,CAAC,QAAQ,CAAC;AACf"}
@@ -18,7 +18,7 @@ function sendFirstVisit(pathname) {
18
18
  require_setup_index.config.storage.setItem(require_constants_storage.keys.first_visit_time, (/* @__PURE__ */ new Date()).toISOString());
19
19
  }
20
20
  function sendUserEngagement(trigger) {
21
- const engagement_time_msec = require_setup_session.session.flush();
21
+ const engagement_time_msec = require_setup_session.getSession().flush();
22
22
  if (engagement_time_msec <= 0) return;
23
23
  require_track_index.sendBeacon("user_engagement", {
24
24
  engagement_time_msec,
@@ -26,7 +26,7 @@ function sendUserEngagement(trigger) {
26
26
  });
27
27
  }
28
28
  function sendScroll() {
29
- const engagement_time_msec = require_setup_session.session.flush();
29
+ const engagement_time_msec = require_setup_session.getSession().flush();
30
30
  if (engagement_time_msec <= 0) return;
31
31
  require_track_index.track("scroll", { engagement_time_msec });
32
32
  }
@@ -38,11 +38,11 @@ function getScrollPercent() {
38
38
  return (scrollTop + windowHeight) * 100 / docHeight;
39
39
  }
40
40
  function onPageHide() {
41
- require_setup_session.session.pagehide();
41
+ require_setup_session.getSession().pagehide();
42
42
  sendUserEngagement("pagehide");
43
43
  }
44
44
  function onVisibilityChange() {
45
- require_setup_session.session.visibilitychange(document.visibilityState);
45
+ require_setup_session.getSession().visibilitychange(document.visibilityState);
46
46
  if (document.visibilityState === "hidden") sendUserEngagement("visibilitychange");
47
47
  }
48
48
  /**
@@ -57,10 +57,11 @@ function useWebAnalytics(pathname) {
57
57
  hasSendScroll.current = false;
58
58
  }, [pathname]);
59
59
  (0, react.useEffect)(() => {
60
+ const session = require_setup_session.getSession();
60
61
  sendFirstVisit(pathname);
61
62
  require_track_index.track("session_start", void 0);
62
63
  const onScroll = (0, _shware_utils.throttle)(() => {
63
- require_setup_session.session.updateAccumulator();
64
+ session.updateAccumulator();
64
65
  if (hasSendScroll.current) return;
65
66
  if (getScrollPercent() < 90) return;
66
67
  hasSendScroll.current = true;
@@ -71,11 +72,11 @@ function useWebAnalytics(pathname) {
71
72
  "keydown",
72
73
  "touchstart"
73
74
  ];
74
- const checkpoint = (0, _shware_utils.throttle)(require_setup_session.session.updateAccumulator, 1e3);
75
- window.addEventListener("focus", require_setup_session.session.focus);
76
- window.addEventListener("blur", require_setup_session.session.blur);
75
+ const checkpoint = (0, _shware_utils.throttle)(session.updateAccumulator, 1e3);
76
+ window.addEventListener("focus", session.focus);
77
+ window.addEventListener("blur", session.blur);
77
78
  window.addEventListener("scroll", onScroll, { passive: true });
78
- window.addEventListener("pageshow", require_setup_session.session.pageshow);
79
+ window.addEventListener("pageshow", session.pageshow);
79
80
  window.addEventListener("pagehide", onPageHide);
80
81
  document.addEventListener("visibilitychange", onVisibilityChange);
81
82
  checkpointEvents.forEach((e) => {
@@ -85,10 +86,10 @@ function useWebAnalytics(pathname) {
85
86
  });
86
87
  });
87
88
  return () => {
88
- window.removeEventListener("focus", require_setup_session.session.focus);
89
- window.removeEventListener("blur", require_setup_session.session.blur);
89
+ window.removeEventListener("focus", session.focus);
90
+ window.removeEventListener("blur", session.blur);
90
91
  window.removeEventListener("scroll", onScroll);
91
- window.removeEventListener("pageshow", require_setup_session.session.pageshow);
92
+ window.removeEventListener("pageshow", session.pageshow);
92
93
  window.removeEventListener("pagehide", onPageHide);
93
94
  document.removeEventListener("visibilitychange", onVisibilityChange);
94
95
  checkpointEvents.forEach((e) => window.removeEventListener(e, checkpoint));
@@ -103,7 +104,7 @@ function useWebAnalytics(pathname) {
103
104
  page_referrer: document.referrer,
104
105
  page_location: window.location.href,
105
106
  previous_page_path: prevPathname ?? void 0,
106
- engagement_time_msec: prevPathname ? require_setup_session.session.flush() : void 0
107
+ engagement_time_msec: prevPathname ? require_setup_session.getSession().flush() : void 0
107
108
  });
108
109
  }, [pathname]);
109
110
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-web-analytics.cjs","names":["config","keys","session","usePrevious"],"sources":["../../src/hooks/use-web-analytics.ts"],"sourcesContent":["import { throttle } from '@shware/utils';\nimport { useEffect, useRef } from 'react';\nimport { keys } from '../constants/storage';\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 if (config.storage.getItem(keys.first_visit_time)) return;\n track('first_visit', {\n page_path: pathname,\n page_title: document.title,\n page_referrer: document.referrer,\n page_location: window.location.href,\n });\n config.storage.setItem(keys.first_visit_time, new Date().toISOString());\n}\n\nfunction sendUserEngagement(trigger: 'pagehide' | 'visibilitychange') {\n const engagement_time_msec = session.flush();\n if (engagement_time_msec <= 0) return;\n sendBeacon('user_engagement', { engagement_time_msec, trigger });\n}\n\nfunction sendScroll() {\n const engagement_time_msec = session.flush();\n if (engagement_time_msec <= 0) return;\n track('scroll', { engagement_time_msec });\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('pagehide');\n}\n\nfunction onVisibilityChange() {\n session.visibilitychange(document.visibilityState);\n if (document.visibilityState === 'hidden') {\n sendUserEngagement('visibilitychange');\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);\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);\n window.addEventListener('blur', session.blur);\n window.addEventListener('scroll', onScroll, { passive: true });\n window.addEventListener('pageshow', session.pageshow);\n window.addEventListener('pagehide', onPageHide);\n document.addEventListener('visibilitychange', onVisibilityChange);\n\n // save checkpoint\n checkpointEvents.forEach((e) => {\n window.addEventListener(e, checkpoint, { passive: true, capture: true });\n });\n\n return () => {\n window.removeEventListener('focus', session.focus);\n window.removeEventListener('blur', session.blur);\n window.removeEventListener('scroll', onScroll);\n window.removeEventListener('pageshow', session.pageshow);\n window.removeEventListener('pagehide', onPageHide);\n document.removeEventListener('visibilitychange', onVisibilityChange);\n checkpointEvents.forEach((e) => window.removeEventListener(e, checkpoint));\n\n onScroll.cancel();\n checkpoint.cancel();\n };\n }, []);\n\n useEffect(() => {\n track('page_view', {\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 }, [pathname]);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-web-analytics.cjs","names":["config","keys","getSession","usePrevious"],"sources":["../../src/hooks/use-web-analytics.ts"],"sourcesContent":["import { throttle } from '@shware/utils';\nimport { useEffect, useRef } from 'react';\nimport { keys } from '../constants/storage';\nimport { config } from '../setup/index';\nimport { getSession } from '../setup/session';\nimport { sendBeacon, track } from '../track/index';\nimport { usePrevious } from './use-previous';\n\nfunction sendFirstVisit(pathname: string) {\n if (config.storage.getItem(keys.first_visit_time)) return;\n track('first_visit', {\n page_path: pathname,\n page_title: document.title,\n page_referrer: document.referrer,\n page_location: window.location.href,\n });\n config.storage.setItem(keys.first_visit_time, new Date().toISOString());\n}\n\nfunction sendUserEngagement(trigger: 'pagehide' | 'visibilitychange') {\n const engagement_time_msec = getSession().flush();\n if (engagement_time_msec <= 0) return;\n sendBeacon('user_engagement', { engagement_time_msec, trigger });\n}\n\nfunction sendScroll() {\n const engagement_time_msec = getSession().flush();\n if (engagement_time_msec <= 0) return;\n track('scroll', { engagement_time_msec });\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 getSession().pagehide();\n sendUserEngagement('pagehide');\n}\n\nfunction onVisibilityChange() {\n getSession().visibilitychange(document.visibilityState);\n if (document.visibilityState === 'hidden') {\n sendUserEngagement('visibilitychange');\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 // One lookup for the whole effect: `addEventListener` and its matching\n // `removeEventListener` have to be handed the very same function.\n const session = getSession();\n\n sendFirstVisit(pathname);\n track('session_start', undefined);\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);\n window.addEventListener('blur', session.blur);\n window.addEventListener('scroll', onScroll, { passive: true });\n window.addEventListener('pageshow', session.pageshow);\n window.addEventListener('pagehide', onPageHide);\n document.addEventListener('visibilitychange', onVisibilityChange);\n\n // save checkpoint\n checkpointEvents.forEach((e) => {\n window.addEventListener(e, checkpoint, { passive: true, capture: true });\n });\n\n return () => {\n window.removeEventListener('focus', session.focus);\n window.removeEventListener('blur', session.blur);\n window.removeEventListener('scroll', onScroll);\n window.removeEventListener('pageshow', session.pageshow);\n window.removeEventListener('pagehide', onPageHide);\n document.removeEventListener('visibilitychange', onVisibilityChange);\n checkpointEvents.forEach((e) => window.removeEventListener(e, checkpoint));\n\n onScroll.cancel();\n checkpoint.cancel();\n };\n }, []);\n\n useEffect(() => {\n track('page_view', {\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 ? getSession().flush() : undefined,\n });\n }, [pathname]);\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { config } from "../setup/index.mjs";
2
- import { session } from "../setup/session.mjs";
2
+ import { getSession } from "../setup/session.mjs";
3
3
  import { keys } from "../constants/storage.mjs";
4
4
  import { sendBeacon, track } from "../track/index.mjs";
5
5
  import { usePrevious } from "./use-previous.mjs";
@@ -17,7 +17,7 @@ function sendFirstVisit(pathname) {
17
17
  config.storage.setItem(keys.first_visit_time, (/* @__PURE__ */ new Date()).toISOString());
18
18
  }
19
19
  function sendUserEngagement(trigger) {
20
- const engagement_time_msec = session.flush();
20
+ const engagement_time_msec = getSession().flush();
21
21
  if (engagement_time_msec <= 0) return;
22
22
  sendBeacon("user_engagement", {
23
23
  engagement_time_msec,
@@ -25,7 +25,7 @@ function sendUserEngagement(trigger) {
25
25
  });
26
26
  }
27
27
  function sendScroll() {
28
- const engagement_time_msec = session.flush();
28
+ const engagement_time_msec = getSession().flush();
29
29
  if (engagement_time_msec <= 0) return;
30
30
  track("scroll", { engagement_time_msec });
31
31
  }
@@ -37,11 +37,11 @@ function getScrollPercent() {
37
37
  return (scrollTop + windowHeight) * 100 / docHeight;
38
38
  }
39
39
  function onPageHide() {
40
- session.pagehide();
40
+ getSession().pagehide();
41
41
  sendUserEngagement("pagehide");
42
42
  }
43
43
  function onVisibilityChange() {
44
- session.visibilitychange(document.visibilityState);
44
+ getSession().visibilitychange(document.visibilityState);
45
45
  if (document.visibilityState === "hidden") sendUserEngagement("visibilitychange");
46
46
  }
47
47
  /**
@@ -56,6 +56,7 @@ function useWebAnalytics(pathname) {
56
56
  hasSendScroll.current = false;
57
57
  }, [pathname]);
58
58
  useEffect(() => {
59
+ const session = getSession();
59
60
  sendFirstVisit(pathname);
60
61
  track("session_start", void 0);
61
62
  const onScroll = throttle(() => {
@@ -102,7 +103,7 @@ function useWebAnalytics(pathname) {
102
103
  page_referrer: document.referrer,
103
104
  page_location: window.location.href,
104
105
  previous_page_path: prevPathname ?? void 0,
105
- engagement_time_msec: prevPathname ? session.flush() : void 0
106
+ engagement_time_msec: prevPathname ? getSession().flush() : void 0
106
107
  });
107
108
  }, [pathname]);
108
109
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-web-analytics.mjs","names":[],"sources":["../../src/hooks/use-web-analytics.ts"],"sourcesContent":["import { throttle } from '@shware/utils';\nimport { useEffect, useRef } from 'react';\nimport { keys } from '../constants/storage';\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 if (config.storage.getItem(keys.first_visit_time)) return;\n track('first_visit', {\n page_path: pathname,\n page_title: document.title,\n page_referrer: document.referrer,\n page_location: window.location.href,\n });\n config.storage.setItem(keys.first_visit_time, new Date().toISOString());\n}\n\nfunction sendUserEngagement(trigger: 'pagehide' | 'visibilitychange') {\n const engagement_time_msec = session.flush();\n if (engagement_time_msec <= 0) return;\n sendBeacon('user_engagement', { engagement_time_msec, trigger });\n}\n\nfunction sendScroll() {\n const engagement_time_msec = session.flush();\n if (engagement_time_msec <= 0) return;\n track('scroll', { engagement_time_msec });\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('pagehide');\n}\n\nfunction onVisibilityChange() {\n session.visibilitychange(document.visibilityState);\n if (document.visibilityState === 'hidden') {\n sendUserEngagement('visibilitychange');\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);\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);\n window.addEventListener('blur', session.blur);\n window.addEventListener('scroll', onScroll, { passive: true });\n window.addEventListener('pageshow', session.pageshow);\n window.addEventListener('pagehide', onPageHide);\n document.addEventListener('visibilitychange', onVisibilityChange);\n\n // save checkpoint\n checkpointEvents.forEach((e) => {\n window.addEventListener(e, checkpoint, { passive: true, capture: true });\n });\n\n return () => {\n window.removeEventListener('focus', session.focus);\n window.removeEventListener('blur', session.blur);\n window.removeEventListener('scroll', onScroll);\n window.removeEventListener('pageshow', session.pageshow);\n window.removeEventListener('pagehide', onPageHide);\n document.removeEventListener('visibilitychange', onVisibilityChange);\n checkpointEvents.forEach((e) => window.removeEventListener(e, checkpoint));\n\n onScroll.cancel();\n checkpoint.cancel();\n };\n }, []);\n\n useEffect(() => {\n track('page_view', {\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 }, [pathname]);\n}\n"],"mappings":";;;;;;;;AAQA,SAAS,eAAe,UAAkB;CACxC,IAAI,OAAO,QAAQ,QAAQ,KAAK,gBAAgB,GAAG;CACnD,MAAM,eAAe;EACnB,WAAW;EACX,YAAY,SAAS;EACrB,eAAe,SAAS;EACxB,eAAe,OAAO,SAAS;CACjC,CAAC;CACD,OAAO,QAAQ,QAAQ,KAAK,mCAAkB,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC;AACxE;AAEA,SAAS,mBAAmB,SAA0C;CACpE,MAAM,uBAAuB,QAAQ,MAAM;CAC3C,IAAI,wBAAwB,GAAG;CAC/B,WAAW,mBAAmB;EAAE;EAAsB;CAAQ,CAAC;AACjE;AAEA,SAAS,aAAa;CACpB,MAAM,uBAAuB,QAAQ,MAAM;CAC3C,IAAI,wBAAwB,GAAG;CAC/B,MAAM,UAAU,EAAE,qBAAqB,CAAC;AAC1C;AAEA,SAAS,mBAAmB;CAC1B,MAAM,YAAY,OAAO,WAAW,SAAS,gBAAgB;CAC7D,MAAM,eAAe,OAAO;CAC5B,MAAM,YAAY,SAAS,gBAAgB;CAC3C,IAAI,cAAc,GAAG,OAAO;CAC5B,QAAS,YAAY,gBAAgB,MAAO;AAC9C;AAEA,SAAS,aAAa;CACpB,QAAQ,SAAS;CACjB,mBAAmB,UAAU;AAC/B;AAEA,SAAS,qBAAqB;CAC5B,QAAQ,iBAAiB,SAAS,eAAe;CACjD,IAAI,SAAS,oBAAoB,UAC/B,mBAAmB,kBAAkB;AAEzC;;;;;;AAOA,SAAgB,gBAAgB,UAAkB;CAChD,MAAM,eAAe,YAAY,QAAQ;CAGzC,MAAM,gBAAgB,OAAO,KAAK;CAClC,gBAAgB;EACd,cAAc,UAAU;CAC1B,GAAG,CAAC,QAAQ,CAAC;CAEb,gBAAgB;EACd,eAAe,QAAQ;EACvB,MAAM,iBAAiB,KAAA,CAAS;EAEhC,MAAM,WAAW,eAAe;GAC9B,QAAQ,kBAAkB;GAC1B,IAAI,cAAc,SAAS;GAE3B,IAAI,iBAAiB,IAAI,IAAI;GAC7B,cAAc,UAAU;GACxB,WAAW;EACb,GAAG,GAAG;EAEN,MAAM,mBAAmB;GAAC;GAAa;GAAW;EAAY;EAC9D,MAAM,aAAa,SAAS,QAAQ,mBAAmB,GAAI;EAE3D,OAAO,iBAAiB,SAAS,QAAQ,KAAK;EAC9C,OAAO,iBAAiB,QAAQ,QAAQ,IAAI;EAC5C,OAAO,iBAAiB,UAAU,UAAU,EAAE,SAAS,KAAK,CAAC;EAC7D,OAAO,iBAAiB,YAAY,QAAQ,QAAQ;EACpD,OAAO,iBAAiB,YAAY,UAAU;EAC9C,SAAS,iBAAiB,oBAAoB,kBAAkB;EAGhE,iBAAiB,SAAS,MAAM;GAC9B,OAAO,iBAAiB,GAAG,YAAY;IAAE,SAAS;IAAM,SAAS;GAAK,CAAC;EACzE,CAAC;EAED,aAAa;GACX,OAAO,oBAAoB,SAAS,QAAQ,KAAK;GACjD,OAAO,oBAAoB,QAAQ,QAAQ,IAAI;GAC/C,OAAO,oBAAoB,UAAU,QAAQ;GAC7C,OAAO,oBAAoB,YAAY,QAAQ,QAAQ;GACvD,OAAO,oBAAoB,YAAY,UAAU;GACjD,SAAS,oBAAoB,oBAAoB,kBAAkB;GACnE,iBAAiB,SAAS,MAAM,OAAO,oBAAoB,GAAG,UAAU,CAAC;GAEzE,SAAS,OAAO;GAChB,WAAW,OAAO;EACpB;CACF,GAAG,CAAC,CAAC;CAEL,gBAAgB;EACd,MAAM,aAAa;GACjB,WAAW;GACX,YAAY,SAAS;GACrB,eAAe,SAAS;GACxB,eAAe,OAAO,SAAS;GAC/B,oBAAoB,gBAAgB,KAAA;GACpC,sBAAsB,eAAe,QAAQ,MAAM,IAAI,KAAA;EACzD,CAAC;CACH,GAAG,CAAC,QAAQ,CAAC;AACf"}
1
+ {"version":3,"file":"use-web-analytics.mjs","names":[],"sources":["../../src/hooks/use-web-analytics.ts"],"sourcesContent":["import { throttle } from '@shware/utils';\nimport { useEffect, useRef } from 'react';\nimport { keys } from '../constants/storage';\nimport { config } from '../setup/index';\nimport { getSession } from '../setup/session';\nimport { sendBeacon, track } from '../track/index';\nimport { usePrevious } from './use-previous';\n\nfunction sendFirstVisit(pathname: string) {\n if (config.storage.getItem(keys.first_visit_time)) return;\n track('first_visit', {\n page_path: pathname,\n page_title: document.title,\n page_referrer: document.referrer,\n page_location: window.location.href,\n });\n config.storage.setItem(keys.first_visit_time, new Date().toISOString());\n}\n\nfunction sendUserEngagement(trigger: 'pagehide' | 'visibilitychange') {\n const engagement_time_msec = getSession().flush();\n if (engagement_time_msec <= 0) return;\n sendBeacon('user_engagement', { engagement_time_msec, trigger });\n}\n\nfunction sendScroll() {\n const engagement_time_msec = getSession().flush();\n if (engagement_time_msec <= 0) return;\n track('scroll', { engagement_time_msec });\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 getSession().pagehide();\n sendUserEngagement('pagehide');\n}\n\nfunction onVisibilityChange() {\n getSession().visibilitychange(document.visibilityState);\n if (document.visibilityState === 'hidden') {\n sendUserEngagement('visibilitychange');\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 // One lookup for the whole effect: `addEventListener` and its matching\n // `removeEventListener` have to be handed the very same function.\n const session = getSession();\n\n sendFirstVisit(pathname);\n track('session_start', undefined);\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);\n window.addEventListener('blur', session.blur);\n window.addEventListener('scroll', onScroll, { passive: true });\n window.addEventListener('pageshow', session.pageshow);\n window.addEventListener('pagehide', onPageHide);\n document.addEventListener('visibilitychange', onVisibilityChange);\n\n // save checkpoint\n checkpointEvents.forEach((e) => {\n window.addEventListener(e, checkpoint, { passive: true, capture: true });\n });\n\n return () => {\n window.removeEventListener('focus', session.focus);\n window.removeEventListener('blur', session.blur);\n window.removeEventListener('scroll', onScroll);\n window.removeEventListener('pageshow', session.pageshow);\n window.removeEventListener('pagehide', onPageHide);\n document.removeEventListener('visibilitychange', onVisibilityChange);\n checkpointEvents.forEach((e) => window.removeEventListener(e, checkpoint));\n\n onScroll.cancel();\n checkpoint.cancel();\n };\n }, []);\n\n useEffect(() => {\n track('page_view', {\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 ? getSession().flush() : undefined,\n });\n }, [pathname]);\n}\n"],"mappings":";;;;;;;;AAQA,SAAS,eAAe,UAAkB;CACxC,IAAI,OAAO,QAAQ,QAAQ,KAAK,gBAAgB,GAAG;CACnD,MAAM,eAAe;EACnB,WAAW;EACX,YAAY,SAAS;EACrB,eAAe,SAAS;EACxB,eAAe,OAAO,SAAS;CACjC,CAAC;CACD,OAAO,QAAQ,QAAQ,KAAK,mCAAkB,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC;AACxE;AAEA,SAAS,mBAAmB,SAA0C;CACpE,MAAM,uBAAuB,WAAW,CAAC,CAAC,MAAM;CAChD,IAAI,wBAAwB,GAAG;CAC/B,WAAW,mBAAmB;EAAE;EAAsB;CAAQ,CAAC;AACjE;AAEA,SAAS,aAAa;CACpB,MAAM,uBAAuB,WAAW,CAAC,CAAC,MAAM;CAChD,IAAI,wBAAwB,GAAG;CAC/B,MAAM,UAAU,EAAE,qBAAqB,CAAC;AAC1C;AAEA,SAAS,mBAAmB;CAC1B,MAAM,YAAY,OAAO,WAAW,SAAS,gBAAgB;CAC7D,MAAM,eAAe,OAAO;CAC5B,MAAM,YAAY,SAAS,gBAAgB;CAC3C,IAAI,cAAc,GAAG,OAAO;CAC5B,QAAS,YAAY,gBAAgB,MAAO;AAC9C;AAEA,SAAS,aAAa;CACpB,WAAW,CAAC,CAAC,SAAS;CACtB,mBAAmB,UAAU;AAC/B;AAEA,SAAS,qBAAqB;CAC5B,WAAW,CAAC,CAAC,iBAAiB,SAAS,eAAe;CACtD,IAAI,SAAS,oBAAoB,UAC/B,mBAAmB,kBAAkB;AAEzC;;;;;;AAOA,SAAgB,gBAAgB,UAAkB;CAChD,MAAM,eAAe,YAAY,QAAQ;CAGzC,MAAM,gBAAgB,OAAO,KAAK;CAClC,gBAAgB;EACd,cAAc,UAAU;CAC1B,GAAG,CAAC,QAAQ,CAAC;CAEb,gBAAgB;EAGd,MAAM,UAAU,WAAW;EAE3B,eAAe,QAAQ;EACvB,MAAM,iBAAiB,KAAA,CAAS;EAEhC,MAAM,WAAW,eAAe;GAC9B,QAAQ,kBAAkB;GAC1B,IAAI,cAAc,SAAS;GAE3B,IAAI,iBAAiB,IAAI,IAAI;GAC7B,cAAc,UAAU;GACxB,WAAW;EACb,GAAG,GAAG;EAEN,MAAM,mBAAmB;GAAC;GAAa;GAAW;EAAY;EAC9D,MAAM,aAAa,SAAS,QAAQ,mBAAmB,GAAI;EAE3D,OAAO,iBAAiB,SAAS,QAAQ,KAAK;EAC9C,OAAO,iBAAiB,QAAQ,QAAQ,IAAI;EAC5C,OAAO,iBAAiB,UAAU,UAAU,EAAE,SAAS,KAAK,CAAC;EAC7D,OAAO,iBAAiB,YAAY,QAAQ,QAAQ;EACpD,OAAO,iBAAiB,YAAY,UAAU;EAC9C,SAAS,iBAAiB,oBAAoB,kBAAkB;EAGhE,iBAAiB,SAAS,MAAM;GAC9B,OAAO,iBAAiB,GAAG,YAAY;IAAE,SAAS;IAAM,SAAS;GAAK,CAAC;EACzE,CAAC;EAED,aAAa;GACX,OAAO,oBAAoB,SAAS,QAAQ,KAAK;GACjD,OAAO,oBAAoB,QAAQ,QAAQ,IAAI;GAC/C,OAAO,oBAAoB,UAAU,QAAQ;GAC7C,OAAO,oBAAoB,YAAY,QAAQ,QAAQ;GACvD,OAAO,oBAAoB,YAAY,UAAU;GACjD,SAAS,oBAAoB,oBAAoB,kBAAkB;GACnE,iBAAiB,SAAS,MAAM,OAAO,oBAAoB,GAAG,UAAU,CAAC;GAEzE,SAAS,OAAO;GAChB,WAAW,OAAO;EACpB;CACF,GAAG,CAAC,CAAC;CAEL,gBAAgB;EACd,MAAM,aAAa;GACjB,WAAW;GACX,YAAY,SAAS;GACrB,eAAe,SAAS;GACxB,eAAe,OAAO,SAAS;GAC/B,oBAAoB,gBAAgB,KAAA;GACpC,sBAAsB,eAAe,WAAW,CAAC,CAAC,MAAM,IAAI,KAAA;EAC9D,CAAC;CACH,GAAG,CAAC,QAAQ,CAAC;AACf"}
@@ -141,19 +141,42 @@ function getServerEvent(event, data, appPackageName) {
141
141
  }
142
142
  return serverEvent;
143
143
  }
144
+ /**
145
+ * Meta's SDK rejects on API errors, where every other conversion sender here logs and resolves.
146
+ * Keep the failure inside this module, so one rejected batch cannot fail the caller's request.
147
+ * Never log the error itself: it also carries the access token, in both `url` and `data`.
148
+ */
149
+ function logError(error) {
150
+ const { status, message, response } = error ?? {};
151
+ if (typeof status !== "number") {
152
+ console.error(`Failed to send Meta conversion, network error: ${message ?? String(error)}`);
153
+ return;
154
+ }
155
+ console.error(`Failed to send Meta conversion, status: ${status}, body: ${JSON.stringify(response)}`);
156
+ }
144
157
  async function sendEvent(accessToken, pixelId, event, data = {}, appPackageName) {
145
158
  if (require_third_parties_ignored_events.IGNORED_EVENTS.includes(event.name)) return void 0;
146
159
  const request = new facebook_nodejs_business_sdk.EventRequest(accessToken, pixelId);
147
160
  const fbEvent = getServerEvent(event, data, appPackageName);
148
161
  request.setEvents([fbEvent]);
149
- return request.execute();
162
+ try {
163
+ return await request.execute();
164
+ } catch (error) {
165
+ logError(error);
166
+ return;
167
+ }
150
168
  }
151
169
  async function sendEvents(accessToken, pixelId, events, data = {}, appPackageName) {
152
170
  const fbEvents = events.filter((event) => !require_third_parties_ignored_events.IGNORED_EVENTS.includes(event.name)).map((event) => getServerEvent(event, data, appPackageName));
153
171
  if (fbEvents.length === 0) return void 0;
154
172
  const request = new facebook_nodejs_business_sdk.EventRequest(accessToken, pixelId);
155
173
  request.setEvents(fbEvents);
156
- return request.execute();
174
+ try {
175
+ return await request.execute();
176
+ } catch (error) {
177
+ logError(error);
178
+ return;
179
+ }
157
180
  }
158
181
  async function sendTestEvent(accessToken, pixelId, testEventCode) {
159
182
  const extinfo = new facebook_nodejs_business_sdk.ExtendedDeviceInfo().setExtInfoVersion("a2").setAppPackageName("com.some.app").setShortVersion("771").setLongVersion("Version 7.7.1").setOsVersion("10.1.1").setDeviceModelName("OnePlus6").setLocale("en_US").setTimezoneAbbreviation("GMT-1").setCarrier("TMobile").setScreenWidth(1920).setScreenHeight(1080).setScreenDensity("2.00").setCpuCoreCount(2).setTotalDiskSpaceGb(128).setFreeDiskSpaceGb(8).setDeviceTimeZone("USA/New York");
@@ -1 +1 @@
1
- {"version":3,"file":"meta-conversions-api.cjs","names":["UserData","ExtendedDeviceInfo","AppData","CustomData","mapFBEvent","Content","ServerEvent","IGNORED_EVENTS","EventRequest"],"sources":["../../src/server/meta-conversions-api.ts"],"sourcesContent":["import {\n AppData,\n Content,\n CustomData,\n EventRequest,\n ExtendedDeviceInfo,\n ServerEvent,\n UserData,\n} from 'facebook-nodejs-business-sdk';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { mapFBEvent } from '../track/fbq';\nimport type { TrackEvent, TrackTags, UserProvidedData } from '../track/types';\n\nconst USER_ASSIGNED_COUNTRIES: string[] = ['xk'];\nfunction normalizeCountry(input: string | undefined): string | undefined {\n const country = input?.split(/[-_]/).at(0)?.toLowerCase();\n if (!country) return undefined;\n return USER_ASSIGNED_COUNTRIES.includes(country) ? undefined : country;\n}\n\nfunction getUserData(tags: TrackTags, data: UserProvidedData) {\n const userData = new UserData();\n\n // set user-provided data\n if (data.email) {\n if (Array.isArray(data.email)) {\n userData.setEmails(data.email);\n } else {\n userData.setEmail(data.email);\n }\n }\n if (data.phone_number) {\n if (Array.isArray(data.phone_number)) {\n userData.setPhones(data.phone_number);\n } else {\n userData.setPhone(data.phone_number);\n }\n }\n if (data.gender) {\n if (data.gender === 'female') {\n userData.setGender('f');\n // oxlint-disable-next-line typescript/no-unnecessary-condition -- user-provided data may carry values outside the declared union at runtime\n } else if (data.gender === 'male') {\n userData.setGender('m');\n }\n }\n if (data.address) {\n if (Array.isArray(data.address)) {\n const firstNames = data.address.map((a) => a.first_name).filter(Boolean);\n const lastNames = data.address.map((a) => a.last_name).filter(Boolean);\n const cities = data.address.map((a) => a.city).filter(Boolean);\n const states = data.address.map((a) => a.region).filter(Boolean);\n const postalCodes = data.address.map((a) => a.postal_code).filter(Boolean);\n const countries = data.address.map((a) => normalizeCountry(a.country)).filter(Boolean);\n\n userData.setFirstNames(firstNames as string[]);\n userData.setLastNames(lastNames as string[]);\n userData.setCities(cities as string[]);\n userData.setStates(states as string[]);\n userData.setZips(postalCodes as string[]);\n userData.setCountries(countries as string[]);\n } else {\n if (data.address.first_name) {\n userData.setFirstName(data.address.first_name);\n userData.setF5First(data.address.first_name.slice(0, 5));\n }\n if (data.address.last_name) {\n userData.setLastName(data.address.last_name);\n userData.setF5Last(data.address.last_name.slice(0, 5));\n }\n if (data.address.city) userData.setCity(data.address.city);\n if (data.address.region) userData.setState(data.address.region);\n if (data.address.postal_code) userData.setZip(data.address.postal_code);\n if (data.address.country) {\n const country = normalizeCountry(data.address.country);\n if (country) userData.setCountry(country);\n }\n }\n }\n if (data.birthday) {\n userData.setDoby(data.birthday.year.toString());\n userData.setDobm(data.birthday.month.toString());\n userData.setDobd(data.birthday.day.toString());\n }\n if (data.user_id && data.user_id.length !== 0) {\n userData.setExternalId(data.user_id);\n }\n if (data.ip_address) {\n userData.setClientIpAddress(data.ip_address);\n }\n if (data.user_agent) {\n userData.setClientUserAgent(data.user_agent);\n }\n if (data.fb_login_id) {\n userData.setFbLoginId(data.fb_login_id);\n }\n if (data.fb_page_id) {\n userData.setPageId(data.fb_page_id);\n }\n\n // set tags info\n if (tags.fbc) {\n userData.setFbc(tags.fbc);\n } else if (tags.fbclid) {\n // ref: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc#2--format-clickid\n // The formatted ClickID value must be of the form `version.subdomainIndex.creationTime.<fbclid>`, where:\n // - version is always this prefix: fb\n // - subdomainIndex is which domain the cookie is defined on ('com' = 0, 'example.com' = 1, 'www.example.com' = 2)\n // - creationTime is the UNIX time since epoch in milliseconds when the _fbc was stored. If you don't save the _fbc cookie, use the timestamp when you first observed or received this fbclid value\n // - <fbclid> is the value for the fbclid query parameter in the page URL.\n\n const fbc = `fb.1.${Date.now()}.${tags.fbclid}`;\n userData.setFbc(fbc);\n }\n\n if (tags.fbp) {\n userData.setFbp(tags.fbp);\n }\n if (tags.advertising_id) {\n userData.setMadid(tags.advertising_id);\n }\n if (tags.ip_address && typeof tags.ip_address === 'string') {\n userData.setClientIpAddress(tags.ip_address);\n }\n\n return userData;\n}\n\nfunction getAppData(tags: TrackTags, appPackageName: string) {\n const extinfo = new ExtendedDeviceInfo();\n if (tags.os_name) {\n if (tags.os_name === 'iOS' || tags.os_name === 'iPadOS') {\n extinfo.setExtInfoVersion('i2');\n } else if (tags.os_name === 'Android') {\n extinfo.setExtInfoVersion('a2');\n }\n }\n extinfo.setAppPackageName(appPackageName);\n const shortVersion = tags.release?.split('.').at(0);\n if (shortVersion) {\n extinfo.setShortVersion(shortVersion);\n }\n if (tags.release) {\n extinfo.setLongVersion(tags.release);\n }\n if (tags.os_version) {\n extinfo.setOsVersion(tags.os_version);\n }\n if (tags.device_model_id) {\n extinfo.setDeviceModelName(tags.device_model_id);\n }\n if (tags.language) {\n extinfo.setLocale(tags.language);\n }\n if (tags.screen_width) {\n extinfo.setScreenWidth(tags.screen_width);\n }\n if (tags.screen_height) {\n extinfo.setScreenHeight(tags.screen_height);\n }\n if (tags.device_pixel_ratio) {\n extinfo.setScreenDensity(tags.device_pixel_ratio.toString());\n }\n\n const appData = new AppData();\n appData.setExtinfo(extinfo);\n if (tags.install_referrer) {\n appData.setInstallReferrer(tags.install_referrer);\n }\n if (tags.advertising_id) {\n appData.setAdvertiserTrackingEnabled(true);\n }\n if (tags.install_referrer) {\n appData.setInstallReferrer(tags.install_referrer);\n }\n\n return appData;\n}\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction getCustomData({ name, properties }: TrackEvent<any>) {\n const data = new CustomData();\n const [_, _name, fbEventProperties] = mapFBEvent(name, properties);\n const {\n value,\n currency,\n content_name,\n content_category,\n content_ids,\n contents,\n content_type,\n // order_id,\n predicted_ltv,\n num_items,\n search_string,\n status,\n // item_number,\n delivery_category,\n ...custom_properties\n } = fbEventProperties;\n // oxlint-disable-next-line typescript/no-unnecessary-condition -- custom event properties may carry `value: null` at runtime despite the declared type\n if (value !== undefined && value !== null) data.setValue(value);\n if (currency) data.setCurrency(currency);\n if (content_name) data.setContentName(content_name);\n if (content_category) data.setContentCategory(content_category);\n if (content_ids) data.setContentIds(content_ids);\n if (contents)\n data.setContents(\n contents.map((c) => {\n const result = new Content().setId(c.id).setQuantity(c.quantity);\n if (c.item_price) result.setItemPrice(c.item_price);\n if (c.title) result.setTitle(c.title);\n if (c.description) result.setDescription(c.description);\n if (c.brand) result.setBrand(c.brand);\n if (c.category) result.setCategory(c.category);\n if (c.delivery_category) result.setDeliveryCategory(c.delivery_category);\n return result;\n })\n );\n if (content_type) data.setContentType(content_type);\n // if (order_id) data.setOrderId(order_id);\n if (predicted_ltv) data.setPredictedLtv(predicted_ltv);\n if (num_items) data.setNumItems(num_items);\n if (search_string) data.setSearchString(search_string);\n if (status) data.setStatus(status.toString());\n // if (item_number) data.setItemNumber(item_number);\n if (delivery_category) data.setDeliveryCategory(delivery_category);\n data.setCustomProperties(custom_properties);\n return data;\n}\n\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData,\n appPackageName?: string\n) {\n const userData = getUserData(event.tags, data);\n const customData = getCustomData(event);\n const [_, eventName] = mapFBEvent(event.name, event.properties);\n const serverEvent = new ServerEvent()\n .setEventId(event.tags.idempotency_key ?? event.id)\n .setEventName(eventName)\n .setEventTime(Math.round(Date.now() / 1000))\n .setUserData(userData)\n .setCustomData(customData);\n\n if (event.tags.source === 'app' && appPackageName) {\n const appData = getAppData(event.tags, appPackageName);\n serverEvent.setAppData(appData);\n }\n if (event.tags.source_url) {\n serverEvent.setEventSourceUrl(event.tags.source_url);\n }\n switch (event.tags.source) {\n case 'app':\n serverEvent.setActionSource('app');\n break;\n case 'web':\n serverEvent.setActionSource('website');\n break;\n default:\n break;\n }\n return serverEvent;\n}\n\nexport async function sendEvent(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData = {},\n appPackageName?: string\n) {\n if (IGNORED_EVENTS.includes(event.name)) return undefined;\n const request = new EventRequest(accessToken, pixelId);\n const fbEvent = getServerEvent(event, data, appPackageName);\n request.setEvents([fbEvent]);\n return request.execute();\n}\n\nexport async function sendEvents(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n appPackageName?: string\n) {\n const fbEvents = events\n .filter((event) => !IGNORED_EVENTS.includes(event.name))\n .map((event) => getServerEvent(event, data, appPackageName));\n if (fbEvents.length === 0) return undefined;\n const request = new EventRequest(accessToken, pixelId);\n request.setEvents(fbEvents);\n return request.execute();\n}\n\nexport async function sendTestEvent(accessToken: string, pixelId: string, testEventCode: string) {\n const extinfo = new ExtendedDeviceInfo()\n .setExtInfoVersion('a2')\n .setAppPackageName('com.some.app')\n .setShortVersion('771')\n .setLongVersion('Version 7.7.1')\n .setOsVersion('10.1.1')\n .setDeviceModelName('OnePlus6')\n .setLocale('en_US')\n .setTimezoneAbbreviation('GMT-1')\n .setCarrier('TMobile')\n .setScreenWidth(1920)\n .setScreenHeight(1080)\n .setScreenDensity('2.00')\n .setCpuCoreCount(2)\n .setTotalDiskSpaceGb(128)\n .setFreeDiskSpaceGb(8)\n .setDeviceTimeZone('USA/New York');\n\n const userData = new UserData().setEmail('test@example.com');\n const appData = new AppData().setExtinfo(extinfo);\n const event = new ServerEvent()\n .setEventId(Math.round(Math.random() * 1000_000).toString())\n .setEventName('TestEvent')\n .setEventTime(Math.round(Date.now() / 1000))\n .setUserData(userData)\n .setAppData(appData)\n .setActionSource('app');\n\n const request = new EventRequest(accessToken, pixelId);\n request.setTestEventCode(testEventCode);\n request.setEvents([event]);\n return request.execute();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"meta-conversions-api.cjs","names":["UserData","ExtendedDeviceInfo","AppData","CustomData","mapFBEvent","Content","ServerEvent","IGNORED_EVENTS","EventRequest"],"sources":["../../src/server/meta-conversions-api.ts"],"sourcesContent":["import {\n AppData,\n Content,\n CustomData,\n EventRequest,\n ExtendedDeviceInfo,\n ServerEvent,\n UserData,\n} from 'facebook-nodejs-business-sdk';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { mapFBEvent } from '../track/fbq';\nimport type { TrackEvent, TrackTags, UserProvidedData } from '../track/types';\n\nconst USER_ASSIGNED_COUNTRIES: string[] = ['xk'];\nfunction normalizeCountry(input: string | undefined): string | undefined {\n const country = input?.split(/[-_]/).at(0)?.toLowerCase();\n if (!country) return undefined;\n return USER_ASSIGNED_COUNTRIES.includes(country) ? undefined : country;\n}\n\nfunction getUserData(tags: TrackTags, data: UserProvidedData) {\n const userData = new UserData();\n\n // set user-provided data\n if (data.email) {\n if (Array.isArray(data.email)) {\n userData.setEmails(data.email);\n } else {\n userData.setEmail(data.email);\n }\n }\n if (data.phone_number) {\n if (Array.isArray(data.phone_number)) {\n userData.setPhones(data.phone_number);\n } else {\n userData.setPhone(data.phone_number);\n }\n }\n if (data.gender) {\n if (data.gender === 'female') {\n userData.setGender('f');\n // oxlint-disable-next-line typescript/no-unnecessary-condition -- user-provided data may carry values outside the declared union at runtime\n } else if (data.gender === 'male') {\n userData.setGender('m');\n }\n }\n if (data.address) {\n if (Array.isArray(data.address)) {\n const firstNames = data.address.map((a) => a.first_name).filter(Boolean);\n const lastNames = data.address.map((a) => a.last_name).filter(Boolean);\n const cities = data.address.map((a) => a.city).filter(Boolean);\n const states = data.address.map((a) => a.region).filter(Boolean);\n const postalCodes = data.address.map((a) => a.postal_code).filter(Boolean);\n const countries = data.address.map((a) => normalizeCountry(a.country)).filter(Boolean);\n\n userData.setFirstNames(firstNames as string[]);\n userData.setLastNames(lastNames as string[]);\n userData.setCities(cities as string[]);\n userData.setStates(states as string[]);\n userData.setZips(postalCodes as string[]);\n userData.setCountries(countries as string[]);\n } else {\n if (data.address.first_name) {\n userData.setFirstName(data.address.first_name);\n userData.setF5First(data.address.first_name.slice(0, 5));\n }\n if (data.address.last_name) {\n userData.setLastName(data.address.last_name);\n userData.setF5Last(data.address.last_name.slice(0, 5));\n }\n if (data.address.city) userData.setCity(data.address.city);\n if (data.address.region) userData.setState(data.address.region);\n if (data.address.postal_code) userData.setZip(data.address.postal_code);\n if (data.address.country) {\n const country = normalizeCountry(data.address.country);\n if (country) userData.setCountry(country);\n }\n }\n }\n if (data.birthday) {\n userData.setDoby(data.birthday.year.toString());\n userData.setDobm(data.birthday.month.toString());\n userData.setDobd(data.birthday.day.toString());\n }\n if (data.user_id && data.user_id.length !== 0) {\n userData.setExternalId(data.user_id);\n }\n if (data.ip_address) {\n userData.setClientIpAddress(data.ip_address);\n }\n if (data.user_agent) {\n userData.setClientUserAgent(data.user_agent);\n }\n if (data.fb_login_id) {\n userData.setFbLoginId(data.fb_login_id);\n }\n if (data.fb_page_id) {\n userData.setPageId(data.fb_page_id);\n }\n\n // set tags info\n if (tags.fbc) {\n userData.setFbc(tags.fbc);\n } else if (tags.fbclid) {\n // ref: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc#2--format-clickid\n // The formatted ClickID value must be of the form `version.subdomainIndex.creationTime.<fbclid>`, where:\n // - version is always this prefix: fb\n // - subdomainIndex is which domain the cookie is defined on ('com' = 0, 'example.com' = 1, 'www.example.com' = 2)\n // - creationTime is the UNIX time since epoch in milliseconds when the _fbc was stored. If you don't save the _fbc cookie, use the timestamp when you first observed or received this fbclid value\n // - <fbclid> is the value for the fbclid query parameter in the page URL.\n\n const fbc = `fb.1.${Date.now()}.${tags.fbclid}`;\n userData.setFbc(fbc);\n }\n\n if (tags.fbp) {\n userData.setFbp(tags.fbp);\n }\n if (tags.advertising_id) {\n userData.setMadid(tags.advertising_id);\n }\n if (tags.ip_address && typeof tags.ip_address === 'string') {\n userData.setClientIpAddress(tags.ip_address);\n }\n\n return userData;\n}\n\nfunction getAppData(tags: TrackTags, appPackageName: string) {\n const extinfo = new ExtendedDeviceInfo();\n if (tags.os_name) {\n if (tags.os_name === 'iOS' || tags.os_name === 'iPadOS') {\n extinfo.setExtInfoVersion('i2');\n } else if (tags.os_name === 'Android') {\n extinfo.setExtInfoVersion('a2');\n }\n }\n extinfo.setAppPackageName(appPackageName);\n const shortVersion = tags.release?.split('.').at(0);\n if (shortVersion) {\n extinfo.setShortVersion(shortVersion);\n }\n if (tags.release) {\n extinfo.setLongVersion(tags.release);\n }\n if (tags.os_version) {\n extinfo.setOsVersion(tags.os_version);\n }\n if (tags.device_model_id) {\n extinfo.setDeviceModelName(tags.device_model_id);\n }\n if (tags.language) {\n extinfo.setLocale(tags.language);\n }\n if (tags.screen_width) {\n extinfo.setScreenWidth(tags.screen_width);\n }\n if (tags.screen_height) {\n extinfo.setScreenHeight(tags.screen_height);\n }\n if (tags.device_pixel_ratio) {\n extinfo.setScreenDensity(tags.device_pixel_ratio.toString());\n }\n\n const appData = new AppData();\n appData.setExtinfo(extinfo);\n if (tags.install_referrer) {\n appData.setInstallReferrer(tags.install_referrer);\n }\n if (tags.advertising_id) {\n appData.setAdvertiserTrackingEnabled(true);\n }\n if (tags.install_referrer) {\n appData.setInstallReferrer(tags.install_referrer);\n }\n\n return appData;\n}\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction getCustomData({ name, properties }: TrackEvent<any>) {\n const data = new CustomData();\n const [_, _name, fbEventProperties] = mapFBEvent(name, properties);\n const {\n value,\n currency,\n content_name,\n content_category,\n content_ids,\n contents,\n content_type,\n // order_id,\n predicted_ltv,\n num_items,\n search_string,\n status,\n // item_number,\n delivery_category,\n ...custom_properties\n } = fbEventProperties;\n // oxlint-disable-next-line typescript/no-unnecessary-condition -- custom event properties may carry `value: null` at runtime despite the declared type\n if (value !== undefined && value !== null) data.setValue(value);\n if (currency) data.setCurrency(currency);\n if (content_name) data.setContentName(content_name);\n if (content_category) data.setContentCategory(content_category);\n if (content_ids) data.setContentIds(content_ids);\n if (contents)\n data.setContents(\n contents.map((c) => {\n const result = new Content().setId(c.id).setQuantity(c.quantity);\n if (c.item_price) result.setItemPrice(c.item_price);\n if (c.title) result.setTitle(c.title);\n if (c.description) result.setDescription(c.description);\n if (c.brand) result.setBrand(c.brand);\n if (c.category) result.setCategory(c.category);\n if (c.delivery_category) result.setDeliveryCategory(c.delivery_category);\n return result;\n })\n );\n if (content_type) data.setContentType(content_type);\n // if (order_id) data.setOrderId(order_id);\n if (predicted_ltv) data.setPredictedLtv(predicted_ltv);\n if (num_items) data.setNumItems(num_items);\n if (search_string) data.setSearchString(search_string);\n if (status) data.setStatus(status.toString());\n // if (item_number) data.setItemNumber(item_number);\n if (delivery_category) data.setDeliveryCategory(delivery_category);\n data.setCustomProperties(custom_properties);\n return data;\n}\n\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData,\n appPackageName?: string\n) {\n const userData = getUserData(event.tags, data);\n const customData = getCustomData(event);\n const [_, eventName] = mapFBEvent(event.name, event.properties);\n const serverEvent = new ServerEvent()\n .setEventId(event.tags.idempotency_key ?? event.id)\n .setEventName(eventName)\n .setEventTime(Math.round(Date.now() / 1000))\n .setUserData(userData)\n .setCustomData(customData);\n\n if (event.tags.source === 'app' && appPackageName) {\n const appData = getAppData(event.tags, appPackageName);\n serverEvent.setAppData(appData);\n }\n if (event.tags.source_url) {\n serverEvent.setEventSourceUrl(event.tags.source_url);\n }\n switch (event.tags.source) {\n case 'app':\n serverEvent.setActionSource('app');\n break;\n case 'web':\n serverEvent.setActionSource('website');\n break;\n default:\n break;\n }\n return serverEvent;\n}\n\n/**\n * The shape `FacebookRequestError` exposes: `status` and `response` are the HTTP status and the\n * parsed error body, and both are null when the request never received a response.\n */\ntype MetaRequestError = { status?: number | null; message?: string; response?: unknown };\n\n/**\n * Meta's SDK rejects on API errors, where every other conversion sender here logs and resolves.\n * Keep the failure inside this module, so one rejected batch cannot fail the caller's request.\n * Never log the error itself: it also carries the access token, in both `url` and `data`.\n */\nfunction logError(error: unknown) {\n const { status, message, response } = (error ?? {}) as MetaRequestError;\n if (typeof status !== 'number') {\n console.error(`Failed to send Meta conversion, network error: ${message ?? String(error)}`);\n return;\n }\n console.error(\n `Failed to send Meta conversion, status: ${status}, body: ${JSON.stringify(response)}`\n );\n}\n\nexport async function sendEvent(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData = {},\n appPackageName?: string\n) {\n if (IGNORED_EVENTS.includes(event.name)) return undefined;\n const request = new EventRequest(accessToken, pixelId);\n const fbEvent = getServerEvent(event, data, appPackageName);\n request.setEvents([fbEvent]);\n try {\n return await request.execute();\n } catch (error) {\n logError(error);\n return undefined;\n }\n}\n\nexport async function sendEvents(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n appPackageName?: string\n) {\n const fbEvents = events\n .filter((event) => !IGNORED_EVENTS.includes(event.name))\n .map((event) => getServerEvent(event, data, appPackageName));\n if (fbEvents.length === 0) return undefined;\n const request = new EventRequest(accessToken, pixelId);\n request.setEvents(fbEvents);\n try {\n return await request.execute();\n } catch (error) {\n logError(error);\n return undefined;\n }\n}\n\nexport async function sendTestEvent(accessToken: string, pixelId: string, testEventCode: string) {\n const extinfo = new ExtendedDeviceInfo()\n .setExtInfoVersion('a2')\n .setAppPackageName('com.some.app')\n .setShortVersion('771')\n .setLongVersion('Version 7.7.1')\n .setOsVersion('10.1.1')\n .setDeviceModelName('OnePlus6')\n .setLocale('en_US')\n .setTimezoneAbbreviation('GMT-1')\n .setCarrier('TMobile')\n .setScreenWidth(1920)\n .setScreenHeight(1080)\n .setScreenDensity('2.00')\n .setCpuCoreCount(2)\n .setTotalDiskSpaceGb(128)\n .setFreeDiskSpaceGb(8)\n .setDeviceTimeZone('USA/New York');\n\n const userData = new UserData().setEmail('test@example.com');\n const appData = new AppData().setExtinfo(extinfo);\n const event = new ServerEvent()\n .setEventId(Math.round(Math.random() * 1000_000).toString())\n .setEventName('TestEvent')\n .setEventTime(Math.round(Date.now() / 1000))\n .setUserData(userData)\n .setAppData(appData)\n .setActionSource('app');\n\n const request = new EventRequest(accessToken, pixelId);\n request.setTestEventCode(testEventCode);\n request.setEvents([event]);\n return request.execute();\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"meta-conversions-api.d.cts","names":[],"sources":["../../src/server/meta-conversions-api.ts"],"mappings":";;;iBAuOgB,eAEd,OAAO,iBACP,MAAM,kBACN,0BAAuB;iBAgCH,UACpB,qBACA,iBAEA,OAAO,iBACP,OAAM,kBACN,0BAAuB,+CAAA;iBASH,WACpB,qBACA,iBAEA,QAAQ,mBACR,OAAM,kBACN,0BAAuB,+CAAA;iBAWH,cAAc,qBAAqB,iBAAiB,wBAAqB,+CAAA"}
1
+ {"version":3,"file":"meta-conversions-api.d.cts","names":[],"sources":["../../src/server/meta-conversions-api.ts"],"mappings":";;;iBAuOgB,eAEd,OAAO,iBACP,MAAM,kBACN,0BAAuB;iBAsDH,UACpB,qBACA,iBAEA,OAAO,iBACP,OAAM,kBACN,0BAAuB,+CAAA;iBAcH,WACpB,qBACA,iBAEA,QAAQ,mBACR,OAAM,kBACN,0BAAuB,+CAAA;iBAgBH,cAAc,qBAAqB,iBAAiB,wBAAqB,+CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"meta-conversions-api.d.mts","names":[],"sources":["../../src/server/meta-conversions-api.ts"],"mappings":";;;iBAuOgB,eAEd,OAAO,iBACP,MAAM,kBACN,0BAAuB;iBAgCH,UACpB,qBACA,iBAEA,OAAO,iBACP,OAAM,kBACN,0BAAuB,+CAAA;iBASH,WACpB,qBACA,iBAEA,QAAQ,mBACR,OAAM,kBACN,0BAAuB,+CAAA;iBAWH,cAAc,qBAAqB,iBAAiB,wBAAqB,+CAAA"}
1
+ {"version":3,"file":"meta-conversions-api.d.mts","names":[],"sources":["../../src/server/meta-conversions-api.ts"],"mappings":";;;iBAuOgB,eAEd,OAAO,iBACP,MAAM,kBACN,0BAAuB;iBAsDH,UACpB,qBACA,iBAEA,OAAO,iBACP,OAAM,kBACN,0BAAuB,+CAAA;iBAcH,WACpB,qBACA,iBAEA,QAAQ,mBACR,OAAM,kBACN,0BAAuB,+CAAA;iBAgBH,cAAc,qBAAqB,iBAAiB,wBAAqB,+CAAA"}
@@ -140,19 +140,42 @@ function getServerEvent(event, data, appPackageName) {
140
140
  }
141
141
  return serverEvent;
142
142
  }
143
+ /**
144
+ * Meta's SDK rejects on API errors, where every other conversion sender here logs and resolves.
145
+ * Keep the failure inside this module, so one rejected batch cannot fail the caller's request.
146
+ * Never log the error itself: it also carries the access token, in both `url` and `data`.
147
+ */
148
+ function logError(error) {
149
+ const { status, message, response } = error ?? {};
150
+ if (typeof status !== "number") {
151
+ console.error(`Failed to send Meta conversion, network error: ${message ?? String(error)}`);
152
+ return;
153
+ }
154
+ console.error(`Failed to send Meta conversion, status: ${status}, body: ${JSON.stringify(response)}`);
155
+ }
143
156
  async function sendEvent(accessToken, pixelId, event, data = {}, appPackageName) {
144
157
  if (IGNORED_EVENTS.includes(event.name)) return void 0;
145
158
  const request = new EventRequest(accessToken, pixelId);
146
159
  const fbEvent = getServerEvent(event, data, appPackageName);
147
160
  request.setEvents([fbEvent]);
148
- return request.execute();
161
+ try {
162
+ return await request.execute();
163
+ } catch (error) {
164
+ logError(error);
165
+ return;
166
+ }
149
167
  }
150
168
  async function sendEvents(accessToken, pixelId, events, data = {}, appPackageName) {
151
169
  const fbEvents = events.filter((event) => !IGNORED_EVENTS.includes(event.name)).map((event) => getServerEvent(event, data, appPackageName));
152
170
  if (fbEvents.length === 0) return void 0;
153
171
  const request = new EventRequest(accessToken, pixelId);
154
172
  request.setEvents(fbEvents);
155
- return request.execute();
173
+ try {
174
+ return await request.execute();
175
+ } catch (error) {
176
+ logError(error);
177
+ return;
178
+ }
156
179
  }
157
180
  async function sendTestEvent(accessToken, pixelId, testEventCode) {
158
181
  const extinfo = new ExtendedDeviceInfo().setExtInfoVersion("a2").setAppPackageName("com.some.app").setShortVersion("771").setLongVersion("Version 7.7.1").setOsVersion("10.1.1").setDeviceModelName("OnePlus6").setLocale("en_US").setTimezoneAbbreviation("GMT-1").setCarrier("TMobile").setScreenWidth(1920).setScreenHeight(1080).setScreenDensity("2.00").setCpuCoreCount(2).setTotalDiskSpaceGb(128).setFreeDiskSpaceGb(8).setDeviceTimeZone("USA/New York");
@@ -1 +1 @@
1
- {"version":3,"file":"meta-conversions-api.mjs","names":[],"sources":["../../src/server/meta-conversions-api.ts"],"sourcesContent":["import {\n AppData,\n Content,\n CustomData,\n EventRequest,\n ExtendedDeviceInfo,\n ServerEvent,\n UserData,\n} from 'facebook-nodejs-business-sdk';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { mapFBEvent } from '../track/fbq';\nimport type { TrackEvent, TrackTags, UserProvidedData } from '../track/types';\n\nconst USER_ASSIGNED_COUNTRIES: string[] = ['xk'];\nfunction normalizeCountry(input: string | undefined): string | undefined {\n const country = input?.split(/[-_]/).at(0)?.toLowerCase();\n if (!country) return undefined;\n return USER_ASSIGNED_COUNTRIES.includes(country) ? undefined : country;\n}\n\nfunction getUserData(tags: TrackTags, data: UserProvidedData) {\n const userData = new UserData();\n\n // set user-provided data\n if (data.email) {\n if (Array.isArray(data.email)) {\n userData.setEmails(data.email);\n } else {\n userData.setEmail(data.email);\n }\n }\n if (data.phone_number) {\n if (Array.isArray(data.phone_number)) {\n userData.setPhones(data.phone_number);\n } else {\n userData.setPhone(data.phone_number);\n }\n }\n if (data.gender) {\n if (data.gender === 'female') {\n userData.setGender('f');\n // oxlint-disable-next-line typescript/no-unnecessary-condition -- user-provided data may carry values outside the declared union at runtime\n } else if (data.gender === 'male') {\n userData.setGender('m');\n }\n }\n if (data.address) {\n if (Array.isArray(data.address)) {\n const firstNames = data.address.map((a) => a.first_name).filter(Boolean);\n const lastNames = data.address.map((a) => a.last_name).filter(Boolean);\n const cities = data.address.map((a) => a.city).filter(Boolean);\n const states = data.address.map((a) => a.region).filter(Boolean);\n const postalCodes = data.address.map((a) => a.postal_code).filter(Boolean);\n const countries = data.address.map((a) => normalizeCountry(a.country)).filter(Boolean);\n\n userData.setFirstNames(firstNames as string[]);\n userData.setLastNames(lastNames as string[]);\n userData.setCities(cities as string[]);\n userData.setStates(states as string[]);\n userData.setZips(postalCodes as string[]);\n userData.setCountries(countries as string[]);\n } else {\n if (data.address.first_name) {\n userData.setFirstName(data.address.first_name);\n userData.setF5First(data.address.first_name.slice(0, 5));\n }\n if (data.address.last_name) {\n userData.setLastName(data.address.last_name);\n userData.setF5Last(data.address.last_name.slice(0, 5));\n }\n if (data.address.city) userData.setCity(data.address.city);\n if (data.address.region) userData.setState(data.address.region);\n if (data.address.postal_code) userData.setZip(data.address.postal_code);\n if (data.address.country) {\n const country = normalizeCountry(data.address.country);\n if (country) userData.setCountry(country);\n }\n }\n }\n if (data.birthday) {\n userData.setDoby(data.birthday.year.toString());\n userData.setDobm(data.birthday.month.toString());\n userData.setDobd(data.birthday.day.toString());\n }\n if (data.user_id && data.user_id.length !== 0) {\n userData.setExternalId(data.user_id);\n }\n if (data.ip_address) {\n userData.setClientIpAddress(data.ip_address);\n }\n if (data.user_agent) {\n userData.setClientUserAgent(data.user_agent);\n }\n if (data.fb_login_id) {\n userData.setFbLoginId(data.fb_login_id);\n }\n if (data.fb_page_id) {\n userData.setPageId(data.fb_page_id);\n }\n\n // set tags info\n if (tags.fbc) {\n userData.setFbc(tags.fbc);\n } else if (tags.fbclid) {\n // ref: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc#2--format-clickid\n // The formatted ClickID value must be of the form `version.subdomainIndex.creationTime.<fbclid>`, where:\n // - version is always this prefix: fb\n // - subdomainIndex is which domain the cookie is defined on ('com' = 0, 'example.com' = 1, 'www.example.com' = 2)\n // - creationTime is the UNIX time since epoch in milliseconds when the _fbc was stored. If you don't save the _fbc cookie, use the timestamp when you first observed or received this fbclid value\n // - <fbclid> is the value for the fbclid query parameter in the page URL.\n\n const fbc = `fb.1.${Date.now()}.${tags.fbclid}`;\n userData.setFbc(fbc);\n }\n\n if (tags.fbp) {\n userData.setFbp(tags.fbp);\n }\n if (tags.advertising_id) {\n userData.setMadid(tags.advertising_id);\n }\n if (tags.ip_address && typeof tags.ip_address === 'string') {\n userData.setClientIpAddress(tags.ip_address);\n }\n\n return userData;\n}\n\nfunction getAppData(tags: TrackTags, appPackageName: string) {\n const extinfo = new ExtendedDeviceInfo();\n if (tags.os_name) {\n if (tags.os_name === 'iOS' || tags.os_name === 'iPadOS') {\n extinfo.setExtInfoVersion('i2');\n } else if (tags.os_name === 'Android') {\n extinfo.setExtInfoVersion('a2');\n }\n }\n extinfo.setAppPackageName(appPackageName);\n const shortVersion = tags.release?.split('.').at(0);\n if (shortVersion) {\n extinfo.setShortVersion(shortVersion);\n }\n if (tags.release) {\n extinfo.setLongVersion(tags.release);\n }\n if (tags.os_version) {\n extinfo.setOsVersion(tags.os_version);\n }\n if (tags.device_model_id) {\n extinfo.setDeviceModelName(tags.device_model_id);\n }\n if (tags.language) {\n extinfo.setLocale(tags.language);\n }\n if (tags.screen_width) {\n extinfo.setScreenWidth(tags.screen_width);\n }\n if (tags.screen_height) {\n extinfo.setScreenHeight(tags.screen_height);\n }\n if (tags.device_pixel_ratio) {\n extinfo.setScreenDensity(tags.device_pixel_ratio.toString());\n }\n\n const appData = new AppData();\n appData.setExtinfo(extinfo);\n if (tags.install_referrer) {\n appData.setInstallReferrer(tags.install_referrer);\n }\n if (tags.advertising_id) {\n appData.setAdvertiserTrackingEnabled(true);\n }\n if (tags.install_referrer) {\n appData.setInstallReferrer(tags.install_referrer);\n }\n\n return appData;\n}\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction getCustomData({ name, properties }: TrackEvent<any>) {\n const data = new CustomData();\n const [_, _name, fbEventProperties] = mapFBEvent(name, properties);\n const {\n value,\n currency,\n content_name,\n content_category,\n content_ids,\n contents,\n content_type,\n // order_id,\n predicted_ltv,\n num_items,\n search_string,\n status,\n // item_number,\n delivery_category,\n ...custom_properties\n } = fbEventProperties;\n // oxlint-disable-next-line typescript/no-unnecessary-condition -- custom event properties may carry `value: null` at runtime despite the declared type\n if (value !== undefined && value !== null) data.setValue(value);\n if (currency) data.setCurrency(currency);\n if (content_name) data.setContentName(content_name);\n if (content_category) data.setContentCategory(content_category);\n if (content_ids) data.setContentIds(content_ids);\n if (contents)\n data.setContents(\n contents.map((c) => {\n const result = new Content().setId(c.id).setQuantity(c.quantity);\n if (c.item_price) result.setItemPrice(c.item_price);\n if (c.title) result.setTitle(c.title);\n if (c.description) result.setDescription(c.description);\n if (c.brand) result.setBrand(c.brand);\n if (c.category) result.setCategory(c.category);\n if (c.delivery_category) result.setDeliveryCategory(c.delivery_category);\n return result;\n })\n );\n if (content_type) data.setContentType(content_type);\n // if (order_id) data.setOrderId(order_id);\n if (predicted_ltv) data.setPredictedLtv(predicted_ltv);\n if (num_items) data.setNumItems(num_items);\n if (search_string) data.setSearchString(search_string);\n if (status) data.setStatus(status.toString());\n // if (item_number) data.setItemNumber(item_number);\n if (delivery_category) data.setDeliveryCategory(delivery_category);\n data.setCustomProperties(custom_properties);\n return data;\n}\n\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData,\n appPackageName?: string\n) {\n const userData = getUserData(event.tags, data);\n const customData = getCustomData(event);\n const [_, eventName] = mapFBEvent(event.name, event.properties);\n const serverEvent = new ServerEvent()\n .setEventId(event.tags.idempotency_key ?? event.id)\n .setEventName(eventName)\n .setEventTime(Math.round(Date.now() / 1000))\n .setUserData(userData)\n .setCustomData(customData);\n\n if (event.tags.source === 'app' && appPackageName) {\n const appData = getAppData(event.tags, appPackageName);\n serverEvent.setAppData(appData);\n }\n if (event.tags.source_url) {\n serverEvent.setEventSourceUrl(event.tags.source_url);\n }\n switch (event.tags.source) {\n case 'app':\n serverEvent.setActionSource('app');\n break;\n case 'web':\n serverEvent.setActionSource('website');\n break;\n default:\n break;\n }\n return serverEvent;\n}\n\nexport async function sendEvent(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData = {},\n appPackageName?: string\n) {\n if (IGNORED_EVENTS.includes(event.name)) return undefined;\n const request = new EventRequest(accessToken, pixelId);\n const fbEvent = getServerEvent(event, data, appPackageName);\n request.setEvents([fbEvent]);\n return request.execute();\n}\n\nexport async function sendEvents(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n appPackageName?: string\n) {\n const fbEvents = events\n .filter((event) => !IGNORED_EVENTS.includes(event.name))\n .map((event) => getServerEvent(event, data, appPackageName));\n if (fbEvents.length === 0) return undefined;\n const request = new EventRequest(accessToken, pixelId);\n request.setEvents(fbEvents);\n return request.execute();\n}\n\nexport async function sendTestEvent(accessToken: string, pixelId: string, testEventCode: string) {\n const extinfo = new ExtendedDeviceInfo()\n .setExtInfoVersion('a2')\n .setAppPackageName('com.some.app')\n .setShortVersion('771')\n .setLongVersion('Version 7.7.1')\n .setOsVersion('10.1.1')\n .setDeviceModelName('OnePlus6')\n .setLocale('en_US')\n .setTimezoneAbbreviation('GMT-1')\n .setCarrier('TMobile')\n .setScreenWidth(1920)\n .setScreenHeight(1080)\n .setScreenDensity('2.00')\n .setCpuCoreCount(2)\n .setTotalDiskSpaceGb(128)\n .setFreeDiskSpaceGb(8)\n .setDeviceTimeZone('USA/New York');\n\n const userData = new UserData().setEmail('test@example.com');\n const appData = new AppData().setExtinfo(extinfo);\n const event = new ServerEvent()\n .setEventId(Math.round(Math.random() * 1000_000).toString())\n .setEventName('TestEvent')\n .setEventTime(Math.round(Date.now() / 1000))\n .setUserData(userData)\n .setAppData(appData)\n .setActionSource('app');\n\n const request = new EventRequest(accessToken, pixelId);\n request.setTestEventCode(testEventCode);\n request.setEvents([event]);\n return request.execute();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"meta-conversions-api.mjs","names":[],"sources":["../../src/server/meta-conversions-api.ts"],"sourcesContent":["import {\n AppData,\n Content,\n CustomData,\n EventRequest,\n ExtendedDeviceInfo,\n ServerEvent,\n UserData,\n} from 'facebook-nodejs-business-sdk';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { mapFBEvent } from '../track/fbq';\nimport type { TrackEvent, TrackTags, UserProvidedData } from '../track/types';\n\nconst USER_ASSIGNED_COUNTRIES: string[] = ['xk'];\nfunction normalizeCountry(input: string | undefined): string | undefined {\n const country = input?.split(/[-_]/).at(0)?.toLowerCase();\n if (!country) return undefined;\n return USER_ASSIGNED_COUNTRIES.includes(country) ? undefined : country;\n}\n\nfunction getUserData(tags: TrackTags, data: UserProvidedData) {\n const userData = new UserData();\n\n // set user-provided data\n if (data.email) {\n if (Array.isArray(data.email)) {\n userData.setEmails(data.email);\n } else {\n userData.setEmail(data.email);\n }\n }\n if (data.phone_number) {\n if (Array.isArray(data.phone_number)) {\n userData.setPhones(data.phone_number);\n } else {\n userData.setPhone(data.phone_number);\n }\n }\n if (data.gender) {\n if (data.gender === 'female') {\n userData.setGender('f');\n // oxlint-disable-next-line typescript/no-unnecessary-condition -- user-provided data may carry values outside the declared union at runtime\n } else if (data.gender === 'male') {\n userData.setGender('m');\n }\n }\n if (data.address) {\n if (Array.isArray(data.address)) {\n const firstNames = data.address.map((a) => a.first_name).filter(Boolean);\n const lastNames = data.address.map((a) => a.last_name).filter(Boolean);\n const cities = data.address.map((a) => a.city).filter(Boolean);\n const states = data.address.map((a) => a.region).filter(Boolean);\n const postalCodes = data.address.map((a) => a.postal_code).filter(Boolean);\n const countries = data.address.map((a) => normalizeCountry(a.country)).filter(Boolean);\n\n userData.setFirstNames(firstNames as string[]);\n userData.setLastNames(lastNames as string[]);\n userData.setCities(cities as string[]);\n userData.setStates(states as string[]);\n userData.setZips(postalCodes as string[]);\n userData.setCountries(countries as string[]);\n } else {\n if (data.address.first_name) {\n userData.setFirstName(data.address.first_name);\n userData.setF5First(data.address.first_name.slice(0, 5));\n }\n if (data.address.last_name) {\n userData.setLastName(data.address.last_name);\n userData.setF5Last(data.address.last_name.slice(0, 5));\n }\n if (data.address.city) userData.setCity(data.address.city);\n if (data.address.region) userData.setState(data.address.region);\n if (data.address.postal_code) userData.setZip(data.address.postal_code);\n if (data.address.country) {\n const country = normalizeCountry(data.address.country);\n if (country) userData.setCountry(country);\n }\n }\n }\n if (data.birthday) {\n userData.setDoby(data.birthday.year.toString());\n userData.setDobm(data.birthday.month.toString());\n userData.setDobd(data.birthday.day.toString());\n }\n if (data.user_id && data.user_id.length !== 0) {\n userData.setExternalId(data.user_id);\n }\n if (data.ip_address) {\n userData.setClientIpAddress(data.ip_address);\n }\n if (data.user_agent) {\n userData.setClientUserAgent(data.user_agent);\n }\n if (data.fb_login_id) {\n userData.setFbLoginId(data.fb_login_id);\n }\n if (data.fb_page_id) {\n userData.setPageId(data.fb_page_id);\n }\n\n // set tags info\n if (tags.fbc) {\n userData.setFbc(tags.fbc);\n } else if (tags.fbclid) {\n // ref: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc#2--format-clickid\n // The formatted ClickID value must be of the form `version.subdomainIndex.creationTime.<fbclid>`, where:\n // - version is always this prefix: fb\n // - subdomainIndex is which domain the cookie is defined on ('com' = 0, 'example.com' = 1, 'www.example.com' = 2)\n // - creationTime is the UNIX time since epoch in milliseconds when the _fbc was stored. If you don't save the _fbc cookie, use the timestamp when you first observed or received this fbclid value\n // - <fbclid> is the value for the fbclid query parameter in the page URL.\n\n const fbc = `fb.1.${Date.now()}.${tags.fbclid}`;\n userData.setFbc(fbc);\n }\n\n if (tags.fbp) {\n userData.setFbp(tags.fbp);\n }\n if (tags.advertising_id) {\n userData.setMadid(tags.advertising_id);\n }\n if (tags.ip_address && typeof tags.ip_address === 'string') {\n userData.setClientIpAddress(tags.ip_address);\n }\n\n return userData;\n}\n\nfunction getAppData(tags: TrackTags, appPackageName: string) {\n const extinfo = new ExtendedDeviceInfo();\n if (tags.os_name) {\n if (tags.os_name === 'iOS' || tags.os_name === 'iPadOS') {\n extinfo.setExtInfoVersion('i2');\n } else if (tags.os_name === 'Android') {\n extinfo.setExtInfoVersion('a2');\n }\n }\n extinfo.setAppPackageName(appPackageName);\n const shortVersion = tags.release?.split('.').at(0);\n if (shortVersion) {\n extinfo.setShortVersion(shortVersion);\n }\n if (tags.release) {\n extinfo.setLongVersion(tags.release);\n }\n if (tags.os_version) {\n extinfo.setOsVersion(tags.os_version);\n }\n if (tags.device_model_id) {\n extinfo.setDeviceModelName(tags.device_model_id);\n }\n if (tags.language) {\n extinfo.setLocale(tags.language);\n }\n if (tags.screen_width) {\n extinfo.setScreenWidth(tags.screen_width);\n }\n if (tags.screen_height) {\n extinfo.setScreenHeight(tags.screen_height);\n }\n if (tags.device_pixel_ratio) {\n extinfo.setScreenDensity(tags.device_pixel_ratio.toString());\n }\n\n const appData = new AppData();\n appData.setExtinfo(extinfo);\n if (tags.install_referrer) {\n appData.setInstallReferrer(tags.install_referrer);\n }\n if (tags.advertising_id) {\n appData.setAdvertiserTrackingEnabled(true);\n }\n if (tags.install_referrer) {\n appData.setInstallReferrer(tags.install_referrer);\n }\n\n return appData;\n}\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction getCustomData({ name, properties }: TrackEvent<any>) {\n const data = new CustomData();\n const [_, _name, fbEventProperties] = mapFBEvent(name, properties);\n const {\n value,\n currency,\n content_name,\n content_category,\n content_ids,\n contents,\n content_type,\n // order_id,\n predicted_ltv,\n num_items,\n search_string,\n status,\n // item_number,\n delivery_category,\n ...custom_properties\n } = fbEventProperties;\n // oxlint-disable-next-line typescript/no-unnecessary-condition -- custom event properties may carry `value: null` at runtime despite the declared type\n if (value !== undefined && value !== null) data.setValue(value);\n if (currency) data.setCurrency(currency);\n if (content_name) data.setContentName(content_name);\n if (content_category) data.setContentCategory(content_category);\n if (content_ids) data.setContentIds(content_ids);\n if (contents)\n data.setContents(\n contents.map((c) => {\n const result = new Content().setId(c.id).setQuantity(c.quantity);\n if (c.item_price) result.setItemPrice(c.item_price);\n if (c.title) result.setTitle(c.title);\n if (c.description) result.setDescription(c.description);\n if (c.brand) result.setBrand(c.brand);\n if (c.category) result.setCategory(c.category);\n if (c.delivery_category) result.setDeliveryCategory(c.delivery_category);\n return result;\n })\n );\n if (content_type) data.setContentType(content_type);\n // if (order_id) data.setOrderId(order_id);\n if (predicted_ltv) data.setPredictedLtv(predicted_ltv);\n if (num_items) data.setNumItems(num_items);\n if (search_string) data.setSearchString(search_string);\n if (status) data.setStatus(status.toString());\n // if (item_number) data.setItemNumber(item_number);\n if (delivery_category) data.setDeliveryCategory(delivery_category);\n data.setCustomProperties(custom_properties);\n return data;\n}\n\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData,\n appPackageName?: string\n) {\n const userData = getUserData(event.tags, data);\n const customData = getCustomData(event);\n const [_, eventName] = mapFBEvent(event.name, event.properties);\n const serverEvent = new ServerEvent()\n .setEventId(event.tags.idempotency_key ?? event.id)\n .setEventName(eventName)\n .setEventTime(Math.round(Date.now() / 1000))\n .setUserData(userData)\n .setCustomData(customData);\n\n if (event.tags.source === 'app' && appPackageName) {\n const appData = getAppData(event.tags, appPackageName);\n serverEvent.setAppData(appData);\n }\n if (event.tags.source_url) {\n serverEvent.setEventSourceUrl(event.tags.source_url);\n }\n switch (event.tags.source) {\n case 'app':\n serverEvent.setActionSource('app');\n break;\n case 'web':\n serverEvent.setActionSource('website');\n break;\n default:\n break;\n }\n return serverEvent;\n}\n\n/**\n * The shape `FacebookRequestError` exposes: `status` and `response` are the HTTP status and the\n * parsed error body, and both are null when the request never received a response.\n */\ntype MetaRequestError = { status?: number | null; message?: string; response?: unknown };\n\n/**\n * Meta's SDK rejects on API errors, where every other conversion sender here logs and resolves.\n * Keep the failure inside this module, so one rejected batch cannot fail the caller's request.\n * Never log the error itself: it also carries the access token, in both `url` and `data`.\n */\nfunction logError(error: unknown) {\n const { status, message, response } = (error ?? {}) as MetaRequestError;\n if (typeof status !== 'number') {\n console.error(`Failed to send Meta conversion, network error: ${message ?? String(error)}`);\n return;\n }\n console.error(\n `Failed to send Meta conversion, status: ${status}, body: ${JSON.stringify(response)}`\n );\n}\n\nexport async function sendEvent(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData = {},\n appPackageName?: string\n) {\n if (IGNORED_EVENTS.includes(event.name)) return undefined;\n const request = new EventRequest(accessToken, pixelId);\n const fbEvent = getServerEvent(event, data, appPackageName);\n request.setEvents([fbEvent]);\n try {\n return await request.execute();\n } catch (error) {\n logError(error);\n return undefined;\n }\n}\n\nexport async function sendEvents(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n appPackageName?: string\n) {\n const fbEvents = events\n .filter((event) => !IGNORED_EVENTS.includes(event.name))\n .map((event) => getServerEvent(event, data, appPackageName));\n if (fbEvents.length === 0) return undefined;\n const request = new EventRequest(accessToken, pixelId);\n request.setEvents(fbEvents);\n try {\n return await request.execute();\n } catch (error) {\n logError(error);\n return undefined;\n }\n}\n\nexport async function sendTestEvent(accessToken: string, pixelId: string, testEventCode: string) {\n const extinfo = new ExtendedDeviceInfo()\n .setExtInfoVersion('a2')\n .setAppPackageName('com.some.app')\n .setShortVersion('771')\n .setLongVersion('Version 7.7.1')\n .setOsVersion('10.1.1')\n .setDeviceModelName('OnePlus6')\n .setLocale('en_US')\n .setTimezoneAbbreviation('GMT-1')\n .setCarrier('TMobile')\n .setScreenWidth(1920)\n .setScreenHeight(1080)\n .setScreenDensity('2.00')\n .setCpuCoreCount(2)\n .setTotalDiskSpaceGb(128)\n .setFreeDiskSpaceGb(8)\n .setDeviceTimeZone('USA/New York');\n\n const userData = new UserData().setEmail('test@example.com');\n const appData = new AppData().setExtinfo(extinfo);\n const event = new ServerEvent()\n .setEventId(Math.round(Math.random() * 1000_000).toString())\n .setEventName('TestEvent')\n .setEventTime(Math.round(Date.now() / 1000))\n .setUserData(userData)\n .setAppData(appData)\n .setActionSource('app');\n\n const request = new EventRequest(accessToken, pixelId);\n request.setTestEventCode(testEventCode);\n request.setEvents([event]);\n return request.execute();\n}\n"],"mappings":"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"}
@@ -65,9 +65,35 @@ var Session = class {
65
65
  this.focused = typeof document !== "undefined" ? document.hasFocus() : true;
66
66
  }
67
67
  };
68
- const session = new Session();
68
+ let session;
69
+ /**
70
+ * The session, built the first time something asks for it.
71
+ *
72
+ * Deliberately not a module-scope `new Session()`. The constructor calls
73
+ * `uuidv7()`, `uuid` draws its bytes from `crypto.getRandomValues`, and
74
+ * Cloudflare Workers reject that outside a request handler:
75
+ *
76
+ * Disallowed operation called within global scope. Asynchronous I/O
77
+ * (ex: fetch() or connect()), setting a timeout, and generating random
78
+ * values are not allowed within global scope.
79
+ *
80
+ * Module scope in a Worker is evaluated once when the isolate boots and is
81
+ * then shared by every request that isolate serves, so a random value drawn
82
+ * there would be the same for all of them — which is why the runtime refuses
83
+ * to produce one. A host that server-renders on Workers reaches this module
84
+ * through `track()` on the server as well, and the throw happened as the
85
+ * isolate booted, taking down every route before a component rendered.
86
+ *
87
+ * Everything here is per-visitor browser or app state, so deferring the
88
+ * construction costs nothing and buys two things: the server bundle can be
89
+ * evaluated, and `startTime` marks when the session actually began rather
90
+ * than when the isolate happened to start.
91
+ */
92
+ function getSession() {
93
+ return session ??= new Session();
94
+ }
69
95
  //#endregion
70
96
  exports.SESSION_TIMEOUT = SESSION_TIMEOUT;
71
- exports.session = session;
97
+ exports.getSession = getSession;
72
98
 
73
99
  //# sourceMappingURL=session.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"session.cjs","names":[],"sources":["../../src/setup/session.ts"],"sourcesContent":["import { v7 as uuidv7 } from 'uuid';\n\nexport const SESSION_TIMEOUT = 30 * 60 * 1000;\n\nclass Session {\n private id: string;\n private startTime: number;\n private lastActiveTime: number;\n private accumulatedTime: number;\n\n private active: boolean;\n private visible: boolean;\n private focused: boolean;\n\n constructor() {\n this.id = uuidv7();\n this.startTime = Date.now();\n this.lastActiveTime = Date.now();\n this.accumulatedTime = 0;\n\n this.active = true;\n this.visible = typeof document !== 'undefined' ? document.visibilityState === 'visible' : true;\n this.focused = typeof document !== 'undefined' ? document.hasFocus() : true;\n }\n\n getId = () => this.id;\n\n isActive = () => this.active;\n isVisible = () => this.visible;\n isFocused = () => this.focused;\n isExpired = () => Date.now() - this.lastActiveTime > SESSION_TIMEOUT;\n\n updateLastActiveTime = () => {\n this.lastActiveTime = Date.now();\n };\n\n updateActive = (active: boolean) => {\n this.active = active;\n };\n\n refresh = () => {\n this.id = uuidv7();\n this.lastActiveTime = Date.now();\n };\n\n updateAccumulator = () => {\n const now = Date.now();\n if (this.focused && this.visible && this.active) {\n const delta = now - this.startTime;\n if (delta > 0 && delta < SESSION_TIMEOUT) {\n this.accumulatedTime += delta;\n }\n }\n this.startTime = now;\n };\n\n focus = () => {\n this.updateAccumulator();\n this.updateLastActiveTime();\n this.focused = true;\n };\n\n blur = () => {\n this.updateAccumulator();\n this.focused = false;\n };\n\n pageshow = () => {\n this.updateAccumulator();\n this.updateLastActiveTime();\n this.active = true;\n };\n\n pagehide = () => {\n this.updateAccumulator();\n this.active = false;\n };\n\n visibilitychange = (state: DocumentVisibilityState) => {\n this.updateAccumulator();\n if (state === 'visible') {\n this.updateLastActiveTime();\n }\n this.visible = state === 'visible';\n };\n\n flush = () => {\n this.updateAccumulator();\n const engagementTime = this.accumulatedTime;\n this.accumulatedTime = 0;\n return engagementTime;\n };\n}\n\nexport const session = new Session();\n"],"mappings":";;;AAEA,MAAa,kBAAkB,OAAU;AAEzC,IAAM,UAAN,MAAc;CAUZ,cAAc;EAWA,KAAA,cAAA,KAAK;EAEF,KAAA,iBAAA,KAAK;EACJ,KAAA,kBAAA,KAAK;EACL,KAAA,kBAAA,KAAK;EACL,KAAA,kBAAA,KAAK,IAAI,IAAI,KAAK,iBAAiB;EAExB,KAAA,6BAAA;GAC3B,KAAK,iBAAiB,KAAK,IAAI;EACjC;EAEgB,KAAA,gBAAA,WAAoB;GAClC,KAAK,SAAS;EAChB;EAEgB,KAAA,gBAAA;GACd,KAAK,MAAA,GAAA,KAAA,GAAA,CAAY;GACjB,KAAK,iBAAiB,KAAK,IAAI;EACjC;EAE0B,KAAA,0BAAA;GACxB,MAAM,MAAM,KAAK,IAAI;GACrB,IAAI,KAAK,WAAW,KAAK,WAAW,KAAK,QAAQ;IAC/C,MAAM,QAAQ,MAAM,KAAK;IACzB,IAAI,QAAQ,KAAK,QAAA,MACf,KAAK,mBAAmB;GAE5B;GACA,KAAK,YAAY;EACnB;EAEc,KAAA,cAAA;GACZ,KAAK,kBAAkB;GACvB,KAAK,qBAAqB;GAC1B,KAAK,UAAU;EACjB;EAEa,KAAA,aAAA;GACX,KAAK,kBAAkB;GACvB,KAAK,UAAU;EACjB;EAEiB,KAAA,iBAAA;GACf,KAAK,kBAAkB;GACvB,KAAK,qBAAqB;GAC1B,KAAK,SAAS;EAChB;EAEiB,KAAA,iBAAA;GACf,KAAK,kBAAkB;GACvB,KAAK,SAAS;EAChB;EAEoB,KAAA,oBAAA,UAAmC;GACrD,KAAK,kBAAkB;GACvB,IAAI,UAAU,WACZ,KAAK,qBAAqB;GAE5B,KAAK,UAAU,UAAU;EAC3B;EAEc,KAAA,cAAA;GACZ,KAAK,kBAAkB;GACvB,MAAM,iBAAiB,KAAK;GAC5B,KAAK,kBAAkB;GACvB,OAAO;EACT;EA5EE,KAAK,MAAA,GAAA,KAAA,GAAA,CAAY;EACjB,KAAK,YAAY,KAAK,IAAI;EAC1B,KAAK,iBAAiB,KAAK,IAAI;EAC/B,KAAK,kBAAkB;EAEvB,KAAK,SAAS;EACd,KAAK,UAAU,OAAO,aAAa,cAAc,SAAS,oBAAoB,YAAY;EAC1F,KAAK,UAAU,OAAO,aAAa,cAAc,SAAS,SAAS,IAAI;CACzE;AAqEF;AAEA,MAAa,UAAU,IAAI,QAAQ"}
1
+ {"version":3,"file":"session.cjs","names":[],"sources":["../../src/setup/session.ts"],"sourcesContent":["import { v7 as uuidv7 } from 'uuid';\n\nexport const SESSION_TIMEOUT = 30 * 60 * 1000;\n\nclass Session {\n private id: string;\n private startTime: number;\n private lastActiveTime: number;\n private accumulatedTime: number;\n\n private active: boolean;\n private visible: boolean;\n private focused: boolean;\n\n constructor() {\n this.id = uuidv7();\n this.startTime = Date.now();\n this.lastActiveTime = Date.now();\n this.accumulatedTime = 0;\n\n this.active = true;\n this.visible = typeof document !== 'undefined' ? document.visibilityState === 'visible' : true;\n this.focused = typeof document !== 'undefined' ? document.hasFocus() : true;\n }\n\n getId = () => this.id;\n\n isActive = () => this.active;\n isVisible = () => this.visible;\n isFocused = () => this.focused;\n isExpired = () => Date.now() - this.lastActiveTime > SESSION_TIMEOUT;\n\n updateLastActiveTime = () => {\n this.lastActiveTime = Date.now();\n };\n\n updateActive = (active: boolean) => {\n this.active = active;\n };\n\n refresh = () => {\n this.id = uuidv7();\n this.lastActiveTime = Date.now();\n };\n\n updateAccumulator = () => {\n const now = Date.now();\n if (this.focused && this.visible && this.active) {\n const delta = now - this.startTime;\n if (delta > 0 && delta < SESSION_TIMEOUT) {\n this.accumulatedTime += delta;\n }\n }\n this.startTime = now;\n };\n\n focus = () => {\n this.updateAccumulator();\n this.updateLastActiveTime();\n this.focused = true;\n };\n\n blur = () => {\n this.updateAccumulator();\n this.focused = false;\n };\n\n pageshow = () => {\n this.updateAccumulator();\n this.updateLastActiveTime();\n this.active = true;\n };\n\n pagehide = () => {\n this.updateAccumulator();\n this.active = false;\n };\n\n visibilitychange = (state: DocumentVisibilityState) => {\n this.updateAccumulator();\n if (state === 'visible') {\n this.updateLastActiveTime();\n }\n this.visible = state === 'visible';\n };\n\n flush = () => {\n this.updateAccumulator();\n const engagementTime = this.accumulatedTime;\n this.accumulatedTime = 0;\n return engagementTime;\n };\n}\n\nlet session: Session | undefined;\n\n/**\n * The session, built the first time something asks for it.\n *\n * Deliberately not a module-scope `new Session()`. The constructor calls\n * `uuidv7()`, `uuid` draws its bytes from `crypto.getRandomValues`, and\n * Cloudflare Workers reject that outside a request handler:\n *\n * Disallowed operation called within global scope. Asynchronous I/O\n * (ex: fetch() or connect()), setting a timeout, and generating random\n * values are not allowed within global scope.\n *\n * Module scope in a Worker is evaluated once when the isolate boots and is\n * then shared by every request that isolate serves, so a random value drawn\n * there would be the same for all of them — which is why the runtime refuses\n * to produce one. A host that server-renders on Workers reaches this module\n * through `track()` on the server as well, and the throw happened as the\n * isolate booted, taking down every route before a component rendered.\n *\n * Everything here is per-visitor browser or app state, so deferring the\n * construction costs nothing and buys two things: the server bundle can be\n * evaluated, and `startTime` marks when the session actually began rather\n * than when the isolate happened to start.\n */\nexport function getSession() {\n return (session ??= new Session());\n}\n"],"mappings":";;;AAEA,MAAa,kBAAkB,OAAU;AAEzC,IAAM,UAAN,MAAc;CAUZ,cAAc;EAWA,KAAA,cAAA,KAAK;EAEF,KAAA,iBAAA,KAAK;EACJ,KAAA,kBAAA,KAAK;EACL,KAAA,kBAAA,KAAK;EACL,KAAA,kBAAA,KAAK,IAAI,IAAI,KAAK,iBAAiB;EAExB,KAAA,6BAAA;GAC3B,KAAK,iBAAiB,KAAK,IAAI;EACjC;EAEgB,KAAA,gBAAA,WAAoB;GAClC,KAAK,SAAS;EAChB;EAEgB,KAAA,gBAAA;GACd,KAAK,MAAA,GAAA,KAAA,GAAA,CAAY;GACjB,KAAK,iBAAiB,KAAK,IAAI;EACjC;EAE0B,KAAA,0BAAA;GACxB,MAAM,MAAM,KAAK,IAAI;GACrB,IAAI,KAAK,WAAW,KAAK,WAAW,KAAK,QAAQ;IAC/C,MAAM,QAAQ,MAAM,KAAK;IACzB,IAAI,QAAQ,KAAK,QAAA,MACf,KAAK,mBAAmB;GAE5B;GACA,KAAK,YAAY;EACnB;EAEc,KAAA,cAAA;GACZ,KAAK,kBAAkB;GACvB,KAAK,qBAAqB;GAC1B,KAAK,UAAU;EACjB;EAEa,KAAA,aAAA;GACX,KAAK,kBAAkB;GACvB,KAAK,UAAU;EACjB;EAEiB,KAAA,iBAAA;GACf,KAAK,kBAAkB;GACvB,KAAK,qBAAqB;GAC1B,KAAK,SAAS;EAChB;EAEiB,KAAA,iBAAA;GACf,KAAK,kBAAkB;GACvB,KAAK,SAAS;EAChB;EAEoB,KAAA,oBAAA,UAAmC;GACrD,KAAK,kBAAkB;GACvB,IAAI,UAAU,WACZ,KAAK,qBAAqB;GAE5B,KAAK,UAAU,UAAU;EAC3B;EAEc,KAAA,cAAA;GACZ,KAAK,kBAAkB;GACvB,MAAM,iBAAiB,KAAK;GAC5B,KAAK,kBAAkB;GACvB,OAAO;EACT;EA5EE,KAAK,MAAA,GAAA,KAAA,GAAA,CAAY;EACjB,KAAK,YAAY,KAAK,IAAI;EAC1B,KAAK,iBAAiB,KAAK,IAAI;EAC/B,KAAK,kBAAkB;EAEvB,KAAK,SAAS;EACd,KAAK,UAAU,OAAO,aAAa,cAAc,SAAS,oBAAoB,YAAY;EAC1F,KAAK,UAAU,OAAO,aAAa,cAAc,SAAS,SAAS,IAAI;CACzE;AAqEF;AAEA,IAAI;;;;;;;;;;;;;;;;;;;;;;;;AAyBJ,SAAgB,aAAa;CAC3B,OAAQ,YAAY,IAAI,QAAQ;AAClC"}
@@ -25,7 +25,30 @@ declare class Session {
25
25
  visibilitychange: (state: DocumentVisibilityState) => void;
26
26
  flush: () => number;
27
27
  }
28
- declare const session: Session;
28
+ /**
29
+ * The session, built the first time something asks for it.
30
+ *
31
+ * Deliberately not a module-scope `new Session()`. The constructor calls
32
+ * `uuidv7()`, `uuid` draws its bytes from `crypto.getRandomValues`, and
33
+ * Cloudflare Workers reject that outside a request handler:
34
+ *
35
+ * Disallowed operation called within global scope. Asynchronous I/O
36
+ * (ex: fetch() or connect()), setting a timeout, and generating random
37
+ * values are not allowed within global scope.
38
+ *
39
+ * Module scope in a Worker is evaluated once when the isolate boots and is
40
+ * then shared by every request that isolate serves, so a random value drawn
41
+ * there would be the same for all of them — which is why the runtime refuses
42
+ * to produce one. A host that server-renders on Workers reaches this module
43
+ * through `track()` on the server as well, and the throw happened as the
44
+ * isolate booted, taking down every route before a component rendered.
45
+ *
46
+ * Everything here is per-visitor browser or app state, so deferring the
47
+ * construction costs nothing and buys two things: the server bundle can be
48
+ * evaluated, and `startTime` marks when the session actually began rather
49
+ * than when the isolate happened to start.
50
+ */
51
+ declare function getSession(): Session;
29
52
  //#endregion
30
- export { SESSION_TIMEOUT, session };
53
+ export { SESSION_TIMEOUT, getSession };
31
54
  //# sourceMappingURL=session.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.cts","names":[],"sources":["../../src/setup/session.ts"],"mappings":";cAEa;cAEP;UACI;UACA;UACA;UACA;UAEA;UACA;UACA;EAER;EAWA;EAEA;EACA;EACA;EACA;EAEA;EAIA,eAAY;EAIZ;EAKA;EAWA;EAMA;EAKA;EAMA;EAKA,mBAAgB,OAAW;EAQ3B;;cAQW,SAAO"}
1
+ {"version":3,"file":"session.d.cts","names":[],"sources":["../../src/setup/session.ts"],"mappings":";cAEa;cAEP;UACI;UACA;UACA;UACA;UAEA;UACA;UACA;EAER;EAWA;EAEA;EACA;EACA;EACA;EAEA;EAIA,eAAY;EAIZ;EAKA;EAWA;EAMA;EAKA;EAMA;EAKA,mBAAgB,OAAW;EAQ3B;;;;;;;;;;;;;;;;;;;;;;;;;iBAiCc,cAAU"}
@@ -25,7 +25,30 @@ declare class Session {
25
25
  visibilitychange: (state: DocumentVisibilityState) => void;
26
26
  flush: () => number;
27
27
  }
28
- declare const session: Session;
28
+ /**
29
+ * The session, built the first time something asks for it.
30
+ *
31
+ * Deliberately not a module-scope `new Session()`. The constructor calls
32
+ * `uuidv7()`, `uuid` draws its bytes from `crypto.getRandomValues`, and
33
+ * Cloudflare Workers reject that outside a request handler:
34
+ *
35
+ * Disallowed operation called within global scope. Asynchronous I/O
36
+ * (ex: fetch() or connect()), setting a timeout, and generating random
37
+ * values are not allowed within global scope.
38
+ *
39
+ * Module scope in a Worker is evaluated once when the isolate boots and is
40
+ * then shared by every request that isolate serves, so a random value drawn
41
+ * there would be the same for all of them — which is why the runtime refuses
42
+ * to produce one. A host that server-renders on Workers reaches this module
43
+ * through `track()` on the server as well, and the throw happened as the
44
+ * isolate booted, taking down every route before a component rendered.
45
+ *
46
+ * Everything here is per-visitor browser or app state, so deferring the
47
+ * construction costs nothing and buys two things: the server bundle can be
48
+ * evaluated, and `startTime` marks when the session actually began rather
49
+ * than when the isolate happened to start.
50
+ */
51
+ declare function getSession(): Session;
29
52
  //#endregion
30
- export { SESSION_TIMEOUT, session };
53
+ export { SESSION_TIMEOUT, getSession };
31
54
  //# sourceMappingURL=session.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.mts","names":[],"sources":["../../src/setup/session.ts"],"mappings":";cAEa;cAEP;UACI;UACA;UACA;UACA;UAEA;UACA;UACA;EAER;EAWA;EAEA;EACA;EACA;EACA;EAEA;EAIA,eAAY;EAIZ;EAKA;EAWA;EAMA;EAKA;EAMA;EAKA,mBAAgB,OAAW;EAQ3B;;cAQW,SAAO"}
1
+ {"version":3,"file":"session.d.mts","names":[],"sources":["../../src/setup/session.ts"],"mappings":";cAEa;cAEP;UACI;UACA;UACA;UACA;UAEA;UACA;UACA;EAER;EAWA;EAEA;EACA;EACA;EACA;EAEA;EAIA,eAAY;EAIZ;EAKA;EAWA;EAMA;EAKA;EAMA;EAKA,mBAAgB,OAAW;EAQ3B;;;;;;;;;;;;;;;;;;;;;;;;;iBAiCc,cAAU"}
@@ -64,8 +64,34 @@ var Session = class {
64
64
  this.focused = typeof document !== "undefined" ? document.hasFocus() : true;
65
65
  }
66
66
  };
67
- const session = new Session();
67
+ let session;
68
+ /**
69
+ * The session, built the first time something asks for it.
70
+ *
71
+ * Deliberately not a module-scope `new Session()`. The constructor calls
72
+ * `uuidv7()`, `uuid` draws its bytes from `crypto.getRandomValues`, and
73
+ * Cloudflare Workers reject that outside a request handler:
74
+ *
75
+ * Disallowed operation called within global scope. Asynchronous I/O
76
+ * (ex: fetch() or connect()), setting a timeout, and generating random
77
+ * values are not allowed within global scope.
78
+ *
79
+ * Module scope in a Worker is evaluated once when the isolate boots and is
80
+ * then shared by every request that isolate serves, so a random value drawn
81
+ * there would be the same for all of them — which is why the runtime refuses
82
+ * to produce one. A host that server-renders on Workers reaches this module
83
+ * through `track()` on the server as well, and the throw happened as the
84
+ * isolate booted, taking down every route before a component rendered.
85
+ *
86
+ * Everything here is per-visitor browser or app state, so deferring the
87
+ * construction costs nothing and buys two things: the server bundle can be
88
+ * evaluated, and `startTime` marks when the session actually began rather
89
+ * than when the isolate happened to start.
90
+ */
91
+ function getSession() {
92
+ return session ??= new Session();
93
+ }
68
94
  //#endregion
69
- export { SESSION_TIMEOUT, session };
95
+ export { SESSION_TIMEOUT, getSession };
70
96
 
71
97
  //# sourceMappingURL=session.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"session.mjs","names":["uuidv7"],"sources":["../../src/setup/session.ts"],"sourcesContent":["import { v7 as uuidv7 } from 'uuid';\n\nexport const SESSION_TIMEOUT = 30 * 60 * 1000;\n\nclass Session {\n private id: string;\n private startTime: number;\n private lastActiveTime: number;\n private accumulatedTime: number;\n\n private active: boolean;\n private visible: boolean;\n private focused: boolean;\n\n constructor() {\n this.id = uuidv7();\n this.startTime = Date.now();\n this.lastActiveTime = Date.now();\n this.accumulatedTime = 0;\n\n this.active = true;\n this.visible = typeof document !== 'undefined' ? document.visibilityState === 'visible' : true;\n this.focused = typeof document !== 'undefined' ? document.hasFocus() : true;\n }\n\n getId = () => this.id;\n\n isActive = () => this.active;\n isVisible = () => this.visible;\n isFocused = () => this.focused;\n isExpired = () => Date.now() - this.lastActiveTime > SESSION_TIMEOUT;\n\n updateLastActiveTime = () => {\n this.lastActiveTime = Date.now();\n };\n\n updateActive = (active: boolean) => {\n this.active = active;\n };\n\n refresh = () => {\n this.id = uuidv7();\n this.lastActiveTime = Date.now();\n };\n\n updateAccumulator = () => {\n const now = Date.now();\n if (this.focused && this.visible && this.active) {\n const delta = now - this.startTime;\n if (delta > 0 && delta < SESSION_TIMEOUT) {\n this.accumulatedTime += delta;\n }\n }\n this.startTime = now;\n };\n\n focus = () => {\n this.updateAccumulator();\n this.updateLastActiveTime();\n this.focused = true;\n };\n\n blur = () => {\n this.updateAccumulator();\n this.focused = false;\n };\n\n pageshow = () => {\n this.updateAccumulator();\n this.updateLastActiveTime();\n this.active = true;\n };\n\n pagehide = () => {\n this.updateAccumulator();\n this.active = false;\n };\n\n visibilitychange = (state: DocumentVisibilityState) => {\n this.updateAccumulator();\n if (state === 'visible') {\n this.updateLastActiveTime();\n }\n this.visible = state === 'visible';\n };\n\n flush = () => {\n this.updateAccumulator();\n const engagementTime = this.accumulatedTime;\n this.accumulatedTime = 0;\n return engagementTime;\n };\n}\n\nexport const session = new Session();\n"],"mappings":";;AAEA,MAAa,kBAAkB,OAAU;AAEzC,IAAM,UAAN,MAAc;CAUZ,cAAc;EAWA,KAAA,cAAA,KAAK;EAEF,KAAA,iBAAA,KAAK;EACJ,KAAA,kBAAA,KAAK;EACL,KAAA,kBAAA,KAAK;EACL,KAAA,kBAAA,KAAK,IAAI,IAAI,KAAK,iBAAiB;EAExB,KAAA,6BAAA;GAC3B,KAAK,iBAAiB,KAAK,IAAI;EACjC;EAEgB,KAAA,gBAAA,WAAoB;GAClC,KAAK,SAAS;EAChB;EAEgB,KAAA,gBAAA;GACd,KAAK,KAAKA,GAAO;GACjB,KAAK,iBAAiB,KAAK,IAAI;EACjC;EAE0B,KAAA,0BAAA;GACxB,MAAM,MAAM,KAAK,IAAI;GACrB,IAAI,KAAK,WAAW,KAAK,WAAW,KAAK,QAAQ;IAC/C,MAAM,QAAQ,MAAM,KAAK;IACzB,IAAI,QAAQ,KAAK,QAAA,MACf,KAAK,mBAAmB;GAE5B;GACA,KAAK,YAAY;EACnB;EAEc,KAAA,cAAA;GACZ,KAAK,kBAAkB;GACvB,KAAK,qBAAqB;GAC1B,KAAK,UAAU;EACjB;EAEa,KAAA,aAAA;GACX,KAAK,kBAAkB;GACvB,KAAK,UAAU;EACjB;EAEiB,KAAA,iBAAA;GACf,KAAK,kBAAkB;GACvB,KAAK,qBAAqB;GAC1B,KAAK,SAAS;EAChB;EAEiB,KAAA,iBAAA;GACf,KAAK,kBAAkB;GACvB,KAAK,SAAS;EAChB;EAEoB,KAAA,oBAAA,UAAmC;GACrD,KAAK,kBAAkB;GACvB,IAAI,UAAU,WACZ,KAAK,qBAAqB;GAE5B,KAAK,UAAU,UAAU;EAC3B;EAEc,KAAA,cAAA;GACZ,KAAK,kBAAkB;GACvB,MAAM,iBAAiB,KAAK;GAC5B,KAAK,kBAAkB;GACvB,OAAO;EACT;EA5EE,KAAK,KAAKA,GAAO;EACjB,KAAK,YAAY,KAAK,IAAI;EAC1B,KAAK,iBAAiB,KAAK,IAAI;EAC/B,KAAK,kBAAkB;EAEvB,KAAK,SAAS;EACd,KAAK,UAAU,OAAO,aAAa,cAAc,SAAS,oBAAoB,YAAY;EAC1F,KAAK,UAAU,OAAO,aAAa,cAAc,SAAS,SAAS,IAAI;CACzE;AAqEF;AAEA,MAAa,UAAU,IAAI,QAAQ"}
1
+ {"version":3,"file":"session.mjs","names":["uuidv7"],"sources":["../../src/setup/session.ts"],"sourcesContent":["import { v7 as uuidv7 } from 'uuid';\n\nexport const SESSION_TIMEOUT = 30 * 60 * 1000;\n\nclass Session {\n private id: string;\n private startTime: number;\n private lastActiveTime: number;\n private accumulatedTime: number;\n\n private active: boolean;\n private visible: boolean;\n private focused: boolean;\n\n constructor() {\n this.id = uuidv7();\n this.startTime = Date.now();\n this.lastActiveTime = Date.now();\n this.accumulatedTime = 0;\n\n this.active = true;\n this.visible = typeof document !== 'undefined' ? document.visibilityState === 'visible' : true;\n this.focused = typeof document !== 'undefined' ? document.hasFocus() : true;\n }\n\n getId = () => this.id;\n\n isActive = () => this.active;\n isVisible = () => this.visible;\n isFocused = () => this.focused;\n isExpired = () => Date.now() - this.lastActiveTime > SESSION_TIMEOUT;\n\n updateLastActiveTime = () => {\n this.lastActiveTime = Date.now();\n };\n\n updateActive = (active: boolean) => {\n this.active = active;\n };\n\n refresh = () => {\n this.id = uuidv7();\n this.lastActiveTime = Date.now();\n };\n\n updateAccumulator = () => {\n const now = Date.now();\n if (this.focused && this.visible && this.active) {\n const delta = now - this.startTime;\n if (delta > 0 && delta < SESSION_TIMEOUT) {\n this.accumulatedTime += delta;\n }\n }\n this.startTime = now;\n };\n\n focus = () => {\n this.updateAccumulator();\n this.updateLastActiveTime();\n this.focused = true;\n };\n\n blur = () => {\n this.updateAccumulator();\n this.focused = false;\n };\n\n pageshow = () => {\n this.updateAccumulator();\n this.updateLastActiveTime();\n this.active = true;\n };\n\n pagehide = () => {\n this.updateAccumulator();\n this.active = false;\n };\n\n visibilitychange = (state: DocumentVisibilityState) => {\n this.updateAccumulator();\n if (state === 'visible') {\n this.updateLastActiveTime();\n }\n this.visible = state === 'visible';\n };\n\n flush = () => {\n this.updateAccumulator();\n const engagementTime = this.accumulatedTime;\n this.accumulatedTime = 0;\n return engagementTime;\n };\n}\n\nlet session: Session | undefined;\n\n/**\n * The session, built the first time something asks for it.\n *\n * Deliberately not a module-scope `new Session()`. The constructor calls\n * `uuidv7()`, `uuid` draws its bytes from `crypto.getRandomValues`, and\n * Cloudflare Workers reject that outside a request handler:\n *\n * Disallowed operation called within global scope. Asynchronous I/O\n * (ex: fetch() or connect()), setting a timeout, and generating random\n * values are not allowed within global scope.\n *\n * Module scope in a Worker is evaluated once when the isolate boots and is\n * then shared by every request that isolate serves, so a random value drawn\n * there would be the same for all of them — which is why the runtime refuses\n * to produce one. A host that server-renders on Workers reaches this module\n * through `track()` on the server as well, and the throw happened as the\n * isolate booted, taking down every route before a component rendered.\n *\n * Everything here is per-visitor browser or app state, so deferring the\n * construction costs nothing and buys two things: the server bundle can be\n * evaluated, and `startTime` marks when the session actually began rather\n * than when the isolate happened to start.\n */\nexport function getSession() {\n return (session ??= new Session());\n}\n"],"mappings":";;AAEA,MAAa,kBAAkB,OAAU;AAEzC,IAAM,UAAN,MAAc;CAUZ,cAAc;EAWA,KAAA,cAAA,KAAK;EAEF,KAAA,iBAAA,KAAK;EACJ,KAAA,kBAAA,KAAK;EACL,KAAA,kBAAA,KAAK;EACL,KAAA,kBAAA,KAAK,IAAI,IAAI,KAAK,iBAAiB;EAExB,KAAA,6BAAA;GAC3B,KAAK,iBAAiB,KAAK,IAAI;EACjC;EAEgB,KAAA,gBAAA,WAAoB;GAClC,KAAK,SAAS;EAChB;EAEgB,KAAA,gBAAA;GACd,KAAK,KAAKA,GAAO;GACjB,KAAK,iBAAiB,KAAK,IAAI;EACjC;EAE0B,KAAA,0BAAA;GACxB,MAAM,MAAM,KAAK,IAAI;GACrB,IAAI,KAAK,WAAW,KAAK,WAAW,KAAK,QAAQ;IAC/C,MAAM,QAAQ,MAAM,KAAK;IACzB,IAAI,QAAQ,KAAK,QAAA,MACf,KAAK,mBAAmB;GAE5B;GACA,KAAK,YAAY;EACnB;EAEc,KAAA,cAAA;GACZ,KAAK,kBAAkB;GACvB,KAAK,qBAAqB;GAC1B,KAAK,UAAU;EACjB;EAEa,KAAA,aAAA;GACX,KAAK,kBAAkB;GACvB,KAAK,UAAU;EACjB;EAEiB,KAAA,iBAAA;GACf,KAAK,kBAAkB;GACvB,KAAK,qBAAqB;GAC1B,KAAK,SAAS;EAChB;EAEiB,KAAA,iBAAA;GACf,KAAK,kBAAkB;GACvB,KAAK,SAAS;EAChB;EAEoB,KAAA,oBAAA,UAAmC;GACrD,KAAK,kBAAkB;GACvB,IAAI,UAAU,WACZ,KAAK,qBAAqB;GAE5B,KAAK,UAAU,UAAU;EAC3B;EAEc,KAAA,cAAA;GACZ,KAAK,kBAAkB;GACvB,MAAM,iBAAiB,KAAK;GAC5B,KAAK,kBAAkB;GACvB,OAAO;EACT;EA5EE,KAAK,KAAKA,GAAO;EACjB,KAAK,YAAY,KAAK,IAAI;EAC1B,KAAK,iBAAiB,KAAK,IAAI;EAC/B,KAAK,kBAAkB;EAEvB,KAAK,SAAS;EACd,KAAK,UAAU,OAAO,aAAa,cAAc,SAAS,oBAAoB,YAAY;EAC1F,KAAK,UAAU,OAAO,aAAa,cAAc,SAAS,SAAS,IAAI;CACzE;AAqEF;AAEA,IAAI;;;;;;;;;;;;;;;;;;;;;;;;AAyBJ,SAAgB,aAAa;CAC3B,OAAQ,YAAY,IAAI,QAAQ;AAClC"}
@@ -6,24 +6,34 @@ const require_visitor_index = require("../visitor/index.cjs");
6
6
  let _shware_utils = require("@shware/utils");
7
7
  //#region src/track/index.ts
8
8
  const defaultOptions = { enableThirdPartyTracking: true };
9
- const tokenBucket = new _shware_utils.TokenBucket({
10
- rate: 1,
11
- capacity: 20,
12
- requested: 2
13
- });
9
+ let tokenBucket;
10
+ /**
11
+ * The rate limiter, built on the first send rather than at module scope: its
12
+ * constructor starts a refill `setInterval`, and Cloudflare Workers refuse to
13
+ * set a timer outside a request handler for the same reason they refuse to
14
+ * generate random values there — see `setup/session.ts`.
15
+ */
16
+ function getTokenBucket() {
17
+ return tokenBucket ??= new _shware_utils.TokenBucket({
18
+ rate: 1,
19
+ capacity: 20,
20
+ requested: 2
21
+ });
22
+ }
14
23
  async function sendEvents(events) {
15
24
  try {
16
25
  if (events.length === 0) return;
17
- if (require_setup_session.session.isExpired()) {
18
- require_setup_session.session.refresh();
26
+ const session = require_setup_session.getSession();
27
+ if (session.isExpired()) {
28
+ session.refresh();
19
29
  events.unshift({
20
30
  name: "session_start",
21
31
  properties: {},
22
32
  options: { enableThirdPartyTracking: false },
23
33
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
24
34
  });
25
- } else require_setup_session.session.updateLastActiveTime();
26
- await tokenBucket.removeTokens();
35
+ } else session.updateLastActiveTime();
36
+ await getTokenBucket().removeTokens();
27
37
  const tags = await require_setup_index.config.getTags();
28
38
  const visitor_id = (await require_visitor_index.getVisitor()).id;
29
39
  const dto = events.map((event) => ({
@@ -31,7 +41,7 @@ async function sendEvents(events) {
31
41
  properties: event.properties,
32
42
  tags,
33
43
  visitor_id,
34
- session_id: require_setup_session.session.getId(),
44
+ session_id: session.getId(),
35
45
  platform: require_setup_index.config.platform,
36
46
  environment: require_setup_index.config.environment,
37
47
  timestamp: event.timestamp
@@ -98,13 +108,14 @@ async function trackAsync(name, properties, options = defaultOptions) {
98
108
  }
99
109
  function sendBeacon(name, properties) {
100
110
  if (!require_setup_index.cache.tags || !require_setup_index.cache.visitor) return;
101
- require_setup_session.session.updateLastActiveTime();
111
+ const session = require_setup_session.getSession();
112
+ session.updateLastActiveTime();
102
113
  const dto = [{
103
114
  name,
104
115
  properties,
105
116
  tags: require_setup_index.cache.tags,
106
117
  visitor_id: require_setup_index.cache.visitor.id,
107
- session_id: require_setup_session.session.getId(),
118
+ session_id: session.getId(),
108
119
  platform: require_setup_index.config.platform,
109
120
  environment: require_setup_index.config.environment,
110
121
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["TokenBucket","session","config","getVisitor","IGNORED_EVENTS","cache"],"sources":["../../src/track/index.ts"],"sourcesContent":["import { TokenBucket, fetch } from '@shware/utils';\nimport type { CreateTrackEventDTO } from '../schema/index';\nimport { cache, config } from '../setup/index';\nimport { session } from '../setup/session';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { getVisitor } from '../visitor/index';\nimport type { EventName, TrackEventResponse, TrackName, TrackProperties } from './types';\n\nexport interface TrackOptions {\n enableThirdPartyTracking?: boolean;\n onSucceed?: (response?: TrackEventResponse[number]) => void;\n onError?: (error: unknown) => void;\n}\n\nconst defaultOptions: TrackOptions = { enableThirdPartyTracking: true };\nconst tokenBucket = new TokenBucket({ rate: 1, capacity: 20, requested: 2 });\n\ntype Item = {\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n name: TrackName<any>;\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n properties: TrackProperties<any>;\n timestamp: string;\n options: TrackOptions;\n};\n\nasync function sendEvents(events: Item[]) {\n try {\n if (events.length === 0) return;\n\n if (session.isExpired()) {\n session.refresh();\n events.unshift({\n name: 'session_start',\n properties: {},\n options: { enableThirdPartyTracking: false },\n timestamp: new Date().toISOString(),\n });\n } else {\n session.updateLastActiveTime();\n }\n\n await tokenBucket.removeTokens();\n\n const tags = await config.getTags();\n const visitor_id = (await getVisitor()).id;\n\n const dto: CreateTrackEventDTO = events.map((event) => ({\n name: event.name,\n properties: event.properties,\n tags,\n visitor_id,\n session_id: session.getId(),\n platform: config.platform,\n environment: config.environment,\n timestamp: event.timestamp,\n }));\n\n const response = await fetch(`${config.endpoint}/events`, {\n method: 'POST',\n credentials: 'include',\n headers: await config.getHeaders(),\n body: JSON.stringify(dto),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to send track event: ${response.status} ${await response.text()}`);\n }\n\n const data = (await response.json()) as TrackEventResponse;\n\n let index = 0;\n while (events.length > 0) {\n const event = events.shift();\n if (!event) {\n index++;\n continue;\n }\n const { options, name, properties } = event;\n const eventId = data[index].id;\n options.onSucceed?.({ id: eventId });\n index++;\n if (!options.enableThirdPartyTracking || IGNORED_EVENTS.includes(name)) {\n continue;\n }\n config.thirdPartyTrackers.forEach((tracker) => tracker(name, properties, eventId));\n }\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n events.forEach((event) => event.options.onError?.(e));\n }\n}\n\nconst batch = 10;\nconst delay = 2000;\nconst list: Item[] = [];\nlet timer: ReturnType<typeof setTimeout> | null = null;\n\nexport function track<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n list.push({ name, properties, options, timestamp: new Date().toISOString() });\n if (list.length >= batch) {\n const copy = [...list];\n list.length = 0;\n void sendEvents(copy);\n return;\n }\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n const copy = [...list];\n list.length = 0;\n void sendEvents(copy);\n }, delay);\n}\n\nexport async function trackAsync<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n await sendEvents([{ name, properties, options, timestamp: new Date().toISOString() }]);\n}\n\nexport function sendBeacon<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n) {\n if (!cache.tags || !cache.visitor) return;\n session.updateLastActiveTime();\n\n const dto: CreateTrackEventDTO = [\n {\n name,\n properties,\n tags: cache.tags,\n visitor_id: cache.visitor.id,\n session_id: session.getId(),\n platform: config.platform,\n environment: config.environment,\n timestamp: new Date().toISOString(),\n },\n ];\n const blob = new Blob([JSON.stringify(dto)], { type: 'application/json' });\n const success = navigator.sendBeacon(`${config.endpoint}/events`, blob);\n if (success) return;\n console.warn('Failed to send beacon', name, properties);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["TokenBucket","getSession","config","getVisitor","IGNORED_EVENTS","cache"],"sources":["../../src/track/index.ts"],"sourcesContent":["import { TokenBucket, fetch } from '@shware/utils';\nimport type { CreateTrackEventDTO } from '../schema/index';\nimport { cache, config } from '../setup/index';\nimport { getSession } from '../setup/session';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { getVisitor } from '../visitor/index';\nimport type { EventName, TrackEventResponse, TrackName, TrackProperties } from './types';\n\nexport interface TrackOptions {\n enableThirdPartyTracking?: boolean;\n onSucceed?: (response?: TrackEventResponse[number]) => void;\n onError?: (error: unknown) => void;\n}\n\nconst defaultOptions: TrackOptions = { enableThirdPartyTracking: true };\n\nlet tokenBucket: TokenBucket | undefined;\n\n/**\n * The rate limiter, built on the first send rather than at module scope: its\n * constructor starts a refill `setInterval`, and Cloudflare Workers refuse to\n * set a timer outside a request handler for the same reason they refuse to\n * generate random values there — see `setup/session.ts`.\n */\nfunction getTokenBucket() {\n return (tokenBucket ??= new TokenBucket({ rate: 1, capacity: 20, requested: 2 }));\n}\n\ntype Item = {\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n name: TrackName<any>;\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n properties: TrackProperties<any>;\n timestamp: string;\n options: TrackOptions;\n};\n\nasync function sendEvents(events: Item[]) {\n try {\n if (events.length === 0) return;\n\n const session = getSession();\n if (session.isExpired()) {\n session.refresh();\n events.unshift({\n name: 'session_start',\n properties: {},\n options: { enableThirdPartyTracking: false },\n timestamp: new Date().toISOString(),\n });\n } else {\n session.updateLastActiveTime();\n }\n\n await getTokenBucket().removeTokens();\n\n const tags = await config.getTags();\n const visitor_id = (await getVisitor()).id;\n\n const dto: CreateTrackEventDTO = events.map((event) => ({\n name: event.name,\n properties: event.properties,\n tags,\n visitor_id,\n session_id: session.getId(),\n platform: config.platform,\n environment: config.environment,\n timestamp: event.timestamp,\n }));\n\n const response = await fetch(`${config.endpoint}/events`, {\n method: 'POST',\n credentials: 'include',\n headers: await config.getHeaders(),\n body: JSON.stringify(dto),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to send track event: ${response.status} ${await response.text()}`);\n }\n\n const data = (await response.json()) as TrackEventResponse;\n\n let index = 0;\n while (events.length > 0) {\n const event = events.shift();\n if (!event) {\n index++;\n continue;\n }\n const { options, name, properties } = event;\n const eventId = data[index].id;\n options.onSucceed?.({ id: eventId });\n index++;\n if (!options.enableThirdPartyTracking || IGNORED_EVENTS.includes(name)) {\n continue;\n }\n config.thirdPartyTrackers.forEach((tracker) => tracker(name, properties, eventId));\n }\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n events.forEach((event) => event.options.onError?.(e));\n }\n}\n\nconst batch = 10;\nconst delay = 2000;\nconst list: Item[] = [];\nlet timer: ReturnType<typeof setTimeout> | null = null;\n\nexport function track<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n list.push({ name, properties, options, timestamp: new Date().toISOString() });\n if (list.length >= batch) {\n const copy = [...list];\n list.length = 0;\n void sendEvents(copy);\n return;\n }\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n const copy = [...list];\n list.length = 0;\n void sendEvents(copy);\n }, delay);\n}\n\nexport async function trackAsync<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n await sendEvents([{ name, properties, options, timestamp: new Date().toISOString() }]);\n}\n\nexport function sendBeacon<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n) {\n if (!cache.tags || !cache.visitor) return;\n\n const session = getSession();\n session.updateLastActiveTime();\n\n const dto: CreateTrackEventDTO = [\n {\n name,\n properties,\n tags: cache.tags,\n visitor_id: cache.visitor.id,\n session_id: session.getId(),\n platform: config.platform,\n environment: config.environment,\n timestamp: new Date().toISOString(),\n },\n ];\n const blob = new Blob([JSON.stringify(dto)], { type: 'application/json' });\n const success = navigator.sendBeacon(`${config.endpoint}/events`, blob);\n if (success) return;\n console.warn('Failed to send beacon', name, properties);\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/track/index.ts"],"mappings":";;UAQiB;EACf;EACA,aAAa,WAAW;EACxB,WAAW;;iBAuFG,MAAM,UAAU,YAAY,WAC1C,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B,UAAS;iBAkBW,WAAW,UAAU,YAAY,WACrD,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B,UAAS,eAA6B;iBAKxB,WAAW,UAAU,YAAY,WAC/C,MAAM,UAAU,IAChB,aAAa,gBAAgB"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/track/index.ts"],"mappings":";;UAQiB;EACf;EACA,aAAa,WAAW;EACxB,WAAW;;iBAmGG,MAAM,UAAU,YAAY,WAC1C,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B,UAAS;iBAkBW,WAAW,UAAU,YAAY,WACrD,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B,UAAS,eAA6B;iBAKxB,WAAW,UAAU,YAAY,WAC/C,MAAM,UAAU,IAChB,aAAa,gBAAgB"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/track/index.ts"],"mappings":";;UAQiB;EACf;EACA,aAAa,WAAW;EACxB,WAAW;;iBAuFG,MAAM,UAAU,YAAY,WAC1C,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B,UAAS;iBAkBW,WAAW,UAAU,YAAY,WACrD,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B,UAAS,eAA6B;iBAKxB,WAAW,UAAU,YAAY,WAC/C,MAAM,UAAU,IAChB,aAAa,gBAAgB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/track/index.ts"],"mappings":";;UAQiB;EACf;EACA,aAAa,WAAW;EACxB,WAAW;;iBAmGG,MAAM,UAAU,YAAY,WAC1C,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B,UAAS;iBAkBW,WAAW,UAAU,YAAY,WACrD,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B,UAAS,eAA6B;iBAKxB,WAAW,UAAU,YAAY,WAC/C,MAAM,UAAU,IAChB,aAAa,gBAAgB"}
@@ -1,18 +1,28 @@
1
1
  import { cache, config } from "../setup/index.mjs";
2
- import { session } from "../setup/session.mjs";
2
+ import { getSession } from "../setup/session.mjs";
3
3
  import { IGNORED_EVENTS } from "../third-parties/ignored-events.mjs";
4
4
  import { getVisitor } from "../visitor/index.mjs";
5
5
  import { TokenBucket, fetch } from "@shware/utils";
6
6
  //#region src/track/index.ts
7
7
  const defaultOptions = { enableThirdPartyTracking: true };
8
- const tokenBucket = new TokenBucket({
9
- rate: 1,
10
- capacity: 20,
11
- requested: 2
12
- });
8
+ let tokenBucket;
9
+ /**
10
+ * The rate limiter, built on the first send rather than at module scope: its
11
+ * constructor starts a refill `setInterval`, and Cloudflare Workers refuse to
12
+ * set a timer outside a request handler for the same reason they refuse to
13
+ * generate random values there — see `setup/session.ts`.
14
+ */
15
+ function getTokenBucket() {
16
+ return tokenBucket ??= new TokenBucket({
17
+ rate: 1,
18
+ capacity: 20,
19
+ requested: 2
20
+ });
21
+ }
13
22
  async function sendEvents(events) {
14
23
  try {
15
24
  if (events.length === 0) return;
25
+ const session = getSession();
16
26
  if (session.isExpired()) {
17
27
  session.refresh();
18
28
  events.unshift({
@@ -22,7 +32,7 @@ async function sendEvents(events) {
22
32
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
23
33
  });
24
34
  } else session.updateLastActiveTime();
25
- await tokenBucket.removeTokens();
35
+ await getTokenBucket().removeTokens();
26
36
  const tags = await config.getTags();
27
37
  const visitor_id = (await getVisitor()).id;
28
38
  const dto = events.map((event) => ({
@@ -97,6 +107,7 @@ async function trackAsync(name, properties, options = defaultOptions) {
97
107
  }
98
108
  function sendBeacon(name, properties) {
99
109
  if (!cache.tags || !cache.visitor) return;
110
+ const session = getSession();
100
111
  session.updateLastActiveTime();
101
112
  const dto = [{
102
113
  name,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/track/index.ts"],"sourcesContent":["import { TokenBucket, fetch } from '@shware/utils';\nimport type { CreateTrackEventDTO } from '../schema/index';\nimport { cache, config } from '../setup/index';\nimport { session } from '../setup/session';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { getVisitor } from '../visitor/index';\nimport type { EventName, TrackEventResponse, TrackName, TrackProperties } from './types';\n\nexport interface TrackOptions {\n enableThirdPartyTracking?: boolean;\n onSucceed?: (response?: TrackEventResponse[number]) => void;\n onError?: (error: unknown) => void;\n}\n\nconst defaultOptions: TrackOptions = { enableThirdPartyTracking: true };\nconst tokenBucket = new TokenBucket({ rate: 1, capacity: 20, requested: 2 });\n\ntype Item = {\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n name: TrackName<any>;\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n properties: TrackProperties<any>;\n timestamp: string;\n options: TrackOptions;\n};\n\nasync function sendEvents(events: Item[]) {\n try {\n if (events.length === 0) return;\n\n if (session.isExpired()) {\n session.refresh();\n events.unshift({\n name: 'session_start',\n properties: {},\n options: { enableThirdPartyTracking: false },\n timestamp: new Date().toISOString(),\n });\n } else {\n session.updateLastActiveTime();\n }\n\n await tokenBucket.removeTokens();\n\n const tags = await config.getTags();\n const visitor_id = (await getVisitor()).id;\n\n const dto: CreateTrackEventDTO = events.map((event) => ({\n name: event.name,\n properties: event.properties,\n tags,\n visitor_id,\n session_id: session.getId(),\n platform: config.platform,\n environment: config.environment,\n timestamp: event.timestamp,\n }));\n\n const response = await fetch(`${config.endpoint}/events`, {\n method: 'POST',\n credentials: 'include',\n headers: await config.getHeaders(),\n body: JSON.stringify(dto),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to send track event: ${response.status} ${await response.text()}`);\n }\n\n const data = (await response.json()) as TrackEventResponse;\n\n let index = 0;\n while (events.length > 0) {\n const event = events.shift();\n if (!event) {\n index++;\n continue;\n }\n const { options, name, properties } = event;\n const eventId = data[index].id;\n options.onSucceed?.({ id: eventId });\n index++;\n if (!options.enableThirdPartyTracking || IGNORED_EVENTS.includes(name)) {\n continue;\n }\n config.thirdPartyTrackers.forEach((tracker) => tracker(name, properties, eventId));\n }\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n events.forEach((event) => event.options.onError?.(e));\n }\n}\n\nconst batch = 10;\nconst delay = 2000;\nconst list: Item[] = [];\nlet timer: ReturnType<typeof setTimeout> | null = null;\n\nexport function track<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n list.push({ name, properties, options, timestamp: new Date().toISOString() });\n if (list.length >= batch) {\n const copy = [...list];\n list.length = 0;\n void sendEvents(copy);\n return;\n }\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n const copy = [...list];\n list.length = 0;\n void sendEvents(copy);\n }, delay);\n}\n\nexport async function trackAsync<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n await sendEvents([{ name, properties, options, timestamp: new Date().toISOString() }]);\n}\n\nexport function sendBeacon<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n) {\n if (!cache.tags || !cache.visitor) return;\n session.updateLastActiveTime();\n\n const dto: CreateTrackEventDTO = [\n {\n name,\n properties,\n tags: cache.tags,\n visitor_id: cache.visitor.id,\n session_id: session.getId(),\n platform: config.platform,\n environment: config.environment,\n timestamp: new Date().toISOString(),\n },\n ];\n const blob = new Blob([JSON.stringify(dto)], { type: 'application/json' });\n const success = navigator.sendBeacon(`${config.endpoint}/events`, blob);\n if (success) return;\n console.warn('Failed to send beacon', name, properties);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/track/index.ts"],"sourcesContent":["import { TokenBucket, fetch } from '@shware/utils';\nimport type { CreateTrackEventDTO } from '../schema/index';\nimport { cache, config } from '../setup/index';\nimport { getSession } from '../setup/session';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { getVisitor } from '../visitor/index';\nimport type { EventName, TrackEventResponse, TrackName, TrackProperties } from './types';\n\nexport interface TrackOptions {\n enableThirdPartyTracking?: boolean;\n onSucceed?: (response?: TrackEventResponse[number]) => void;\n onError?: (error: unknown) => void;\n}\n\nconst defaultOptions: TrackOptions = { enableThirdPartyTracking: true };\n\nlet tokenBucket: TokenBucket | undefined;\n\n/**\n * The rate limiter, built on the first send rather than at module scope: its\n * constructor starts a refill `setInterval`, and Cloudflare Workers refuse to\n * set a timer outside a request handler for the same reason they refuse to\n * generate random values there — see `setup/session.ts`.\n */\nfunction getTokenBucket() {\n return (tokenBucket ??= new TokenBucket({ rate: 1, capacity: 20, requested: 2 }));\n}\n\ntype Item = {\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n name: TrackName<any>;\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n properties: TrackProperties<any>;\n timestamp: string;\n options: TrackOptions;\n};\n\nasync function sendEvents(events: Item[]) {\n try {\n if (events.length === 0) return;\n\n const session = getSession();\n if (session.isExpired()) {\n session.refresh();\n events.unshift({\n name: 'session_start',\n properties: {},\n options: { enableThirdPartyTracking: false },\n timestamp: new Date().toISOString(),\n });\n } else {\n session.updateLastActiveTime();\n }\n\n await getTokenBucket().removeTokens();\n\n const tags = await config.getTags();\n const visitor_id = (await getVisitor()).id;\n\n const dto: CreateTrackEventDTO = events.map((event) => ({\n name: event.name,\n properties: event.properties,\n tags,\n visitor_id,\n session_id: session.getId(),\n platform: config.platform,\n environment: config.environment,\n timestamp: event.timestamp,\n }));\n\n const response = await fetch(`${config.endpoint}/events`, {\n method: 'POST',\n credentials: 'include',\n headers: await config.getHeaders(),\n body: JSON.stringify(dto),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to send track event: ${response.status} ${await response.text()}`);\n }\n\n const data = (await response.json()) as TrackEventResponse;\n\n let index = 0;\n while (events.length > 0) {\n const event = events.shift();\n if (!event) {\n index++;\n continue;\n }\n const { options, name, properties } = event;\n const eventId = data[index].id;\n options.onSucceed?.({ id: eventId });\n index++;\n if (!options.enableThirdPartyTracking || IGNORED_EVENTS.includes(name)) {\n continue;\n }\n config.thirdPartyTrackers.forEach((tracker) => tracker(name, properties, eventId));\n }\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n events.forEach((event) => event.options.onError?.(e));\n }\n}\n\nconst batch = 10;\nconst delay = 2000;\nconst list: Item[] = [];\nlet timer: ReturnType<typeof setTimeout> | null = null;\n\nexport function track<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n list.push({ name, properties, options, timestamp: new Date().toISOString() });\n if (list.length >= batch) {\n const copy = [...list];\n list.length = 0;\n void sendEvents(copy);\n return;\n }\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n const copy = [...list];\n list.length = 0;\n void sendEvents(copy);\n }, delay);\n}\n\nexport async function trackAsync<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n await sendEvents([{ name, properties, options, timestamp: new Date().toISOString() }]);\n}\n\nexport function sendBeacon<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n) {\n if (!cache.tags || !cache.visitor) return;\n\n const session = getSession();\n session.updateLastActiveTime();\n\n const dto: CreateTrackEventDTO = [\n {\n name,\n properties,\n tags: cache.tags,\n visitor_id: cache.visitor.id,\n session_id: session.getId(),\n platform: config.platform,\n environment: config.environment,\n timestamp: new Date().toISOString(),\n },\n ];\n const blob = new Blob([JSON.stringify(dto)], { type: 'application/json' });\n const success = navigator.sendBeacon(`${config.endpoint}/events`, blob);\n if (success) return;\n console.warn('Failed to send beacon', name, properties);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shware/analytics",
3
- "version": "5.1.0",
3
+ "version": "5.1.2",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -128,13 +128,13 @@
128
128
  "dependencies": {
129
129
  "cookie": "^2.0.1",
130
130
  "uuid": "^14.0.1",
131
- "web-vitals": "^6.1.0",
131
+ "web-vitals": "^6.1.1",
132
132
  "zod": "^4.4.3",
133
- "@shware/utils": "^1.5.2"
133
+ "@shware/utils": "^1.6.0"
134
134
  },
135
135
  "devDependencies": {
136
136
  "@types/facebook-nodejs-business-sdk": "^24.0.0",
137
- "@types/node": "^26.1.2",
137
+ "@types/node": "^26.2.0",
138
138
  "@types/react": "^19.2.18",
139
139
  "bowser": "^2.14.1",
140
140
  "typescript": "^7.0.2",
@@ -142,9 +142,9 @@
142
142
  "@repo/typescript-config": "0.0.0"
143
143
  },
144
144
  "peerDependencies": {
145
- "@react-native-firebase/analytics": "^26.0.0",
146
- "@tanstack/react-router": "^1.170.18",
147
- "@tanstack/react-start": "^1.168.34",
145
+ "@react-native-firebase/analytics": "^26.2.0",
146
+ "@tanstack/react-router": "^1.170.29",
147
+ "@tanstack/react-start": "^1.168.46",
148
148
  "expo-application": "^57.0.2",
149
149
  "expo-clipboard": "^57.0.1",
150
150
  "expo-crypto": "^57.0.1",
@@ -153,10 +153,10 @@
153
153
  "expo-sqlite": "^57.0.1",
154
154
  "expo-tracking-transparency": "^57.0.1",
155
155
  "facebook-nodejs-business-sdk": "^24.0.1",
156
- "next": "^16.2.12",
157
- "posthog-js": "^1.409.5",
156
+ "next": "^16.3.1",
157
+ "posthog-js": "^1.417.1",
158
158
  "react": "^19.2.8",
159
- "react-native": "^0.86.2",
159
+ "react-native": "^0.87.0",
160
160
  "react-native-fbsdk-next": "^13.4.3",
161
161
  "react-native-url-polyfill": "^4.0.0",
162
162
  "react-router": "^8.3.0"