@shware/analytics 11.3.0 → 11.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hooks/use-app-analytics.cjs +1 -0
- package/dist/hooks/use-app-analytics.cjs.map +1 -1
- package/dist/hooks/use-app-analytics.mjs +2 -1
- package/dist/hooks/use-app-analytics.mjs.map +1 -1
- package/dist/hooks/use-web-analytics.cjs +3 -2
- package/dist/hooks/use-web-analytics.cjs.map +1 -1
- package/dist/hooks/use-web-analytics.d.cts.map +1 -1
- package/dist/hooks/use-web-analytics.d.mts.map +1 -1
- package/dist/hooks/use-web-analytics.mjs +3 -2
- package/dist/hooks/use-web-analytics.mjs.map +1 -1
- package/dist/next/index.cjs +1 -1
- package/dist/next/index.mjs +1 -1
- package/dist/react-router/index.cjs +1 -1
- package/dist/react-router/index.mjs +1 -1
- package/dist/web/index.cjs +28 -7
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts +2 -0
- package/dist/web/index.d.cts.map +1 -1
- package/dist/web/index.d.mts +2 -0
- package/dist/web/index.d.mts.map +1 -1
- package/dist/web/index.mjs +28 -8
- package/dist/web/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
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\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;EAEtB,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;
|
|
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 { sendPendingEvents, 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\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 // The app may be suspended or killed soon after going to the background: send the queue\n // now rather than after the batch delay — a new session's session_start may be in it.\n // React Native has no beacon, so this is the usual send, started at once.\n sendPendingEvents();\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;EAEtB,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;IAInB,oBAAA,kBAAkB;GACpB;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,7 +1,7 @@
|
|
|
1
1
|
import { config } from "../setup/index.mjs";
|
|
2
2
|
import { keys } from "../constants/storage.mjs";
|
|
3
3
|
import { getSession } from "../setup/session.mjs";
|
|
4
|
-
import { track } from "../track/index.mjs";
|
|
4
|
+
import { sendPendingEvents, track } from "../track/index.mjs";
|
|
5
5
|
import { usePrevious } from "./use-previous.mjs";
|
|
6
6
|
import { useEffect } from "react";
|
|
7
7
|
import { AppState } from "react-native";
|
|
@@ -33,6 +33,7 @@ function useAppAnalytics(pathname) {
|
|
|
33
33
|
else if (state !== "active" && session.isActive()) {
|
|
34
34
|
session.updateActive(false);
|
|
35
35
|
sendUserEngagement();
|
|
36
|
+
sendPendingEvents();
|
|
36
37
|
}
|
|
37
38
|
});
|
|
38
39
|
return () => subscription.remove();
|
|
@@ -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 { 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\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;EAEtB,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;
|
|
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 { sendPendingEvents, 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\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 // The app may be suspended or killed soon after going to the background: send the queue\n // now rather than after the batch delay — a new session's session_start may be in it.\n // React Native has no beacon, so this is the usual send, started at once.\n sendPendingEvents();\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;EAEtB,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;IAInB,kBAAkB;GACpB;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"}
|
|
@@ -5,6 +5,7 @@ const require_setup_session = require("../setup/session.cjs");
|
|
|
5
5
|
const require_track_index = require("../track/index.cjs");
|
|
6
6
|
const require_hooks_use_previous = require("./use-previous.cjs");
|
|
7
7
|
const require_web_page_key = require("../web/page-key.cjs");
|
|
8
|
+
const require_web_index = require("../web/index.cjs");
|
|
8
9
|
let _shware_utils = require("@shware/utils");
|
|
9
10
|
let react = require("react");
|
|
10
11
|
//#region src/hooks/use-web-analytics.ts
|
|
@@ -13,7 +14,7 @@ function sendFirstVisit(pathname) {
|
|
|
13
14
|
require_track_index.track("first_visit", {
|
|
14
15
|
page_path: pathname,
|
|
15
16
|
page_title: document.title,
|
|
16
|
-
page_referrer:
|
|
17
|
+
page_referrer: require_web_index.getPageReferrer(),
|
|
17
18
|
page_location: window.location.href
|
|
18
19
|
});
|
|
19
20
|
require_setup_index.config.storage.setItem(require_constants_storage.keys.first_visit_time, (/* @__PURE__ */ new Date()).toISOString());
|
|
@@ -111,7 +112,7 @@ function useWebAnalytics(pathname, search) {
|
|
|
111
112
|
require_track_index.track("page_view", {
|
|
112
113
|
page_path: pathname,
|
|
113
114
|
page_title: document.title,
|
|
114
|
-
page_referrer:
|
|
115
|
+
page_referrer: require_web_index.getPageReferrer(),
|
|
115
116
|
page_location: window.location.href,
|
|
116
117
|
previous_page_path: prevPage === void 0 ? void 0 : prevPathname,
|
|
117
118
|
engagement_time_msec: prevPage === void 0 ? void 0 : require_setup_session.getSession().flush()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-web-analytics.cjs","names":["config","keys","getSession","getPageKey","usePrevious","useRef","throttle"],"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, sendPendingEvents, track } from '../track/index';\nimport { getPageKey } from '../web/page-key';\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:
|
|
1
|
+
{"version":3,"file":"use-web-analytics.cjs","names":["config","keys","getPageReferrer","getSession","getPageKey","usePrevious","useRef","throttle"],"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, sendPendingEvents, track } from '../track/index';\nimport { getPageReferrer } from '../web/index';\nimport { getPageKey } from '../web/page-key';\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: getPageReferrer(),\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\n/** Whether the event was actually sent — a zero-engagement crossing reports nothing. */\nfunction sendScroll(): boolean {\n const engagement_time_msec = getSession().flush();\n if (engagement_time_msec <= 0) return false;\n track('scroll', { engagement_time_msec });\n return true;\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\n// The queue goes first: a new session's `session_start` is in it, ahead of the engagement that\n// reports for that session. On `hidden` as well as on `pagehide`, as GA4 does: a mobile browser\n// often discards a hidden page without firing `pagehide`.\nfunction onPageHide() {\n getSession().pagehide();\n sendPendingEvents();\n sendUserEngagement('pagehide');\n}\n\nfunction onVisibilityChange() {\n getSession().visibilitychange(document.visibilityState);\n if (document.visibilityState === 'hidden') {\n sendPendingEvents();\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 * 4. send page_view when the page changes — path or query, see `getPageKey`; `search` is the\n * router's query string (`?a=1`, or `''`), required so no caller forgets that the query is\n * part of what makes a page\n */\nexport function useWebAnalytics(pathname: string, search: string) {\n const page = getPageKey(pathname, search);\n const prevPage = usePrevious(page);\n const prevPathname = usePrevious(pathname);\n\n // reset state when the page changes and send scroll when the user navigates to a new page\n const hasSendScroll = useRef(false);\n useEffect(() => {\n hasSendScroll.current = false;\n }, [page]);\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\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 // A crossing with no engaged time (a restored scroll position in an unfocused window) must\n // not consume the page's one shot: the flag is set only once the event is actually sent.\n hasSendScroll.current = 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, { capture: true }));\n\n onScroll.cancel();\n checkpoint.cancel();\n };\n }, []);\n\n useEffect(() => {\n // `page_path` stays the path — the query is in `page_location` — and the previous page is\n // named by its path too, so a query-only change reports the same path as its predecessor.\n track('page_view', {\n page_path: pathname,\n page_title: document.title,\n page_referrer: getPageReferrer(),\n page_location: window.location.href,\n previous_page_path: prevPage === undefined ? undefined : prevPathname,\n engagement_time_msec: prevPage === undefined ? undefined : getSession().flush(),\n });\n }, [page]);\n}\n"],"mappings":";;;;;;;;;;;AAUA,SAAS,eAAe,UAAkB;CACxC,IAAIA,oBAAAA,OAAO,QAAQ,QAAQC,0BAAAA,KAAK,gBAAgB,GAAG;CACnD,oBAAA,MAAM,eAAe;EACnB,WAAW;EACX,YAAY,SAAS;EACrB,eAAeC,kBAAAA,gBAAgB;EAC/B,eAAe,OAAO,SAAS;CACjC,CAAC;CACD,oBAAA,OAAO,QAAQ,QAAQD,0BAAAA,KAAK,mCAAkB,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC;AACxE;AAEA,SAAS,mBAAmB,SAA0C;CACpE,MAAM,uBAAuBE,sBAAAA,WAAW,CAAC,CAAC,MAAM;CAChD,IAAI,wBAAwB,GAAG;CAC/B,oBAAA,WAAW,mBAAmB;EAAE;EAAsB;CAAQ,CAAC;AACjE;;AAGA,SAAS,aAAsB;CAC7B,MAAM,uBAAuBA,sBAAAA,WAAW,CAAC,CAAC,MAAM;CAChD,IAAI,wBAAwB,GAAG,OAAO;CACtC,oBAAA,MAAM,UAAU,EAAE,qBAAqB,CAAC;CACxC,OAAO;AACT;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;AAKA,SAAS,aAAa;CACpB,sBAAA,WAAW,CAAC,CAAC,SAAS;CACtB,oBAAA,kBAAkB;CAClB,mBAAmB,UAAU;AAC/B;AAEA,SAAS,qBAAqB;CAC5B,sBAAA,WAAW,CAAC,CAAC,iBAAiB,SAAS,eAAe;CACtD,IAAI,SAAS,oBAAoB,UAAU;EACzC,oBAAA,kBAAkB;EAClB,mBAAmB,kBAAkB;CACvC;AACF;;;;;;;;;AAUA,SAAgB,gBAAgB,UAAkB,QAAgB;CAChE,MAAM,OAAOC,qBAAAA,WAAW,UAAU,MAAM;CACxC,MAAM,WAAWC,2BAAAA,YAAY,IAAI;CACjC,MAAM,eAAeA,2BAAAA,YAAY,QAAQ;CAGzC,MAAM,iBAAA,GAAgBC,MAAAA,OAAAA,CAAO,KAAK;CAClC,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,cAAc,UAAU;CAC1B,GAAG,CAAC,IAAI,CAAC;CAET,CAAA,GAAA,MAAA,UAAA,OAAgB;EAGd,MAAM,UAAUH,sBAAAA,WAAW;EAE3B,eAAe,QAAQ;EAEvB,MAAM,YAAA,GAAWI,cAAAA,SAAAA,OAAe;GAC9B,QAAQ,kBAAkB;GAC1B,IAAI,cAAc,SAAS;GAE3B,IAAI,iBAAiB,IAAI,IAAI;GAG7B,cAAc,UAAU,WAAW;EACrC,GAAG,GAAG;EAEN,MAAM,mBAAmB;GAAC;GAAa;GAAW;EAAY;EAC9D,MAAM,cAAA,GAAaA,cAAAA,SAAAA,CAAS,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,YAAY,EAAE,SAAS,KAAK,CAAC,CAAC;GAE5F,SAAS,OAAO;GAChB,WAAW,OAAO;EACpB;CACF,GAAG,CAAC,CAAC;CAEL,CAAA,GAAA,MAAA,UAAA,OAAgB;EAGd,oBAAA,MAAM,aAAa;GACjB,WAAW;GACX,YAAY,SAAS;GACrB,eAAeL,kBAAAA,gBAAgB;GAC/B,eAAe,OAAO,SAAS;GAC/B,oBAAoB,aAAa,KAAA,IAAY,KAAA,IAAY;GACzD,sBAAsB,aAAa,KAAA,IAAY,KAAA,IAAYC,sBAAAA,WAAW,CAAC,CAAC,MAAM;EAChF,CAAC;CACH,GAAG,CAAC,IAAI,CAAC;AACX"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-web-analytics.d.cts","names":[],"sources":["../../src/hooks/use-web-analytics.ts"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"use-web-analytics.d.cts","names":[],"sources":["../../src/hooks/use-web-analytics.ts"],"mappings":";;;;;;;;;wBAoEgB,gBAAgB,kBAAkB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-web-analytics.d.mts","names":[],"sources":["../../src/hooks/use-web-analytics.ts"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"use-web-analytics.d.mts","names":[],"sources":["../../src/hooks/use-web-analytics.ts"],"mappings":";;;;;;;;;wBAoEgB,gBAAgB,kBAAkB"}
|
|
@@ -4,6 +4,7 @@ import { getSession } from "../setup/session.mjs";
|
|
|
4
4
|
import { sendBeacon, sendPendingEvents, track } from "../track/index.mjs";
|
|
5
5
|
import { usePrevious } from "./use-previous.mjs";
|
|
6
6
|
import { getPageKey } from "../web/page-key.mjs";
|
|
7
|
+
import { getPageReferrer } from "../web/index.mjs";
|
|
7
8
|
import { throttle } from "@shware/utils";
|
|
8
9
|
import { useEffect, useRef } from "react";
|
|
9
10
|
//#region src/hooks/use-web-analytics.ts
|
|
@@ -12,7 +13,7 @@ function sendFirstVisit(pathname) {
|
|
|
12
13
|
track("first_visit", {
|
|
13
14
|
page_path: pathname,
|
|
14
15
|
page_title: document.title,
|
|
15
|
-
page_referrer:
|
|
16
|
+
page_referrer: getPageReferrer(),
|
|
16
17
|
page_location: window.location.href
|
|
17
18
|
});
|
|
18
19
|
config.storage.setItem(keys.first_visit_time, (/* @__PURE__ */ new Date()).toISOString());
|
|
@@ -110,7 +111,7 @@ function useWebAnalytics(pathname, search) {
|
|
|
110
111
|
track("page_view", {
|
|
111
112
|
page_path: pathname,
|
|
112
113
|
page_title: document.title,
|
|
113
|
-
page_referrer:
|
|
114
|
+
page_referrer: getPageReferrer(),
|
|
114
115
|
page_location: window.location.href,
|
|
115
116
|
previous_page_path: prevPage === void 0 ? void 0 : prevPathname,
|
|
116
117
|
engagement_time_msec: prevPage === void 0 ? void 0 : getSession().flush()
|
|
@@ -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 { getSession } from '../setup/session';\nimport { sendBeacon, sendPendingEvents, track } from '../track/index';\nimport { getPageKey } from '../web/page-key';\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:
|
|
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, sendPendingEvents, track } from '../track/index';\nimport { getPageReferrer } from '../web/index';\nimport { getPageKey } from '../web/page-key';\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: getPageReferrer(),\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\n/** Whether the event was actually sent — a zero-engagement crossing reports nothing. */\nfunction sendScroll(): boolean {\n const engagement_time_msec = getSession().flush();\n if (engagement_time_msec <= 0) return false;\n track('scroll', { engagement_time_msec });\n return true;\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\n// The queue goes first: a new session's `session_start` is in it, ahead of the engagement that\n// reports for that session. On `hidden` as well as on `pagehide`, as GA4 does: a mobile browser\n// often discards a hidden page without firing `pagehide`.\nfunction onPageHide() {\n getSession().pagehide();\n sendPendingEvents();\n sendUserEngagement('pagehide');\n}\n\nfunction onVisibilityChange() {\n getSession().visibilitychange(document.visibilityState);\n if (document.visibilityState === 'hidden') {\n sendPendingEvents();\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 * 4. send page_view when the page changes — path or query, see `getPageKey`; `search` is the\n * router's query string (`?a=1`, or `''`), required so no caller forgets that the query is\n * part of what makes a page\n */\nexport function useWebAnalytics(pathname: string, search: string) {\n const page = getPageKey(pathname, search);\n const prevPage = usePrevious(page);\n const prevPathname = usePrevious(pathname);\n\n // reset state when the page changes and send scroll when the user navigates to a new page\n const hasSendScroll = useRef(false);\n useEffect(() => {\n hasSendScroll.current = false;\n }, [page]);\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\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 // A crossing with no engaged time (a restored scroll position in an unfocused window) must\n // not consume the page's one shot: the flag is set only once the event is actually sent.\n hasSendScroll.current = 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, { capture: true }));\n\n onScroll.cancel();\n checkpoint.cancel();\n };\n }, []);\n\n useEffect(() => {\n // `page_path` stays the path — the query is in `page_location` — and the previous page is\n // named by its path too, so a query-only change reports the same path as its predecessor.\n track('page_view', {\n page_path: pathname,\n page_title: document.title,\n page_referrer: getPageReferrer(),\n page_location: window.location.href,\n previous_page_path: prevPage === undefined ? undefined : prevPathname,\n engagement_time_msec: prevPage === undefined ? undefined : getSession().flush(),\n });\n }, [page]);\n}\n"],"mappings":"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"}
|
package/dist/next/index.cjs
CHANGED
|
@@ -6,11 +6,11 @@ const require_hooks_use_outbound_click_analytics = require("../hooks/use-outboun
|
|
|
6
6
|
const require_hooks_use_uet_id_sync = require("../hooks/use-uet-id-sync.cjs");
|
|
7
7
|
const require_hooks_use_web_analytics = require("../hooks/use-web-analytics.cjs");
|
|
8
8
|
let react = require("react");
|
|
9
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
10
|
let next_navigation = require("next/navigation");
|
|
10
11
|
let next_script = require("next/script");
|
|
11
12
|
next_script = require_runtime.__toESM(next_script, 1);
|
|
12
13
|
let next_web_vitals = require("next/web-vitals");
|
|
13
|
-
let react_jsx_runtime = require("react/jsx-runtime");
|
|
14
14
|
//#region src/next/index.tsx
|
|
15
15
|
/**
|
|
16
16
|
* Its own component under a Suspense boundary: `useSearchParams` in a statically rendered route
|
package/dist/next/index.mjs
CHANGED
|
@@ -4,10 +4,10 @@ import { useOutboundClickAnalytics } from "../hooks/use-outbound-click-analytics
|
|
|
4
4
|
import { useUETIdSync } from "../hooks/use-uet-id-sync.mjs";
|
|
5
5
|
import { useWebAnalytics } from "../hooks/use-web-analytics.mjs";
|
|
6
6
|
import { Suspense } from "react";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
8
|
import { usePathname, useSearchParams } from "next/navigation";
|
|
8
9
|
import Script from "next/script";
|
|
9
10
|
import { useReportWebVitals } from "next/web-vitals";
|
|
10
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
11
|
//#region src/next/index.tsx
|
|
12
12
|
/**
|
|
13
13
|
* Its own component under a Suspense boundary: `useSearchParams` in a statically rendered route
|
|
@@ -4,8 +4,8 @@ const require_hooks_use_outbound_click_analytics = require("../hooks/use-outboun
|
|
|
4
4
|
const require_hooks_use_report_web_vitals = require("../hooks/use-report-web-vitals.cjs");
|
|
5
5
|
const require_hooks_use_uet_id_sync = require("../hooks/use-uet-id-sync.cjs");
|
|
6
6
|
const require_hooks_use_web_analytics = require("../hooks/use-web-analytics.cjs");
|
|
7
|
-
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
7
|
let react_router = require("react-router");
|
|
8
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
9
|
//#region src/react-router/index.tsx
|
|
10
10
|
function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, redditPixelId, linkedInPartnerId, uetTagId, uetCustomerId, hotjarId, facebookAppId, reportWebVitals = true }) {
|
|
11
11
|
const { pathname, search } = (0, react_router.useLocation)();
|
|
@@ -3,8 +3,8 @@ import { useOutboundClickAnalytics } from "../hooks/use-outbound-click-analytics
|
|
|
3
3
|
import { useReportWebVitals } from "../hooks/use-report-web-vitals.mjs";
|
|
4
4
|
import { useUETIdSync } from "../hooks/use-uet-id-sync.mjs";
|
|
5
5
|
import { useWebAnalytics } from "../hooks/use-web-analytics.mjs";
|
|
6
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
6
|
import { useLocation } from "react-router";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
//#region src/react-router/index.tsx
|
|
9
9
|
function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, redditPixelId, linkedInPartnerId, uetTagId, uetCustomerId, hotjarId, facebookAppId, reportWebVitals = true }) {
|
|
10
10
|
const { pathname, search } = useLocation();
|
package/dist/web/index.cjs
CHANGED
|
@@ -3,8 +3,8 @@ const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
|
|
|
3
3
|
const require_setup_index = require("../setup/index.cjs");
|
|
4
4
|
const require_constants_storage = require("../constants/storage.cjs");
|
|
5
5
|
const require_link_index = require("../link/index.cjs");
|
|
6
|
-
const require_web_page_key = require("./page-key.cjs");
|
|
7
6
|
const require_es5$1 = require("../node_modules/.pnpm/bowser@2.14.1/node_modules/bowser/es5.cjs");
|
|
7
|
+
const require_web_page_key = require("./page-key.cjs");
|
|
8
8
|
let uuid = require("uuid");
|
|
9
9
|
let cookie = require("cookie");
|
|
10
10
|
//#region src/web/index.ts
|
|
@@ -28,14 +28,35 @@ function getDeviceId() {
|
|
|
28
28
|
* `useWebAnalytics`): the id exists to link an event to the `page_view` of the page it happened
|
|
29
29
|
* on, so it must rotate exactly when a `page_view` is sent — path or query change, yes; hash
|
|
30
30
|
* change or tracking-parameter cleanup, no.
|
|
31
|
+
*
|
|
32
|
+
* `referrer` is the page's referrer as GA4 reports it in a single page app: `document.referrer`
|
|
33
|
+
* for the document's first page, and the previous page's URL after an in-app navigation. The
|
|
34
|
+
* browser never updates `document.referrer` on a `pushState`, so read alone it would name the
|
|
35
|
+
* site the visit came from on every later page — and a session opened there, after 30 minutes
|
|
36
|
+
* idle, would count that site as a second visit from it. With the previous page as its referrer,
|
|
37
|
+
* that session's referrer is the product's own host: no touch, a direct session, which the
|
|
38
|
+
* attribution view then credits to the touch before it.
|
|
31
39
|
*/
|
|
32
40
|
let pageLoad;
|
|
33
|
-
function
|
|
34
|
-
|
|
41
|
+
function currentPage() {
|
|
42
|
+
const href = window.location.href;
|
|
43
|
+
const page = require_web_page_key.getPageKey(window.location.pathname, window.location.search);
|
|
44
|
+
if (pageLoad?.page === page) {
|
|
45
|
+
pageLoad.href = href;
|
|
46
|
+
return pageLoad;
|
|
47
|
+
}
|
|
48
|
+
const referrer = pageLoad ? pageLoad.href : document.referrer || void 0;
|
|
49
|
+
pageLoad = {
|
|
35
50
|
page,
|
|
36
|
-
id: randomUUID()
|
|
51
|
+
id: randomUUID(),
|
|
52
|
+
href,
|
|
53
|
+
referrer
|
|
37
54
|
};
|
|
38
|
-
return pageLoad
|
|
55
|
+
return pageLoad;
|
|
56
|
+
}
|
|
57
|
+
/** The current page's referrer: see `pageLoad`. */
|
|
58
|
+
function getPageReferrer() {
|
|
59
|
+
return currentPage().referrer;
|
|
39
60
|
}
|
|
40
61
|
const links = /* @__PURE__ */ new Map();
|
|
41
62
|
/**
|
|
@@ -57,8 +78,7 @@ function getCachedLink(id) {
|
|
|
57
78
|
let parser;
|
|
58
79
|
async function getTags() {
|
|
59
80
|
const page_location = window.location.href;
|
|
60
|
-
const page_load_id =
|
|
61
|
-
const page_referrer = document.referrer || void 0;
|
|
81
|
+
const { id: page_load_id, referrer: page_referrer } = currentPage();
|
|
62
82
|
const page_title = document.title;
|
|
63
83
|
parser ??= import_es5.default.getParser(window.navigator.userAgent);
|
|
64
84
|
const params = new URLSearchParams(window.location.search);
|
|
@@ -160,6 +180,7 @@ const storage = {
|
|
|
160
180
|
};
|
|
161
181
|
//#endregion
|
|
162
182
|
exports.getDeviceId = getDeviceId;
|
|
183
|
+
exports.getPageReferrer = getPageReferrer;
|
|
163
184
|
exports.getTags = getTags;
|
|
164
185
|
exports.storage = storage;
|
|
165
186
|
|
package/dist/web/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["uuidv4","config","keys","getLink","getPageKey","Bowser","parseCookie"],"sources":["../../src/web/index.ts"],"sourcesContent":["import Bowser from 'bowser';\nimport { parseCookie } from 'cookie';\nimport { v4 as uuidv4 } from 'uuid';\nimport { keys } from '../constants/storage';\nimport { type Link, getLink } from '../link/index';\nimport { type Storage, cache, config } from '../setup/index';\nimport type { TrackTags } from '../track/types';\nimport { getPageKey } from './page-key';\n\n// lib.dom types `crypto.randomUUID` as always present, but it is missing in insecure contexts\n// and older browsers, so probe it before use.\nfunction randomUUID(): string {\n return (crypto as Partial<Crypto>).randomUUID ? crypto.randomUUID() : uuidv4();\n}\n\nexport function getDeviceId() {\n const cached = config.storage.getItem(keys.device_id);\n if (cached) return cached;\n const id = randomUUID();\n config.storage.setItem(keys.device_id, id);\n return id;\n}\n\n/**\n * The current page load, keyed by the page it was loaded at. A module variable and nothing more\n * persistent: the id must die with the page (a reload is a new page load, and storage would\n * carry it over — or, shared across tabs, let two pages overwrite each other's).\n *\n * Keyed by `getPageKey` because that is what the SDK's own `page_view` fires on (see\n * `useWebAnalytics`): the id exists to link an event to the `page_view` of the page it happened\n * on, so it must rotate exactly when a `page_view` is sent — path or query change, yes; hash\n * change or tracking-parameter cleanup, no.\n */\nlet pageLoad: { page: string; id: string } | undefined;\n\nfunction getPageLoadId(page: string): string {\n if (pageLoad?.page !== page) pageLoad = { page, id: randomUUID() };\n return pageLoad.id;\n}\n\nconst links = new Map<string, Promise<Link | null>>();\n\n/**\n * `getTags` runs once per event now, and the link a `?s=` id points at cannot change while the\n * page is open, so an uncached lookup would put an identical request on the wire for every\n * event the page sends.\n */\nfunction getCachedLink(id: string) {\n const cached = links.get(id);\n if (cached) return cached;\n const link = getLink(id).then((result) => {\n // A lookup that came back empty must not stick for the lifetime of the page. `getLink`\n // answers null for a network failure as well as for a link that does not exist, and losing\n // the link's utm params for every later event costs more than repeating a rare request.\n if (!result) links.delete(id);\n return result;\n });\n links.set(id, link);\n return link;\n}\n\n/** The user agent cannot change while the page is open, so it is parsed once. */\nlet parser: ReturnType<typeof Bowser.getParser> | undefined;\n\nexport async function getTags() {\n // Read the page before the first await: `getTags` runs when the event happens, and a single\n // page app can navigate while the link lookup below is still in flight.\n const page_location = window.location.href;\n const page_load_id = getPageLoadId(getPageKey(window.location.pathname, window.location.search));\n const page_referrer = document.referrer || undefined;\n const page_title = document.title;\n\n parser ??= Bowser.getParser(window.navigator.userAgent);\n const params = new URLSearchParams(window.location.search);\n const os = parser.getOS();\n const browser = parser.getBrowser();\n const platform = parser.getPlatform();\n const parsed = parseCookie(document.cookie);\n\n const linkId = params.get('s');\n const link = linkId ? await getCachedLink(linkId) : null;\n\n const tags: TrackTags = {\n os: `${os.name} ${os.version}`,\n os_name: os.name,\n os_version: os.version,\n browser: `${browser.name} ${browser.version}`,\n browser_name: browser.name,\n browser_version: browser.version,\n device: platform.model,\n device_id: getDeviceId(),\n device_type: platform.type,\n device_vendor: platform.vendor,\n device_pixel_ratio: window.devicePixelRatio,\n screen_width: window.screen.width,\n screen_height: window.screen.height,\n screen_resolution: `${window.screen.width}x${window.screen.height}`,\n webdriver: navigator.webdriver || undefined,\n release: config.release,\n language: navigator.language,\n time_zone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n page_location,\n page_referrer,\n page_title,\n page_load_id,\n // Ad click ids from the URL, each under its parameter's name: this page's click, which\n // names the visit's channel (`CLICK_ID_CHANNELS`). Never filled from a cookie — a cookie\n // outlives the click; the cookies go below under their own names.\n fbclid: params.get('fbclid') ?? undefined,\n ad_id: params.get('ad_id') ?? undefined,\n ad_name: params.get('ad_name') ?? undefined,\n adset_id: params.get('adset_id') ?? undefined,\n adset_name: params.get('adset_name') ?? undefined,\n campaign_id: params.get('campaign_id') ?? undefined,\n campaign_name: params.get('campaign_name') ?? undefined,\n placement: params.get('placement') ?? undefined,\n gclid: params.get('gclid') ?? undefined,\n gclsrc: params.get('gclsrc') ?? undefined,\n gad_source: params.get('gad_source') ?? undefined,\n gad_campaignid: params.get('gad_campaignid') ?? undefined,\n wbraid: params.get('wbraid') ?? undefined,\n gbraid: params.get('gbraid') ?? undefined,\n dclid: params.get('dclid') ?? undefined,\n msclkid: params.get('msclkid') ?? undefined,\n rdt_cid: params.get('rdt_cid') ?? undefined,\n li_fat_id: params.get('li_fat_id') ?? undefined,\n oppref: params.get('oppref') ?? undefined,\n ko_click_id: params.get('ko_click_id') ?? undefined,\n // Snapchat appends `ScCid`, and URL parameters are case-sensitive.\n ScCid: params.get('ScCid') ?? undefined,\n ttclid: params.get('ttclid') ?? undefined,\n twclid: params.get('twclid') ?? undefined,\n yclid: params.get('yclid') ?? undefined,\n epik: params.get('epik') ?? undefined,\n // The ad platforms' first-party cookies, raw and under the cookie's own name, for the\n // conversion senders only: they carry the click to the later pages and visits where the\n // URL no longer does. `_fbc`, `_rdt_cid`, `_gcl_*` and `_uetmsclkid` are kept alive\n // server-side (see @shware/analytics/server resolveClickIdCookies); the rest are the pixels'.\n _fbc: parsed._fbc || undefined,\n _fbp: parsed._fbp || undefined,\n _gcl_aw: parsed._gcl_aw || undefined,\n _gcl_gb: parsed._gcl_gb || undefined,\n _uetmsclkid: parsed._uetmsclkid || undefined,\n _rdt_cid: parsed._rdt_cid || undefined,\n _rdt_uuid: parsed._rdt_uuid || undefined,\n // The Insight Tag's cookie is itself named `li_fat_id`; renamed so it cannot pass for the\n // URL parameter.\n _li_fat_id: parsed.li_fat_id || undefined,\n __oppref: parsed.__oppref || undefined,\n __obref: parsed.__obref || undefined,\n // utm params\n utm_source: link?.utm_source ?? params.get('utm_source') ?? undefined,\n utm_medium: link?.utm_medium ?? params.get('utm_medium') ?? undefined,\n utm_campaign: link?.utm_campaign ?? params.get('utm_campaign') ?? undefined,\n utm_term: link?.utm_term ?? params.get('utm_term') ?? undefined,\n utm_content: link?.utm_content ?? params.get('utm_content') ?? undefined,\n utm_id: link?.utm_id ?? params.get('utm_id') ?? undefined,\n utm_source_platform:\n link?.utm_source_platform ?? params.get('utm_source_platform') ?? undefined,\n utm_creative_format:\n link?.utm_creative_format ?? params.get('utm_creative_format') ?? undefined,\n utm_marketing_tactic:\n link?.utm_marketing_tactic ?? params.get('utm_marketing_tactic') ?? undefined,\n };\n\n cache.tags = tags;\n return tags;\n}\n\nconst map = new Map<string, string>();\n\nexport const storage: Storage = {\n getItem: (key) => {\n try {\n return localStorage.getItem(key);\n } catch {\n console.error('localStorage is not available');\n return map.get(key) ?? null;\n }\n },\n setItem: (key, value) => {\n try {\n localStorage.setItem(key, value);\n } catch {\n console.error('localStorage is not available');\n map.set(key, value);\n }\n },\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["uuidv4","config","keys","getPageKey","getLink","Bowser","parseCookie"],"sources":["../../src/web/index.ts"],"sourcesContent":["import Bowser from 'bowser';\nimport { parseCookie } from 'cookie';\nimport { v4 as uuidv4 } from 'uuid';\nimport { keys } from '../constants/storage';\nimport { type Link, getLink } from '../link/index';\nimport { type Storage, cache, config } from '../setup/index';\nimport type { TrackTags } from '../track/types';\nimport { getPageKey } from './page-key';\n\n// lib.dom types `crypto.randomUUID` as always present, but it is missing in insecure contexts\n// and older browsers, so probe it before use.\nfunction randomUUID(): string {\n return (crypto as Partial<Crypto>).randomUUID ? crypto.randomUUID() : uuidv4();\n}\n\nexport function getDeviceId() {\n const cached = config.storage.getItem(keys.device_id);\n if (cached) return cached;\n const id = randomUUID();\n config.storage.setItem(keys.device_id, id);\n return id;\n}\n\n/**\n * The current page load, keyed by the page it was loaded at. A module variable and nothing more\n * persistent: the id must die with the page (a reload is a new page load, and storage would\n * carry it over — or, shared across tabs, let two pages overwrite each other's).\n *\n * Keyed by `getPageKey` because that is what the SDK's own `page_view` fires on (see\n * `useWebAnalytics`): the id exists to link an event to the `page_view` of the page it happened\n * on, so it must rotate exactly when a `page_view` is sent — path or query change, yes; hash\n * change or tracking-parameter cleanup, no.\n *\n * `referrer` is the page's referrer as GA4 reports it in a single page app: `document.referrer`\n * for the document's first page, and the previous page's URL after an in-app navigation. The\n * browser never updates `document.referrer` on a `pushState`, so read alone it would name the\n * site the visit came from on every later page — and a session opened there, after 30 minutes\n * idle, would count that site as a second visit from it. With the previous page as its referrer,\n * that session's referrer is the product's own host: no touch, a direct session, which the\n * attribution view then credits to the touch before it.\n */\nlet pageLoad: { page: string; id: string; href: string; referrer: string | undefined } | undefined;\n\nfunction currentPage() {\n const href = window.location.href;\n const page = getPageKey(window.location.pathname, window.location.search);\n if (pageLoad?.page === page) {\n // The same page, its URL maybe cleaned of tracking parameters since: the next page's\n // referrer is the URL as it last was, as the browser's own would be.\n pageLoad.href = href;\n return pageLoad;\n }\n const referrer = pageLoad ? pageLoad.href : document.referrer || undefined;\n pageLoad = { page, id: randomUUID(), href, referrer };\n return pageLoad;\n}\n\n/** The current page's referrer: see `pageLoad`. */\nexport function getPageReferrer(): string | undefined {\n return currentPage().referrer;\n}\n\nconst links = new Map<string, Promise<Link | null>>();\n\n/**\n * `getTags` runs once per event now, and the link a `?s=` id points at cannot change while the\n * page is open, so an uncached lookup would put an identical request on the wire for every\n * event the page sends.\n */\nfunction getCachedLink(id: string) {\n const cached = links.get(id);\n if (cached) return cached;\n const link = getLink(id).then((result) => {\n // A lookup that came back empty must not stick for the lifetime of the page. `getLink`\n // answers null for a network failure as well as for a link that does not exist, and losing\n // the link's utm params for every later event costs more than repeating a rare request.\n if (!result) links.delete(id);\n return result;\n });\n links.set(id, link);\n return link;\n}\n\n/** The user agent cannot change while the page is open, so it is parsed once. */\nlet parser: ReturnType<typeof Bowser.getParser> | undefined;\n\nexport async function getTags() {\n // Read the page before the first await: `getTags` runs when the event happens, and a single\n // page app can navigate while the link lookup below is still in flight.\n const page_location = window.location.href;\n const { id: page_load_id, referrer: page_referrer } = currentPage();\n const page_title = document.title;\n\n parser ??= Bowser.getParser(window.navigator.userAgent);\n const params = new URLSearchParams(window.location.search);\n const os = parser.getOS();\n const browser = parser.getBrowser();\n const platform = parser.getPlatform();\n const parsed = parseCookie(document.cookie);\n\n const linkId = params.get('s');\n const link = linkId ? await getCachedLink(linkId) : null;\n\n const tags: TrackTags = {\n os: `${os.name} ${os.version}`,\n os_name: os.name,\n os_version: os.version,\n browser: `${browser.name} ${browser.version}`,\n browser_name: browser.name,\n browser_version: browser.version,\n device: platform.model,\n device_id: getDeviceId(),\n device_type: platform.type,\n device_vendor: platform.vendor,\n device_pixel_ratio: window.devicePixelRatio,\n screen_width: window.screen.width,\n screen_height: window.screen.height,\n screen_resolution: `${window.screen.width}x${window.screen.height}`,\n webdriver: navigator.webdriver || undefined,\n release: config.release,\n language: navigator.language,\n time_zone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n page_location,\n page_referrer,\n page_title,\n page_load_id,\n // Ad click ids from the URL, each under its parameter's name: this page's click, which\n // names the visit's channel (`CLICK_ID_CHANNELS`). Never filled from a cookie — a cookie\n // outlives the click; the cookies go below under their own names.\n fbclid: params.get('fbclid') ?? undefined,\n ad_id: params.get('ad_id') ?? undefined,\n ad_name: params.get('ad_name') ?? undefined,\n adset_id: params.get('adset_id') ?? undefined,\n adset_name: params.get('adset_name') ?? undefined,\n campaign_id: params.get('campaign_id') ?? undefined,\n campaign_name: params.get('campaign_name') ?? undefined,\n placement: params.get('placement') ?? undefined,\n gclid: params.get('gclid') ?? undefined,\n gclsrc: params.get('gclsrc') ?? undefined,\n gad_source: params.get('gad_source') ?? undefined,\n gad_campaignid: params.get('gad_campaignid') ?? undefined,\n wbraid: params.get('wbraid') ?? undefined,\n gbraid: params.get('gbraid') ?? undefined,\n dclid: params.get('dclid') ?? undefined,\n msclkid: params.get('msclkid') ?? undefined,\n rdt_cid: params.get('rdt_cid') ?? undefined,\n li_fat_id: params.get('li_fat_id') ?? undefined,\n oppref: params.get('oppref') ?? undefined,\n ko_click_id: params.get('ko_click_id') ?? undefined,\n // Snapchat appends `ScCid`, and URL parameters are case-sensitive.\n ScCid: params.get('ScCid') ?? undefined,\n ttclid: params.get('ttclid') ?? undefined,\n twclid: params.get('twclid') ?? undefined,\n yclid: params.get('yclid') ?? undefined,\n epik: params.get('epik') ?? undefined,\n // The ad platforms' first-party cookies, raw and under the cookie's own name, for the\n // conversion senders only: they carry the click to the later pages and visits where the\n // URL no longer does. `_fbc`, `_rdt_cid`, `_gcl_*` and `_uetmsclkid` are kept alive\n // server-side (see @shware/analytics/server resolveClickIdCookies); the rest are the pixels'.\n _fbc: parsed._fbc || undefined,\n _fbp: parsed._fbp || undefined,\n _gcl_aw: parsed._gcl_aw || undefined,\n _gcl_gb: parsed._gcl_gb || undefined,\n _uetmsclkid: parsed._uetmsclkid || undefined,\n _rdt_cid: parsed._rdt_cid || undefined,\n _rdt_uuid: parsed._rdt_uuid || undefined,\n // The Insight Tag's cookie is itself named `li_fat_id`; renamed so it cannot pass for the\n // URL parameter.\n _li_fat_id: parsed.li_fat_id || undefined,\n __oppref: parsed.__oppref || undefined,\n __obref: parsed.__obref || undefined,\n // utm params\n utm_source: link?.utm_source ?? params.get('utm_source') ?? undefined,\n utm_medium: link?.utm_medium ?? params.get('utm_medium') ?? undefined,\n utm_campaign: link?.utm_campaign ?? params.get('utm_campaign') ?? undefined,\n utm_term: link?.utm_term ?? params.get('utm_term') ?? undefined,\n utm_content: link?.utm_content ?? params.get('utm_content') ?? undefined,\n utm_id: link?.utm_id ?? params.get('utm_id') ?? undefined,\n utm_source_platform:\n link?.utm_source_platform ?? params.get('utm_source_platform') ?? undefined,\n utm_creative_format:\n link?.utm_creative_format ?? params.get('utm_creative_format') ?? undefined,\n utm_marketing_tactic:\n link?.utm_marketing_tactic ?? params.get('utm_marketing_tactic') ?? undefined,\n };\n\n cache.tags = tags;\n return tags;\n}\n\nconst map = new Map<string, string>();\n\nexport const storage: Storage = {\n getItem: (key) => {\n try {\n return localStorage.getItem(key);\n } catch {\n console.error('localStorage is not available');\n return map.get(key) ?? null;\n }\n },\n setItem: (key, value) => {\n try {\n localStorage.setItem(key, value);\n } catch {\n console.error('localStorage is not available');\n map.set(key, value);\n }\n },\n};\n"],"mappings":"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"}
|
package/dist/web/index.d.cts
CHANGED
|
@@ -2,6 +2,8 @@ import { TrackTags } from "../track/types.cjs";
|
|
|
2
2
|
import { Storage } from "../setup/index.cjs";
|
|
3
3
|
//#region src/web/index.d.ts
|
|
4
4
|
export declare function getDeviceId(): string;
|
|
5
|
+
/** The current page's referrer: see `pageLoad`. */
|
|
6
|
+
export declare function getPageReferrer(): string | undefined;
|
|
5
7
|
export declare function getTags(): Promise<TrackTags>;
|
|
6
8
|
export declare const storage: Storage;
|
|
7
9
|
//#endregion
|
package/dist/web/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/web/index.ts"],"mappings":";;;wBAegB;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/web/index.ts"],"mappings":";;;wBAegB;;wBA2CA;wBA4BM,WAAO,QAAA;qBA0GhB,SAAS"}
|
package/dist/web/index.d.mts
CHANGED
|
@@ -2,6 +2,8 @@ import { TrackTags } from "../track/types.mjs";
|
|
|
2
2
|
import { Storage } from "../setup/index.mjs";
|
|
3
3
|
//#region src/web/index.d.ts
|
|
4
4
|
export declare function getDeviceId(): string;
|
|
5
|
+
/** The current page's referrer: see `pageLoad`. */
|
|
6
|
+
export declare function getPageReferrer(): string | undefined;
|
|
5
7
|
export declare function getTags(): Promise<TrackTags>;
|
|
6
8
|
export declare const storage: Storage;
|
|
7
9
|
//#endregion
|
package/dist/web/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/web/index.ts"],"mappings":";;;wBAegB;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/web/index.ts"],"mappings":";;;wBAegB;;wBA2CA;wBA4BM,WAAO,QAAA;qBA0GhB,SAAS"}
|
package/dist/web/index.mjs
CHANGED
|
@@ -2,8 +2,8 @@ import { __toESM } from "../_virtual/_rolldown/runtime.mjs";
|
|
|
2
2
|
import { cache, config } from "../setup/index.mjs";
|
|
3
3
|
import { keys } from "../constants/storage.mjs";
|
|
4
4
|
import { getLink } from "../link/index.mjs";
|
|
5
|
-
import { getPageKey } from "./page-key.mjs";
|
|
6
5
|
import { require_es5 } from "../node_modules/.pnpm/bowser@2.14.1/node_modules/bowser/es5.mjs";
|
|
6
|
+
import { getPageKey } from "./page-key.mjs";
|
|
7
7
|
import { v4 } from "uuid";
|
|
8
8
|
import { parseCookie } from "cookie";
|
|
9
9
|
//#region src/web/index.ts
|
|
@@ -27,14 +27,35 @@ function getDeviceId() {
|
|
|
27
27
|
* `useWebAnalytics`): the id exists to link an event to the `page_view` of the page it happened
|
|
28
28
|
* on, so it must rotate exactly when a `page_view` is sent — path or query change, yes; hash
|
|
29
29
|
* change or tracking-parameter cleanup, no.
|
|
30
|
+
*
|
|
31
|
+
* `referrer` is the page's referrer as GA4 reports it in a single page app: `document.referrer`
|
|
32
|
+
* for the document's first page, and the previous page's URL after an in-app navigation. The
|
|
33
|
+
* browser never updates `document.referrer` on a `pushState`, so read alone it would name the
|
|
34
|
+
* site the visit came from on every later page — and a session opened there, after 30 minutes
|
|
35
|
+
* idle, would count that site as a second visit from it. With the previous page as its referrer,
|
|
36
|
+
* that session's referrer is the product's own host: no touch, a direct session, which the
|
|
37
|
+
* attribution view then credits to the touch before it.
|
|
30
38
|
*/
|
|
31
39
|
let pageLoad;
|
|
32
|
-
function
|
|
33
|
-
|
|
40
|
+
function currentPage() {
|
|
41
|
+
const href = window.location.href;
|
|
42
|
+
const page = getPageKey(window.location.pathname, window.location.search);
|
|
43
|
+
if (pageLoad?.page === page) {
|
|
44
|
+
pageLoad.href = href;
|
|
45
|
+
return pageLoad;
|
|
46
|
+
}
|
|
47
|
+
const referrer = pageLoad ? pageLoad.href : document.referrer || void 0;
|
|
48
|
+
pageLoad = {
|
|
34
49
|
page,
|
|
35
|
-
id: randomUUID()
|
|
50
|
+
id: randomUUID(),
|
|
51
|
+
href,
|
|
52
|
+
referrer
|
|
36
53
|
};
|
|
37
|
-
return pageLoad
|
|
54
|
+
return pageLoad;
|
|
55
|
+
}
|
|
56
|
+
/** The current page's referrer: see `pageLoad`. */
|
|
57
|
+
function getPageReferrer() {
|
|
58
|
+
return currentPage().referrer;
|
|
38
59
|
}
|
|
39
60
|
const links = /* @__PURE__ */ new Map();
|
|
40
61
|
/**
|
|
@@ -56,8 +77,7 @@ function getCachedLink(id) {
|
|
|
56
77
|
let parser;
|
|
57
78
|
async function getTags() {
|
|
58
79
|
const page_location = window.location.href;
|
|
59
|
-
const page_load_id =
|
|
60
|
-
const page_referrer = document.referrer || void 0;
|
|
80
|
+
const { id: page_load_id, referrer: page_referrer } = currentPage();
|
|
61
81
|
const page_title = document.title;
|
|
62
82
|
parser ??= import_es5.default.getParser(window.navigator.userAgent);
|
|
63
83
|
const params = new URLSearchParams(window.location.search);
|
|
@@ -158,6 +178,6 @@ const storage = {
|
|
|
158
178
|
}
|
|
159
179
|
};
|
|
160
180
|
//#endregion
|
|
161
|
-
export { getDeviceId, getTags, storage };
|
|
181
|
+
export { getDeviceId, getPageReferrer, getTags, storage };
|
|
162
182
|
|
|
163
183
|
//# sourceMappingURL=index.mjs.map
|
package/dist/web/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["uuidv4","Bowser"],"sources":["../../src/web/index.ts"],"sourcesContent":["import Bowser from 'bowser';\nimport { parseCookie } from 'cookie';\nimport { v4 as uuidv4 } from 'uuid';\nimport { keys } from '../constants/storage';\nimport { type Link, getLink } from '../link/index';\nimport { type Storage, cache, config } from '../setup/index';\nimport type { TrackTags } from '../track/types';\nimport { getPageKey } from './page-key';\n\n// lib.dom types `crypto.randomUUID` as always present, but it is missing in insecure contexts\n// and older browsers, so probe it before use.\nfunction randomUUID(): string {\n return (crypto as Partial<Crypto>).randomUUID ? crypto.randomUUID() : uuidv4();\n}\n\nexport function getDeviceId() {\n const cached = config.storage.getItem(keys.device_id);\n if (cached) return cached;\n const id = randomUUID();\n config.storage.setItem(keys.device_id, id);\n return id;\n}\n\n/**\n * The current page load, keyed by the page it was loaded at. A module variable and nothing more\n * persistent: the id must die with the page (a reload is a new page load, and storage would\n * carry it over — or, shared across tabs, let two pages overwrite each other's).\n *\n * Keyed by `getPageKey` because that is what the SDK's own `page_view` fires on (see\n * `useWebAnalytics`): the id exists to link an event to the `page_view` of the page it happened\n * on, so it must rotate exactly when a `page_view` is sent — path or query change, yes; hash\n * change or tracking-parameter cleanup, no.\n */\nlet pageLoad: { page: string; id: string } | undefined;\n\nfunction getPageLoadId(page: string): string {\n if (pageLoad?.page !== page) pageLoad = { page, id: randomUUID() };\n return pageLoad.id;\n}\n\nconst links = new Map<string, Promise<Link | null>>();\n\n/**\n * `getTags` runs once per event now, and the link a `?s=` id points at cannot change while the\n * page is open, so an uncached lookup would put an identical request on the wire for every\n * event the page sends.\n */\nfunction getCachedLink(id: string) {\n const cached = links.get(id);\n if (cached) return cached;\n const link = getLink(id).then((result) => {\n // A lookup that came back empty must not stick for the lifetime of the page. `getLink`\n // answers null for a network failure as well as for a link that does not exist, and losing\n // the link's utm params for every later event costs more than repeating a rare request.\n if (!result) links.delete(id);\n return result;\n });\n links.set(id, link);\n return link;\n}\n\n/** The user agent cannot change while the page is open, so it is parsed once. */\nlet parser: ReturnType<typeof Bowser.getParser> | undefined;\n\nexport async function getTags() {\n // Read the page before the first await: `getTags` runs when the event happens, and a single\n // page app can navigate while the link lookup below is still in flight.\n const page_location = window.location.href;\n const page_load_id = getPageLoadId(getPageKey(window.location.pathname, window.location.search));\n const page_referrer = document.referrer || undefined;\n const page_title = document.title;\n\n parser ??= Bowser.getParser(window.navigator.userAgent);\n const params = new URLSearchParams(window.location.search);\n const os = parser.getOS();\n const browser = parser.getBrowser();\n const platform = parser.getPlatform();\n const parsed = parseCookie(document.cookie);\n\n const linkId = params.get('s');\n const link = linkId ? await getCachedLink(linkId) : null;\n\n const tags: TrackTags = {\n os: `${os.name} ${os.version}`,\n os_name: os.name,\n os_version: os.version,\n browser: `${browser.name} ${browser.version}`,\n browser_name: browser.name,\n browser_version: browser.version,\n device: platform.model,\n device_id: getDeviceId(),\n device_type: platform.type,\n device_vendor: platform.vendor,\n device_pixel_ratio: window.devicePixelRatio,\n screen_width: window.screen.width,\n screen_height: window.screen.height,\n screen_resolution: `${window.screen.width}x${window.screen.height}`,\n webdriver: navigator.webdriver || undefined,\n release: config.release,\n language: navigator.language,\n time_zone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n page_location,\n page_referrer,\n page_title,\n page_load_id,\n // Ad click ids from the URL, each under its parameter's name: this page's click, which\n // names the visit's channel (`CLICK_ID_CHANNELS`). Never filled from a cookie — a cookie\n // outlives the click; the cookies go below under their own names.\n fbclid: params.get('fbclid') ?? undefined,\n ad_id: params.get('ad_id') ?? undefined,\n ad_name: params.get('ad_name') ?? undefined,\n adset_id: params.get('adset_id') ?? undefined,\n adset_name: params.get('adset_name') ?? undefined,\n campaign_id: params.get('campaign_id') ?? undefined,\n campaign_name: params.get('campaign_name') ?? undefined,\n placement: params.get('placement') ?? undefined,\n gclid: params.get('gclid') ?? undefined,\n gclsrc: params.get('gclsrc') ?? undefined,\n gad_source: params.get('gad_source') ?? undefined,\n gad_campaignid: params.get('gad_campaignid') ?? undefined,\n wbraid: params.get('wbraid') ?? undefined,\n gbraid: params.get('gbraid') ?? undefined,\n dclid: params.get('dclid') ?? undefined,\n msclkid: params.get('msclkid') ?? undefined,\n rdt_cid: params.get('rdt_cid') ?? undefined,\n li_fat_id: params.get('li_fat_id') ?? undefined,\n oppref: params.get('oppref') ?? undefined,\n ko_click_id: params.get('ko_click_id') ?? undefined,\n // Snapchat appends `ScCid`, and URL parameters are case-sensitive.\n ScCid: params.get('ScCid') ?? undefined,\n ttclid: params.get('ttclid') ?? undefined,\n twclid: params.get('twclid') ?? undefined,\n yclid: params.get('yclid') ?? undefined,\n epik: params.get('epik') ?? undefined,\n // The ad platforms' first-party cookies, raw and under the cookie's own name, for the\n // conversion senders only: they carry the click to the later pages and visits where the\n // URL no longer does. `_fbc`, `_rdt_cid`, `_gcl_*` and `_uetmsclkid` are kept alive\n // server-side (see @shware/analytics/server resolveClickIdCookies); the rest are the pixels'.\n _fbc: parsed._fbc || undefined,\n _fbp: parsed._fbp || undefined,\n _gcl_aw: parsed._gcl_aw || undefined,\n _gcl_gb: parsed._gcl_gb || undefined,\n _uetmsclkid: parsed._uetmsclkid || undefined,\n _rdt_cid: parsed._rdt_cid || undefined,\n _rdt_uuid: parsed._rdt_uuid || undefined,\n // The Insight Tag's cookie is itself named `li_fat_id`; renamed so it cannot pass for the\n // URL parameter.\n _li_fat_id: parsed.li_fat_id || undefined,\n __oppref: parsed.__oppref || undefined,\n __obref: parsed.__obref || undefined,\n // utm params\n utm_source: link?.utm_source ?? params.get('utm_source') ?? undefined,\n utm_medium: link?.utm_medium ?? params.get('utm_medium') ?? undefined,\n utm_campaign: link?.utm_campaign ?? params.get('utm_campaign') ?? undefined,\n utm_term: link?.utm_term ?? params.get('utm_term') ?? undefined,\n utm_content: link?.utm_content ?? params.get('utm_content') ?? undefined,\n utm_id: link?.utm_id ?? params.get('utm_id') ?? undefined,\n utm_source_platform:\n link?.utm_source_platform ?? params.get('utm_source_platform') ?? undefined,\n utm_creative_format:\n link?.utm_creative_format ?? params.get('utm_creative_format') ?? undefined,\n utm_marketing_tactic:\n link?.utm_marketing_tactic ?? params.get('utm_marketing_tactic') ?? undefined,\n };\n\n cache.tags = tags;\n return tags;\n}\n\nconst map = new Map<string, string>();\n\nexport const storage: Storage = {\n getItem: (key) => {\n try {\n return localStorage.getItem(key);\n } catch {\n console.error('localStorage is not available');\n return map.get(key) ?? null;\n }\n },\n setItem: (key, value) => {\n try {\n localStorage.setItem(key, value);\n } catch {\n console.error('localStorage is not available');\n map.set(key, value);\n }\n },\n};\n"],"mappings":";;;;;;;;;;AAWA,SAAS,aAAqB;CAC5B,OAAQ,OAA2B,aAAa,OAAO,WAAW,IAAIA,GAAO;AAC/E;AAEA,SAAgB,cAAc;CAC5B,MAAM,SAAS,OAAO,QAAQ,QAAQ,KAAK,SAAS;CACpD,IAAI,QAAQ,OAAO;CACnB,MAAM,KAAK,WAAW;CACtB,OAAO,QAAQ,QAAQ,KAAK,WAAW,EAAE;CACzC,OAAO;AACT;;;;;;;;;;;AAYA,IAAI;AAEJ,SAAS,cAAc,MAAsB;CAC3C,IAAI,UAAU,SAAS,MAAM,WAAW;EAAE;EAAM,IAAI,WAAW;CAAE;CACjE,OAAO,SAAS;AAClB;AAEA,MAAM,wBAAQ,IAAI,IAAkC;;;;;;AAOpD,SAAS,cAAc,IAAY;CACjC,MAAM,SAAS,MAAM,IAAI,EAAE;CAC3B,IAAI,QAAQ,OAAO;CACnB,MAAM,OAAO,QAAQ,EAAE,CAAC,CAAC,MAAM,WAAW;EAIxC,IAAI,CAAC,QAAQ,MAAM,OAAO,EAAE;EAC5B,OAAO;CACT,CAAC;CACD,MAAM,IAAI,IAAI,IAAI;CAClB,OAAO;AACT;;AAGA,IAAI;AAEJ,eAAsB,UAAU;CAG9B,MAAM,gBAAgB,OAAO,SAAS;CACtC,MAAM,eAAe,cAAc,WAAW,OAAO,SAAS,UAAU,OAAO,SAAS,MAAM,CAAC;CAC/F,MAAM,gBAAgB,SAAS,YAAY,KAAA;CAC3C,MAAM,aAAa,SAAS;CAE5B,WAAWC,WAAAA,QAAO,UAAU,OAAO,UAAU,SAAS;CACtD,MAAM,SAAS,IAAI,gBAAgB,OAAO,SAAS,MAAM;CACzD,MAAM,KAAK,OAAO,MAAM;CACxB,MAAM,UAAU,OAAO,WAAW;CAClC,MAAM,WAAW,OAAO,YAAY;CACpC,MAAM,SAAS,YAAY,SAAS,MAAM;CAE1C,MAAM,SAAS,OAAO,IAAI,GAAG;CAC7B,MAAM,OAAO,SAAS,MAAM,cAAc,MAAM,IAAI;CAEpD,MAAM,OAAkB;EACtB,IAAI,GAAG,GAAG,KAAK,GAAG,GAAG;EACrB,SAAS,GAAG;EACZ,YAAY,GAAG;EACf,SAAS,GAAG,QAAQ,KAAK,GAAG,QAAQ;EACpC,cAAc,QAAQ;EACtB,iBAAiB,QAAQ;EACzB,QAAQ,SAAS;EACjB,WAAW,YAAY;EACvB,aAAa,SAAS;EACtB,eAAe,SAAS;EACxB,oBAAoB,OAAO;EAC3B,cAAc,OAAO,OAAO;EAC5B,eAAe,OAAO,OAAO;EAC7B,mBAAmB,GAAG,OAAO,OAAO,MAAM,GAAG,OAAO,OAAO;EAC3D,WAAW,UAAU,aAAa,KAAA;EAClC,SAAS,OAAO;EAChB,UAAU,UAAU;EACpB,WAAW,KAAK,eAAe,CAAC,CAAC,gBAAgB,CAAC,CAAC;EACnD;EACA;EACA;EACA;EAIA,QAAQ,OAAO,IAAI,QAAQ,KAAK,KAAA;EAChC,OAAO,OAAO,IAAI,OAAO,KAAK,KAAA;EAC9B,SAAS,OAAO,IAAI,SAAS,KAAK,KAAA;EAClC,UAAU,OAAO,IAAI,UAAU,KAAK,KAAA;EACpC,YAAY,OAAO,IAAI,YAAY,KAAK,KAAA;EACxC,aAAa,OAAO,IAAI,aAAa,KAAK,KAAA;EAC1C,eAAe,OAAO,IAAI,eAAe,KAAK,KAAA;EAC9C,WAAW,OAAO,IAAI,WAAW,KAAK,KAAA;EACtC,OAAO,OAAO,IAAI,OAAO,KAAK,KAAA;EAC9B,QAAQ,OAAO,IAAI,QAAQ,KAAK,KAAA;EAChC,YAAY,OAAO,IAAI,YAAY,KAAK,KAAA;EACxC,gBAAgB,OAAO,IAAI,gBAAgB,KAAK,KAAA;EAChD,QAAQ,OAAO,IAAI,QAAQ,KAAK,KAAA;EAChC,QAAQ,OAAO,IAAI,QAAQ,KAAK,KAAA;EAChC,OAAO,OAAO,IAAI,OAAO,KAAK,KAAA;EAC9B,SAAS,OAAO,IAAI,SAAS,KAAK,KAAA;EAClC,SAAS,OAAO,IAAI,SAAS,KAAK,KAAA;EAClC,WAAW,OAAO,IAAI,WAAW,KAAK,KAAA;EACtC,QAAQ,OAAO,IAAI,QAAQ,KAAK,KAAA;EAChC,aAAa,OAAO,IAAI,aAAa,KAAK,KAAA;EAE1C,OAAO,OAAO,IAAI,OAAO,KAAK,KAAA;EAC9B,QAAQ,OAAO,IAAI,QAAQ,KAAK,KAAA;EAChC,QAAQ,OAAO,IAAI,QAAQ,KAAK,KAAA;EAChC,OAAO,OAAO,IAAI,OAAO,KAAK,KAAA;EAC9B,MAAM,OAAO,IAAI,MAAM,KAAK,KAAA;EAK5B,MAAM,OAAO,QAAQ,KAAA;EACrB,MAAM,OAAO,QAAQ,KAAA;EACrB,SAAS,OAAO,WAAW,KAAA;EAC3B,SAAS,OAAO,WAAW,KAAA;EAC3B,aAAa,OAAO,eAAe,KAAA;EACnC,UAAU,OAAO,YAAY,KAAA;EAC7B,WAAW,OAAO,aAAa,KAAA;EAG/B,YAAY,OAAO,aAAa,KAAA;EAChC,UAAU,OAAO,YAAY,KAAA;EAC7B,SAAS,OAAO,WAAW,KAAA;EAE3B,YAAY,MAAM,cAAc,OAAO,IAAI,YAAY,KAAK,KAAA;EAC5D,YAAY,MAAM,cAAc,OAAO,IAAI,YAAY,KAAK,KAAA;EAC5D,cAAc,MAAM,gBAAgB,OAAO,IAAI,cAAc,KAAK,KAAA;EAClE,UAAU,MAAM,YAAY,OAAO,IAAI,UAAU,KAAK,KAAA;EACtD,aAAa,MAAM,eAAe,OAAO,IAAI,aAAa,KAAK,KAAA;EAC/D,QAAQ,MAAM,UAAU,OAAO,IAAI,QAAQ,KAAK,KAAA;EAChD,qBACE,MAAM,uBAAuB,OAAO,IAAI,qBAAqB,KAAK,KAAA;EACpE,qBACE,MAAM,uBAAuB,OAAO,IAAI,qBAAqB,KAAK,KAAA;EACpE,sBACE,MAAM,wBAAwB,OAAO,IAAI,sBAAsB,KAAK,KAAA;CACxE;CAEA,MAAM,OAAO;CACb,OAAO;AACT;AAEA,MAAM,sBAAM,IAAI,IAAoB;AAEpC,MAAa,UAAmB;CAC9B,UAAU,QAAQ;EAChB,IAAI;GACF,OAAO,aAAa,QAAQ,GAAG;EACjC,QAAQ;GACN,QAAQ,MAAM,+BAA+B;GAC7C,OAAO,IAAI,IAAI,GAAG,KAAK;EACzB;CACF;CACA,UAAU,KAAK,UAAU;EACvB,IAAI;GACF,aAAa,QAAQ,KAAK,KAAK;EACjC,QAAQ;GACN,QAAQ,MAAM,+BAA+B;GAC7C,IAAI,IAAI,KAAK,KAAK;EACpB;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["uuidv4","Bowser"],"sources":["../../src/web/index.ts"],"sourcesContent":["import Bowser from 'bowser';\nimport { parseCookie } from 'cookie';\nimport { v4 as uuidv4 } from 'uuid';\nimport { keys } from '../constants/storage';\nimport { type Link, getLink } from '../link/index';\nimport { type Storage, cache, config } from '../setup/index';\nimport type { TrackTags } from '../track/types';\nimport { getPageKey } from './page-key';\n\n// lib.dom types `crypto.randomUUID` as always present, but it is missing in insecure contexts\n// and older browsers, so probe it before use.\nfunction randomUUID(): string {\n return (crypto as Partial<Crypto>).randomUUID ? crypto.randomUUID() : uuidv4();\n}\n\nexport function getDeviceId() {\n const cached = config.storage.getItem(keys.device_id);\n if (cached) return cached;\n const id = randomUUID();\n config.storage.setItem(keys.device_id, id);\n return id;\n}\n\n/**\n * The current page load, keyed by the page it was loaded at. A module variable and nothing more\n * persistent: the id must die with the page (a reload is a new page load, and storage would\n * carry it over — or, shared across tabs, let two pages overwrite each other's).\n *\n * Keyed by `getPageKey` because that is what the SDK's own `page_view` fires on (see\n * `useWebAnalytics`): the id exists to link an event to the `page_view` of the page it happened\n * on, so it must rotate exactly when a `page_view` is sent — path or query change, yes; hash\n * change or tracking-parameter cleanup, no.\n *\n * `referrer` is the page's referrer as GA4 reports it in a single page app: `document.referrer`\n * for the document's first page, and the previous page's URL after an in-app navigation. The\n * browser never updates `document.referrer` on a `pushState`, so read alone it would name the\n * site the visit came from on every later page — and a session opened there, after 30 minutes\n * idle, would count that site as a second visit from it. With the previous page as its referrer,\n * that session's referrer is the product's own host: no touch, a direct session, which the\n * attribution view then credits to the touch before it.\n */\nlet pageLoad: { page: string; id: string; href: string; referrer: string | undefined } | undefined;\n\nfunction currentPage() {\n const href = window.location.href;\n const page = getPageKey(window.location.pathname, window.location.search);\n if (pageLoad?.page === page) {\n // The same page, its URL maybe cleaned of tracking parameters since: the next page's\n // referrer is the URL as it last was, as the browser's own would be.\n pageLoad.href = href;\n return pageLoad;\n }\n const referrer = pageLoad ? pageLoad.href : document.referrer || undefined;\n pageLoad = { page, id: randomUUID(), href, referrer };\n return pageLoad;\n}\n\n/** The current page's referrer: see `pageLoad`. */\nexport function getPageReferrer(): string | undefined {\n return currentPage().referrer;\n}\n\nconst links = new Map<string, Promise<Link | null>>();\n\n/**\n * `getTags` runs once per event now, and the link a `?s=` id points at cannot change while the\n * page is open, so an uncached lookup would put an identical request on the wire for every\n * event the page sends.\n */\nfunction getCachedLink(id: string) {\n const cached = links.get(id);\n if (cached) return cached;\n const link = getLink(id).then((result) => {\n // A lookup that came back empty must not stick for the lifetime of the page. `getLink`\n // answers null for a network failure as well as for a link that does not exist, and losing\n // the link's utm params for every later event costs more than repeating a rare request.\n if (!result) links.delete(id);\n return result;\n });\n links.set(id, link);\n return link;\n}\n\n/** The user agent cannot change while the page is open, so it is parsed once. */\nlet parser: ReturnType<typeof Bowser.getParser> | undefined;\n\nexport async function getTags() {\n // Read the page before the first await: `getTags` runs when the event happens, and a single\n // page app can navigate while the link lookup below is still in flight.\n const page_location = window.location.href;\n const { id: page_load_id, referrer: page_referrer } = currentPage();\n const page_title = document.title;\n\n parser ??= Bowser.getParser(window.navigator.userAgent);\n const params = new URLSearchParams(window.location.search);\n const os = parser.getOS();\n const browser = parser.getBrowser();\n const platform = parser.getPlatform();\n const parsed = parseCookie(document.cookie);\n\n const linkId = params.get('s');\n const link = linkId ? await getCachedLink(linkId) : null;\n\n const tags: TrackTags = {\n os: `${os.name} ${os.version}`,\n os_name: os.name,\n os_version: os.version,\n browser: `${browser.name} ${browser.version}`,\n browser_name: browser.name,\n browser_version: browser.version,\n device: platform.model,\n device_id: getDeviceId(),\n device_type: platform.type,\n device_vendor: platform.vendor,\n device_pixel_ratio: window.devicePixelRatio,\n screen_width: window.screen.width,\n screen_height: window.screen.height,\n screen_resolution: `${window.screen.width}x${window.screen.height}`,\n webdriver: navigator.webdriver || undefined,\n release: config.release,\n language: navigator.language,\n time_zone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n page_location,\n page_referrer,\n page_title,\n page_load_id,\n // Ad click ids from the URL, each under its parameter's name: this page's click, which\n // names the visit's channel (`CLICK_ID_CHANNELS`). Never filled from a cookie — a cookie\n // outlives the click; the cookies go below under their own names.\n fbclid: params.get('fbclid') ?? undefined,\n ad_id: params.get('ad_id') ?? undefined,\n ad_name: params.get('ad_name') ?? undefined,\n adset_id: params.get('adset_id') ?? undefined,\n adset_name: params.get('adset_name') ?? undefined,\n campaign_id: params.get('campaign_id') ?? undefined,\n campaign_name: params.get('campaign_name') ?? undefined,\n placement: params.get('placement') ?? undefined,\n gclid: params.get('gclid') ?? undefined,\n gclsrc: params.get('gclsrc') ?? undefined,\n gad_source: params.get('gad_source') ?? undefined,\n gad_campaignid: params.get('gad_campaignid') ?? undefined,\n wbraid: params.get('wbraid') ?? undefined,\n gbraid: params.get('gbraid') ?? undefined,\n dclid: params.get('dclid') ?? undefined,\n msclkid: params.get('msclkid') ?? undefined,\n rdt_cid: params.get('rdt_cid') ?? undefined,\n li_fat_id: params.get('li_fat_id') ?? undefined,\n oppref: params.get('oppref') ?? undefined,\n ko_click_id: params.get('ko_click_id') ?? undefined,\n // Snapchat appends `ScCid`, and URL parameters are case-sensitive.\n ScCid: params.get('ScCid') ?? undefined,\n ttclid: params.get('ttclid') ?? undefined,\n twclid: params.get('twclid') ?? undefined,\n yclid: params.get('yclid') ?? undefined,\n epik: params.get('epik') ?? undefined,\n // The ad platforms' first-party cookies, raw and under the cookie's own name, for the\n // conversion senders only: they carry the click to the later pages and visits where the\n // URL no longer does. `_fbc`, `_rdt_cid`, `_gcl_*` and `_uetmsclkid` are kept alive\n // server-side (see @shware/analytics/server resolveClickIdCookies); the rest are the pixels'.\n _fbc: parsed._fbc || undefined,\n _fbp: parsed._fbp || undefined,\n _gcl_aw: parsed._gcl_aw || undefined,\n _gcl_gb: parsed._gcl_gb || undefined,\n _uetmsclkid: parsed._uetmsclkid || undefined,\n _rdt_cid: parsed._rdt_cid || undefined,\n _rdt_uuid: parsed._rdt_uuid || undefined,\n // The Insight Tag's cookie is itself named `li_fat_id`; renamed so it cannot pass for the\n // URL parameter.\n _li_fat_id: parsed.li_fat_id || undefined,\n __oppref: parsed.__oppref || undefined,\n __obref: parsed.__obref || undefined,\n // utm params\n utm_source: link?.utm_source ?? params.get('utm_source') ?? undefined,\n utm_medium: link?.utm_medium ?? params.get('utm_medium') ?? undefined,\n utm_campaign: link?.utm_campaign ?? params.get('utm_campaign') ?? undefined,\n utm_term: link?.utm_term ?? params.get('utm_term') ?? undefined,\n utm_content: link?.utm_content ?? params.get('utm_content') ?? undefined,\n utm_id: link?.utm_id ?? params.get('utm_id') ?? undefined,\n utm_source_platform:\n link?.utm_source_platform ?? params.get('utm_source_platform') ?? undefined,\n utm_creative_format:\n link?.utm_creative_format ?? params.get('utm_creative_format') ?? undefined,\n utm_marketing_tactic:\n link?.utm_marketing_tactic ?? params.get('utm_marketing_tactic') ?? undefined,\n };\n\n cache.tags = tags;\n return tags;\n}\n\nconst map = new Map<string, string>();\n\nexport const storage: Storage = {\n getItem: (key) => {\n try {\n return localStorage.getItem(key);\n } catch {\n console.error('localStorage is not available');\n return map.get(key) ?? null;\n }\n },\n setItem: (key, value) => {\n try {\n localStorage.setItem(key, value);\n } catch {\n console.error('localStorage is not available');\n map.set(key, value);\n }\n },\n};\n"],"mappings":"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"}
|