@shware/analytics 8.3.0 → 8.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +28 -16
  2. package/dist/hooks/use-uet-id-sync.cjs +19 -0
  3. package/dist/hooks/use-uet-id-sync.cjs.map +1 -0
  4. package/dist/hooks/use-uet-id-sync.d.cts +10 -0
  5. package/dist/hooks/use-uet-id-sync.d.cts.map +1 -0
  6. package/dist/hooks/use-uet-id-sync.d.mts +10 -0
  7. package/dist/hooks/use-uet-id-sync.d.mts.map +1 -0
  8. package/dist/hooks/use-uet-id-sync.mjs +18 -0
  9. package/dist/hooks/use-uet-id-sync.mjs.map +1 -0
  10. package/dist/hooks/use-web-analytics.cjs +11 -5
  11. package/dist/hooks/use-web-analytics.cjs.map +1 -1
  12. package/dist/hooks/use-web-analytics.d.cts +4 -1
  13. package/dist/hooks/use-web-analytics.d.cts.map +1 -1
  14. package/dist/hooks/use-web-analytics.d.mts +4 -1
  15. package/dist/hooks/use-web-analytics.d.mts.map +1 -1
  16. package/dist/hooks/use-web-analytics.mjs +11 -5
  17. package/dist/hooks/use-web-analytics.mjs.map +1 -1
  18. package/dist/next/index.cjs +40 -2
  19. package/dist/next/index.cjs.map +1 -1
  20. package/dist/next/index.d.cts +5 -1
  21. package/dist/next/index.d.cts.map +1 -1
  22. package/dist/next/index.d.mts +5 -1
  23. package/dist/next/index.d.mts.map +1 -1
  24. package/dist/next/index.mjs +41 -3
  25. package/dist/next/index.mjs.map +1 -1
  26. package/dist/react-router/index.cjs +27 -3
  27. package/dist/react-router/index.cjs.map +1 -1
  28. package/dist/react-router/index.d.cts +5 -1
  29. package/dist/react-router/index.d.cts.map +1 -1
  30. package/dist/react-router/index.d.mts +5 -1
  31. package/dist/react-router/index.d.mts.map +1 -1
  32. package/dist/react-router/index.mjs +27 -3
  33. package/dist/react-router/index.mjs.map +1 -1
  34. package/dist/schema/index.cjs +1 -0
  35. package/dist/schema/index.cjs.map +1 -1
  36. package/dist/schema/index.d.cts +4 -0
  37. package/dist/schema/index.d.cts.map +1 -1
  38. package/dist/schema/index.d.mts +4 -0
  39. package/dist/schema/index.d.mts.map +1 -1
  40. package/dist/schema/index.mjs +1 -0
  41. package/dist/schema/index.mjs.map +1 -1
  42. package/dist/server/index.d.cts +2 -2
  43. package/dist/server/index.d.mts +2 -2
  44. package/dist/server/microsoft-conversions-api.cjs +8 -8
  45. package/dist/server/microsoft-conversions-api.cjs.map +1 -1
  46. package/dist/server/microsoft-conversions-api.d.cts +15 -8
  47. package/dist/server/microsoft-conversions-api.d.cts.map +1 -1
  48. package/dist/server/microsoft-conversions-api.d.mts +15 -8
  49. package/dist/server/microsoft-conversions-api.d.mts.map +1 -1
  50. package/dist/server/microsoft-conversions-api.mjs +8 -8
  51. package/dist/server/microsoft-conversions-api.mjs.map +1 -1
  52. package/dist/server/openai-conversions-api.cjs +1 -1
  53. package/dist/server/openai-conversions-api.mjs +1 -1
  54. package/dist/server/reddit-conversions-api.cjs +1 -1
  55. package/dist/server/reddit-conversions-api.mjs +1 -1
  56. package/dist/tanstack/analytics.cjs +27 -3
  57. package/dist/tanstack/analytics.cjs.map +1 -1
  58. package/dist/tanstack/analytics.d.cts +5 -1
  59. package/dist/tanstack/analytics.d.cts.map +1 -1
  60. package/dist/tanstack/analytics.d.mts +5 -1
  61. package/dist/tanstack/analytics.d.mts.map +1 -1
  62. package/dist/tanstack/analytics.mjs +27 -3
  63. package/dist/tanstack/analytics.mjs.map +1 -1
  64. package/dist/third-parties/index.cjs +3 -1
  65. package/dist/third-parties/index.d.cts +2 -2
  66. package/dist/third-parties/index.d.mts +2 -2
  67. package/dist/third-parties/index.mjs +2 -2
  68. package/dist/third-parties/meta-pixel.cjs +1 -1
  69. package/dist/third-parties/meta-pixel.mjs +1 -1
  70. package/dist/third-parties/microsoft-uet.cjs +45 -17
  71. package/dist/third-parties/microsoft-uet.cjs.map +1 -1
  72. package/dist/third-parties/microsoft-uet.d.cts +13 -19
  73. package/dist/third-parties/microsoft-uet.d.cts.map +1 -1
  74. package/dist/third-parties/microsoft-uet.d.mts +13 -19
  75. package/dist/third-parties/microsoft-uet.d.mts.map +1 -1
  76. package/dist/third-parties/microsoft-uet.mjs +44 -18
  77. package/dist/third-parties/microsoft-uet.mjs.map +1 -1
  78. package/dist/third-parties/openai-pixel.cjs +1 -1
  79. package/dist/third-parties/openai-pixel.mjs +1 -1
  80. package/dist/third-parties/reddit-pixel.cjs +1 -1
  81. package/dist/third-parties/reddit-pixel.mjs +1 -1
  82. package/dist/track/types.d.cts +7 -0
  83. package/dist/track/types.d.cts.map +1 -1
  84. package/dist/track/types.d.mts +7 -0
  85. package/dist/track/types.d.mts.map +1 -1
  86. package/dist/web/index.cjs +25 -1
  87. package/dist/web/index.cjs.map +1 -1
  88. package/dist/web/index.d.cts.map +1 -1
  89. package/dist/web/index.d.mts.map +1 -1
  90. package/dist/web/index.mjs +25 -1
  91. package/dist/web/index.mjs.map +1 -1
  92. package/dist/web/page-key.cjs +53 -0
  93. package/dist/web/page-key.cjs.map +1 -0
  94. package/dist/web/page-key.d.cts +24 -0
  95. package/dist/web/page-key.d.cts.map +1 -0
  96. package/dist/web/page-key.d.mts +24 -0
  97. package/dist/web/page-key.d.mts.map +1 -0
  98. package/dist/web/page-key.mjs +52 -0
  99. package/dist/web/page-key.mjs.map +1 -0
  100. package/package.json +1 -1
package/README.md CHANGED
@@ -46,6 +46,15 @@ function Button() {
46
46
  }
47
47
  ```
48
48
 
49
+ ## Page views
50
+
51
+ `page_view` fires on a path or query change, never on a hash change — GA4's enhanced measurement,
52
+ Next.js's `usePathname` + `useSearchParams` pattern and PostHog's `history_change` all draw the
53
+ line there (`?page=2` is another page to a funnel, `#faq` is not). Tracking parameters (`utm_*`
54
+ and the ad click ids) are ignored when comparing, so a landing page that cleans them out of its
55
+ URL with `replaceState` does not count itself twice. `page_load_id` rotates on exactly the same
56
+ changes. The framework `Analytics` components wire this up.
57
+
49
58
  ## Backend API
50
59
 
51
60
  - /analytics/tracks: track events
@@ -130,28 +139,30 @@ Microsoft recommends the UET tag and the Conversions API (CAPI) together. The tw
130
139
  (`tagId`, `eventName`, `eventId`): the browser sends the internal event name as the UET action with
131
140
  the event id as `event_id`, the server sends the same name as `eventName` with the same id.
132
141
 
133
- Browser — install the UET snippet from the tag's "Set up tagging" step, then:
142
+ Browser — pass the tag id and the customer id to the framework `Analytics` component
143
+ (`<Analytics uetTagId="97267979" uetCustomerId="255004870" />` from `@shware/analytics/tanstack`,
144
+ `/next` or `/react-router`). It inlines Microsoft's snippet with `enableAutoSpaTracking: true` (the
145
+ tag owns page loads, `sendUETEvent` drops `page_view` and forwards everything else) and fires the
146
+ Conversions API's ID Sync pixel once per visit for the SDK visitor — anonymous visitors included,
147
+ they are who remarketing audiences are built from. Then:
134
148
 
135
149
  ```ts
136
- import {
137
- sendUETEvent,
138
- setUETUser,
139
- setUETConsent,
140
- sendUETIdSync,
141
- } from '@shware/analytics/third-parties';
150
+ import { sendUETEvent, setUETUser, setUETConsent } from '@shware/analytics/third-parties';
142
151
 
143
152
  setupAnalytics({
144
153
  thirdPartyTrackers: [sendUETEvent],
145
154
  // Enhanced conversions: the tag hashes the email/phone itself before they leave the page.
146
- thirdPartyUserSetters: [setUETUser()],
155
+ thirdPartyUserSetters: [setUETUser],
147
156
  });
148
157
  // Consent mode (EEA/UK/CH): push `default` before the tag loads, `update` on the visitor's choice.
149
- setUETConsent('default', { ad_storage: 'denied' });
150
- // ID Sync, once per session: ties the visitor id CAPI sends as `anonymousId` to Microsoft's ids.
151
- // Required for remarketing built from CAPI events, recommended for measurement.
152
- sendUETIdSync({ customerId: 255004870, visitorId: (await getVisitor()).id });
158
+ setUETConsent('default', { ad_storage: 'denied', wait_for_update: 2000 });
153
159
  ```
154
160
 
161
+ ID Sync ties the visitor id CAPI receives as `anonymousId` to Microsoft's ids — required for
162
+ remarketing built from CAPI events, recommended for measurement. `setUETUser` re-syncs on sign-in
163
+ with the user id hashed into `UID` (the same SHA-256 the server sends as `externalId`). Outside the
164
+ `Analytics` components, `configureUET({ customerId })` + `syncUETVisitor()` do the same.
165
+
155
166
  Server — the token comes from the UET tag's "Use Conversions API" step (or the Campaign Management
156
167
  API's `UetTagAuthKey/Query`; an account whose tag setup lacks that option has to ask Microsoft
157
168
  Advertising support), `tagId` is the UET tag id:
@@ -180,9 +191,10 @@ await sendMicrosoftEvents(process.env.MS_ADS_CAPI_TOKEN, 97267979, events, userD
180
191
  - em / ph: SHA-256 of the normalized email (dots and `+alias` stripped from the user part for every
181
192
  domain, lowercase) and of the E.164 phone.
182
193
  - `page_view` events are dropped by default (the tag already reports every page load); pass
183
- `pageLoads: true` for a CAPI-only site to send them as `pageLoad` events. Not yet modelled in
184
- that mode: `pageLoadId` (the id that links a `custom` event to the page load it happened on) and
185
- the revenue-only `custom` event a destination-URL goal with variable revenue needs alongside its
186
- `pageLoad`. With the tag on the page neither applies — the tag reports page loads itself.
194
+ `pageLoads: true` for a CAPI-only site to send them as `pageLoad` events, each `custom` event
195
+ then carrying the `pageLoadId` of the page load it happened on (the `page_load_id` tag, one
196
+ v4 UUID per page load). Not yet modelled in that mode: the revenue-only `custom` event a
197
+ destination-URL goal with variable revenue needs alongside its `pageLoad`. With the tag on the
198
+ page neither applies — the tag reports page loads itself.
187
199
 
188
200
  - [Click IDs](https://learn.microsoft.com/en-us/linkedin/marketing/conversions/enabling-first-party-cookies?view=li-lms-2025-10&source=recommendations): get li_fat_id from url params and cookie
@@ -0,0 +1,19 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_third_parties_microsoft_uet = require("../third-parties/microsoft-uet.cjs");
3
+ let react = require("react");
4
+ //#region src/hooks/use-uet-id-sync.ts
5
+ /**
6
+ * Microsoft Advertising ID Sync from the framework `Analytics` components: record the customer
7
+ * id for `setUETUser` and sync the visitor once per mount — the first page view of the visit,
8
+ * which is where Microsoft wants it. No-op without a customer id.
9
+ */
10
+ function useUETIdSync(customerId) {
11
+ (0, react.useEffect)(() => {
12
+ require_third_parties_microsoft_uet.configureUET({ customerId });
13
+ if (customerId !== void 0) require_third_parties_microsoft_uet.syncUETVisitor();
14
+ }, [customerId]);
15
+ }
16
+ //#endregion
17
+ exports.useUETIdSync = useUETIdSync;
18
+
19
+ //# sourceMappingURL=use-uet-id-sync.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-uet-id-sync.cjs","names":["syncUETVisitor"],"sources":["../../src/hooks/use-uet-id-sync.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport { configureUET, syncUETVisitor } from '../third-parties/microsoft-uet';\n\n/**\n * Microsoft Advertising ID Sync from the framework `Analytics` components: record the customer\n * id for `setUETUser` and sync the visitor once per mount — the first page view of the visit,\n * which is where Microsoft wants it. No-op without a customer id.\n */\nexport function useUETIdSync(customerId: string | number | undefined) {\n useEffect(() => {\n configureUET({ customerId });\n if (customerId !== undefined) void syncUETVisitor();\n }, [customerId]);\n}\n"],"mappings":";;;;;;;;;AAQA,SAAgB,aAAa,YAAyC;CACpE,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,oCAAA,aAAa,EAAE,WAAW,CAAC;EAC3B,IAAI,eAAe,KAAA,GAAW,oCAAKA,eAAe;CACpD,GAAG,CAAC,UAAU,CAAC;AACjB"}
@@ -0,0 +1,10 @@
1
+ //#region src/hooks/use-uet-id-sync.d.ts
2
+ /**
3
+ * Microsoft Advertising ID Sync from the framework `Analytics` components: record the customer
4
+ * id for `setUETUser` and sync the visitor once per mount — the first page view of the visit,
5
+ * which is where Microsoft wants it. No-op without a customer id.
6
+ */
7
+ declare function useUETIdSync(customerId: string | number | undefined): void;
8
+ //#endregion
9
+ export { useUETIdSync };
10
+ //# sourceMappingURL=use-uet-id-sync.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-uet-id-sync.d.cts","names":[],"sources":["../../src/hooks/use-uet-id-sync.ts"],"mappings":";;;;;;iBAQgB,aAAa"}
@@ -0,0 +1,10 @@
1
+ //#region src/hooks/use-uet-id-sync.d.ts
2
+ /**
3
+ * Microsoft Advertising ID Sync from the framework `Analytics` components: record the customer
4
+ * id for `setUETUser` and sync the visitor once per mount — the first page view of the visit,
5
+ * which is where Microsoft wants it. No-op without a customer id.
6
+ */
7
+ declare function useUETIdSync(customerId: string | number | undefined): void;
8
+ //#endregion
9
+ export { useUETIdSync };
10
+ //# sourceMappingURL=use-uet-id-sync.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-uet-id-sync.d.mts","names":[],"sources":["../../src/hooks/use-uet-id-sync.ts"],"mappings":";;;;;;iBAQgB,aAAa"}
@@ -0,0 +1,18 @@
1
+ import { configureUET, syncUETVisitor } from "../third-parties/microsoft-uet.mjs";
2
+ import { useEffect } from "react";
3
+ //#region src/hooks/use-uet-id-sync.ts
4
+ /**
5
+ * Microsoft Advertising ID Sync from the framework `Analytics` components: record the customer
6
+ * id for `setUETUser` and sync the visitor once per mount — the first page view of the visit,
7
+ * which is where Microsoft wants it. No-op without a customer id.
8
+ */
9
+ function useUETIdSync(customerId) {
10
+ useEffect(() => {
11
+ configureUET({ customerId });
12
+ if (customerId !== void 0) syncUETVisitor();
13
+ }, [customerId]);
14
+ }
15
+ //#endregion
16
+ export { useUETIdSync };
17
+
18
+ //# sourceMappingURL=use-uet-id-sync.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-uet-id-sync.mjs","names":[],"sources":["../../src/hooks/use-uet-id-sync.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport { configureUET, syncUETVisitor } from '../third-parties/microsoft-uet';\n\n/**\n * Microsoft Advertising ID Sync from the framework `Analytics` components: record the customer\n * id for `setUETUser` and sync the visitor once per mount — the first page view of the visit,\n * which is where Microsoft wants it. No-op without a customer id.\n */\nexport function useUETIdSync(customerId: string | number | undefined) {\n useEffect(() => {\n configureUET({ customerId });\n if (customerId !== undefined) void syncUETVisitor();\n }, [customerId]);\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,aAAa,YAAyC;CACpE,gBAAgB;EACd,aAAa,EAAE,WAAW,CAAC;EAC3B,IAAI,eAAe,KAAA,GAAW,eAAoB;CACpD,GAAG,CAAC,UAAU,CAAC;AACjB"}
@@ -4,6 +4,7 @@ const require_constants_storage = require("../constants/storage.cjs");
4
4
  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
+ const require_web_page_key = require("../web/page-key.cjs");
7
8
  let _shware_utils = require("@shware/utils");
8
9
  let react = require("react");
9
10
  //#region src/hooks/use-web-analytics.ts
@@ -51,13 +52,18 @@ function onVisibilityChange() {
51
52
  * 1. send session_start event when the page is loaded
52
53
  * 2. send scroll event when the user scrolls more than 90% of the page
53
54
  * 3. send user_engagement event when the page is hidden or the user is not focused
55
+ * 4. send page_view when the page changes — path or query, see `getPageKey`; `search` is the
56
+ * router's query string (`?a=1`, or `''`), required so no caller forgets that the query is
57
+ * part of what makes a page
54
58
  */
55
- function useWebAnalytics(pathname) {
59
+ function useWebAnalytics(pathname, search) {
60
+ const page = require_web_page_key.getPageKey(pathname, search);
61
+ const prevPage = require_hooks_use_previous.usePrevious(page);
56
62
  const prevPathname = require_hooks_use_previous.usePrevious(pathname);
57
63
  const hasSendScroll = (0, react.useRef)(false);
58
64
  (0, react.useEffect)(() => {
59
65
  hasSendScroll.current = false;
60
- }, [pathname]);
66
+ }, [page]);
61
67
  (0, react.useEffect)(() => {
62
68
  const session = require_setup_session.getSession();
63
69
  sendFirstVisit(pathname);
@@ -103,10 +109,10 @@ function useWebAnalytics(pathname) {
103
109
  page_title: document.title,
104
110
  page_referrer: document.referrer,
105
111
  page_location: window.location.href,
106
- previous_page_path: prevPathname ?? void 0,
107
- engagement_time_msec: prevPathname ? require_setup_session.getSession().flush() : void 0
112
+ previous_page_path: prevPage === void 0 ? void 0 : prevPathname,
113
+ engagement_time_msec: prevPage === void 0 ? void 0 : require_setup_session.getSession().flush()
108
114
  });
109
- }, [pathname]);
115
+ }, [page]);
110
116
  }
111
117
  //#endregion
112
118
  exports.useWebAnalytics = useWebAnalytics;
@@ -1 +1 @@
1
- {"version":3,"file":"use-web-analytics.cjs","names":["config","keys","getSession","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, 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\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\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\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 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
+ {"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, 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: 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\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\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 * 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: document.referrer,\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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"}
@@ -3,8 +3,11 @@
3
3
  * 1. send session_start event when the page is loaded
4
4
  * 2. send scroll event when the user scrolls more than 90% of the page
5
5
  * 3. send user_engagement event when the page is hidden or the user is not focused
6
+ * 4. send page_view when the page changes — path or query, see `getPageKey`; `search` is the
7
+ * router's query string (`?a=1`, or `''`), required so no caller forgets that the query is
8
+ * part of what makes a page
6
9
  */
7
- declare function useWebAnalytics(pathname: string): void;
10
+ declare function useWebAnalytics(pathname: string, search: string): void;
8
11
  //#endregion
9
12
  export { useWebAnalytics };
10
13
  //# sourceMappingURL=use-web-analytics.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-web-analytics.d.cts","names":[],"sources":["../../src/hooks/use-web-analytics.ts"],"mappings":";;;;;;iBA0DgB,gBAAgB"}
1
+ {"version":3,"file":"use-web-analytics.d.cts","names":[],"sources":["../../src/hooks/use-web-analytics.ts"],"mappings":";;;;;;;;;iBA8DgB,gBAAgB,kBAAkB"}
@@ -3,8 +3,11 @@
3
3
  * 1. send session_start event when the page is loaded
4
4
  * 2. send scroll event when the user scrolls more than 90% of the page
5
5
  * 3. send user_engagement event when the page is hidden or the user is not focused
6
+ * 4. send page_view when the page changes — path or query, see `getPageKey`; `search` is the
7
+ * router's query string (`?a=1`, or `''`), required so no caller forgets that the query is
8
+ * part of what makes a page
6
9
  */
7
- declare function useWebAnalytics(pathname: string): void;
10
+ declare function useWebAnalytics(pathname: string, search: string): void;
8
11
  //#endregion
9
12
  export { useWebAnalytics };
10
13
  //# sourceMappingURL=use-web-analytics.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-web-analytics.d.mts","names":[],"sources":["../../src/hooks/use-web-analytics.ts"],"mappings":";;;;;;iBA0DgB,gBAAgB"}
1
+ {"version":3,"file":"use-web-analytics.d.mts","names":[],"sources":["../../src/hooks/use-web-analytics.ts"],"mappings":";;;;;;;;;iBA8DgB,gBAAgB,kBAAkB"}
@@ -3,6 +3,7 @@ import { keys } from "../constants/storage.mjs";
3
3
  import { getSession } from "../setup/session.mjs";
4
4
  import { sendBeacon, track } from "../track/index.mjs";
5
5
  import { usePrevious } from "./use-previous.mjs";
6
+ import { getPageKey } from "../web/page-key.mjs";
6
7
  import { throttle } from "@shware/utils";
7
8
  import { useEffect, useRef } from "react";
8
9
  //#region src/hooks/use-web-analytics.ts
@@ -50,13 +51,18 @@ function onVisibilityChange() {
50
51
  * 1. send session_start event when the page is loaded
51
52
  * 2. send scroll event when the user scrolls more than 90% of the page
52
53
  * 3. send user_engagement event when the page is hidden or the user is not focused
54
+ * 4. send page_view when the page changes — path or query, see `getPageKey`; `search` is the
55
+ * router's query string (`?a=1`, or `''`), required so no caller forgets that the query is
56
+ * part of what makes a page
53
57
  */
54
- function useWebAnalytics(pathname) {
58
+ function useWebAnalytics(pathname, search) {
59
+ const page = getPageKey(pathname, search);
60
+ const prevPage = usePrevious(page);
55
61
  const prevPathname = usePrevious(pathname);
56
62
  const hasSendScroll = useRef(false);
57
63
  useEffect(() => {
58
64
  hasSendScroll.current = false;
59
- }, [pathname]);
65
+ }, [page]);
60
66
  useEffect(() => {
61
67
  const session = getSession();
62
68
  sendFirstVisit(pathname);
@@ -102,10 +108,10 @@ function useWebAnalytics(pathname) {
102
108
  page_title: document.title,
103
109
  page_referrer: document.referrer,
104
110
  page_location: window.location.href,
105
- previous_page_path: prevPathname ?? void 0,
106
- engagement_time_msec: prevPathname ? getSession().flush() : void 0
111
+ previous_page_path: prevPage === void 0 ? void 0 : prevPathname,
112
+ engagement_time_msec: prevPage === void 0 ? void 0 : getSession().flush()
107
113
  });
108
- }, [pathname]);
114
+ }, [page]);
109
115
  }
110
116
  //#endregion
111
117
  export { useWebAnalytics };
@@ -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, 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\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\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\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 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
+ {"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 { 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: 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\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\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 * 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: document.referrer,\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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"}
@@ -3,17 +3,29 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
4
4
  const require_track_index = require("../track/index.cjs");
5
5
  const require_hooks_use_outbound_click_analytics = require("../hooks/use-outbound-click-analytics.cjs");
6
+ const require_hooks_use_uet_id_sync = require("../hooks/use-uet-id-sync.cjs");
6
7
  const require_hooks_use_web_analytics = require("../hooks/use-web-analytics.cjs");
8
+ let react = require("react");
7
9
  let next_navigation_js = require("next/navigation.js");
8
10
  let next_script_js = require("next/script.js");
9
11
  next_script_js = require_runtime.__toESM(next_script_js, 1);
10
12
  let next_web_vitals_js = require("next/web-vitals.js");
11
13
  let react_jsx_runtime = require("react/jsx-runtime");
12
14
  //#region src/next/index.tsx
13
- function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, hotjarId, redditPixelId, linkedInPartnerId, facebookAppId, reportWebVitals = true }) {
15
+ /**
16
+ * Its own component under a Suspense boundary: `useSearchParams` in a statically rendered route
17
+ * bails the whole tree out to client rendering unless something above it suspends, and Next
18
+ * fails the build for a page that lets it. The boundary is here so a host does not have to know.
19
+ */
20
+ function PageViews() {
14
21
  const pathname = (0, next_navigation_js.usePathname)();
15
- require_hooks_use_web_analytics.useWebAnalytics(pathname);
22
+ const searchParams = (0, next_navigation_js.useSearchParams)();
23
+ require_hooks_use_web_analytics.useWebAnalytics(pathname, searchParams.toString());
24
+ return null;
25
+ }
26
+ function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, hotjarId, redditPixelId, linkedInPartnerId, uetTagId, uetCustomerId, facebookAppId, reportWebVitals = true }) {
16
27
  require_hooks_use_outbound_click_analytics.useOutboundClickAnalytics();
28
+ require_hooks_use_uet_id_sync.useUETIdSync(uetCustomerId);
17
29
  (0, next_web_vitals_js.useReportWebVitals)((metric) => {
18
30
  if (!reportWebVitals) return;
19
31
  const properties = {
@@ -28,6 +40,10 @@ function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, hotjarId, reddi
28
40
  require_track_index.track(metric.name, properties);
29
41
  });
30
42
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
43
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Suspense, {
44
+ fallback: null,
45
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageViews, {})
46
+ }),
31
47
  facebookAppId && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("meta", {
32
48
  property: "fb:app_id",
33
49
  content: facebookAppId
@@ -111,6 +127,28 @@ function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, hotjarId, reddi
111
127
  s.parentNode.insertBefore(b, s);
112
128
  })(window.lintrk);
113
129
  ` }
130
+ }),
131
+ uetTagId && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(next_script_js.default, {
132
+ strategy: "afterInteractive",
133
+ id: "uet-tag",
134
+ dangerouslySetInnerHTML: { __html: `
135
+ (function (w, d, t, u, o) {
136
+ w[u] = w[u] || [], o.ts = (new Date).getTime();
137
+ var n = d.createElement(t);
138
+ n.src = "https://bat.bing.net/bat.js?ti=" + o.ti + ("uetq" != u ? "&q=" + u : ""),
139
+ n.async = 1, n.onload = n.onreadystatechange = function() {
140
+ var s = this.readyState;
141
+ s && "loaded" !== s && "complete" !== s ||
142
+ (o.q = w[u], w[u] = new UET(o), w[u].push("pageLoad"),
143
+ n.onload = n.onreadystatechange = null)
144
+ };
145
+ var i = d.getElementsByTagName(t)[0];
146
+ i.parentNode.insertBefore(n, i);
147
+ })(window, document, "script", "uetq", {
148
+ ti: "${uetTagId}",
149
+ enableAutoSpaTracking: true
150
+ });
151
+ ` }
114
152
  }),
115
153
  hotjarId && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(next_script_js.default, {
116
154
  id: "hotjar",
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["usePathname","Script"],"sources":["../../src/next/index.tsx"],"sourcesContent":["'use client';\n\nimport { usePathname } from 'next/navigation';\nimport Script from 'next/script';\nimport { useReportWebVitals } from 'next/web-vitals';\nimport { useOutboundClickAnalytics } from '../hooks/use-outbound-click-analytics';\nimport { useWebAnalytics } from '../hooks/use-web-analytics';\nimport type { PixelId as MetaPixelId } from '../track/fbq';\nimport type { GaId, GtmId } from '../track/gtag';\nimport { track } from '../track/index';\nimport type { PixelId as RedditPixelId } from '../track/rdt';\n\ninterface Props {\n gaId?: GaId;\n gaSrc?: string;\n gtmId?: GtmId;\n hotjarId?: `${number}`;\n metaPixelId?: MetaPixelId;\n redditPixelId?: RedditPixelId;\n linkedInPartnerId?: `${number}`;\n facebookAppId?: string;\n nonce?: string;\n debugMode?: boolean;\n reportWebVitals?: boolean;\n}\n\nexport function Analytics({\n gaId,\n gaSrc,\n nonce,\n debugMode,\n metaPixelId,\n hotjarId,\n redditPixelId,\n linkedInPartnerId,\n facebookAppId,\n reportWebVitals = true,\n}: Props) {\n const pathname = usePathname();\n useWebAnalytics(pathname);\n useOutboundClickAnalytics();\n\n useReportWebVitals((metric) => {\n if (!reportWebVitals) return;\n const properties = {\n value: metric.delta,\n metric_id: metric.id,\n metric_value: metric.value,\n metric_delta: metric.delta,\n metric_rating: metric.rating,\n metric_navigation_type: metric.navigationType,\n non_interaction: true, // avoids affecting bounce rate.\n };\n track(metric.name, properties);\n });\n\n return (\n <>\n {facebookAppId && <meta property=\"fb:app_id\" content={facebookAppId} />}\n {gaId && (\n <>\n <Script\n id=\"gtag\"\n nonce={nonce}\n src={gaSrc ?? `https://www.googletagmanager.com/gtag/js?id=${gaId}`}\n />\n <Script\n nonce={nonce}\n id=\"gtag-init\"\n dangerouslySetInnerHTML={{\n __html: `\n window.dataLayer = window.dataLayer || [];\n function gtag(){dataLayer.push(arguments);}\n gtag('js', new Date());\n gtag('config', '${gaId}'${debugMode ? \" ,{ 'debug_mode': true }\" : ''});\n `,\n }}\n />\n </>\n )}\n {metaPixelId && (\n <Script\n id=\"meta-pixel\"\n strategy=\"afterInteractive\"\n dangerouslySetInnerHTML={{\n __html: `\n !(function (f, b, e, v, n, t, s) {\n if (f.fbq) return;\n n = f.fbq = function () {\n n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments);\n };\n if (!f._fbq) f._fbq = n;\n n.push = n;\n n.loaded = !0;\n n.version = '2.0';\n n.queue = [];\n t = b.createElement(e);\n t.async = !0;\n t.src = v;\n s = b.getElementsByTagName(e)[0];\n s.parentNode.insertBefore(t, s);\n })(window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js');\n fbq('init', '${metaPixelId}');\n fbq('track', 'PageView');`,\n }}\n />\n )}\n {redditPixelId && (\n <Script\n id=\"reddit-pixel\"\n strategy=\"afterInteractive\"\n dangerouslySetInnerHTML={{\n __html: `\n !function(w,d) {\n if(!w.rdt) {\n var p = w.rdt = function() {\n p.sendEvent ? p.sendEvent.apply(p,arguments) : p.callQueue.push(arguments)\n };\n p.callQueue = [];\n var t = d.createElement(\"script\");\n t.src = \"https://www.redditstatic.com/ads/pixel.js\";\n t.async = !0;\n var s = d.getElementsByTagName(\"script\")[0];\n s.parentNode.insertBefore(t,s)\n }\n }(window, document);\n rdt('init', '${redditPixelId}');\n rdt('track', 'PageVisit');`,\n }}\n />\n )}\n {linkedInPartnerId && (\n <Script\n id=\"linkedin-insight-tag\"\n strategy=\"afterInteractive\"\n dangerouslySetInnerHTML={{\n __html: `\n _linkedin_partner_id = \"${linkedInPartnerId}\";\n window._linkedin_data_partner_ids = window._linkedin_data_partner_ids || [];\n window._linkedin_data_partner_ids.push(_linkedin_partner_id);\n\n (function(l) {\n if (!l){\n window.lintrk = function(a,b){ \n window.lintrk.q.push([a,b])\n };\n window.lintrk.q=[]\n }\n var s = document.getElementsByTagName(\"script\")[0];\n var b = document.createElement(\"script\");\n b.type = \"text/javascript\";b.async = true;\n b.src = \"https://snap.licdn.com/li.lms-analytics/insight.min.js\";\n s.parentNode.insertBefore(b, s);\n })(window.lintrk);\n `,\n }}\n />\n )}\n {hotjarId && (\n <Script\n id=\"hotjar\"\n strategy=\"afterInteractive\"\n dangerouslySetInnerHTML={{\n __html: `\n (function(h,o,t,j,a,r){\n h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};\n h._hjSettings={hjid:${hotjarId},hjsv:6};\n a=o.getElementsByTagName('head')[0];\n r=o.createElement('script');r.async=1;\n r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;\n a.appendChild(r);\n })(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');\n `,\n }}\n />\n )}\n </>\n );\n}\n"],"mappings":";;;;;;;;;;;;AA0BA,SAAgB,UAAU,EACxB,MACA,OACA,OACA,WACA,aACA,UACA,eACA,mBACA,eACA,kBAAkB,QACV;CACR,MAAM,YAAA,GAAWA,mBAAAA,YAAAA,CAAY;CAC7B,gCAAA,gBAAgB,QAAQ;CACxB,2CAAA,0BAA0B;CAE1B,CAAA,GAAA,mBAAA,mBAAA,EAAoB,WAAW;EAC7B,IAAI,CAAC,iBAAiB;EACtB,MAAM,aAAa;GACjB,OAAO,OAAO;GACd,WAAW,OAAO;GAClB,cAAc,OAAO;GACrB,cAAc,OAAO;GACrB,eAAe,OAAO;GACtB,wBAAwB,OAAO;GAC/B,iBAAiB;EACnB;EACA,oBAAA,MAAM,OAAO,MAAM,UAAU;CAC/B,CAAC;CAED,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAA,kBAAA,UAAA,EAAA,UAAA;EACG,iBAAiB,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;GAAM,UAAS;GAAY,SAAS;EAAgB,CAAA;EACrE,QACC,iBAAA,GAAA,kBAAA,KAAA,CAAA,kBAAA,UAAA,EAAA,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAACC,eAAAA,SAAD;GACE,IAAG;GACI;GACP,KAAK,SAAS,+CAA+C;EAC9D,CAAA,GACD,iBAAA,GAAA,kBAAA,IAAA,CAACA,eAAAA,SAAD;GACS;GACP,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;gCAIU,KAAK,GAAG,YAAY,6BAA6B,GAAG;cAExE;EACD,CAAA,CACD,EAAA,CAAA;EAEH,eACC,iBAAA,GAAA,kBAAA,IAAA,CAACA,eAAAA,SAAD;GACE,IAAG;GACH,UAAS;GACT,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;;;;2BAiBO,YAAY;uCAE7B;EACD,CAAA;EAEF,iBACC,iBAAA,GAAA,kBAAA,IAAA,CAACA,eAAAA,SAAD;GACE,IAAG;GACH,UAAS;GACT,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;2BAcO,cAAc;wCAE/B;EACD,CAAA;EAEF,qBACC,iBAAA,GAAA,kBAAA,IAAA,CAACA,eAAAA,SAAD;GACE,IAAG;GACH,UAAS;GACT,yBAAyB,EACvB,QAAQ;wCACoB,kBAAkB;;;;;;;;;;;;;;;;;gBAkBhD;EACD,CAAA;EAEF,YACC,iBAAA,GAAA,kBAAA,IAAA,CAACA,eAAAA,SAAD;GACE,IAAG;GACH,UAAS;GACT,yBAAyB,EACvB,QAAQ;;;oCAGgB,SAAS;;;;;;cAOnC;EACD,CAAA;CAEH,EAAA,CAAA;AAEN"}
1
+ {"version":3,"file":"index.cjs","names":["usePathname","useSearchParams","Suspense","Script"],"sources":["../../src/next/index.tsx"],"sourcesContent":["'use client';\n\nimport { usePathname, useSearchParams } from 'next/navigation';\nimport Script from 'next/script';\nimport { useReportWebVitals } from 'next/web-vitals';\nimport { Suspense } from 'react';\nimport { useOutboundClickAnalytics } from '../hooks/use-outbound-click-analytics';\nimport { useUETIdSync } from '../hooks/use-uet-id-sync';\nimport { useWebAnalytics } from '../hooks/use-web-analytics';\nimport type { PixelId as MetaPixelId } from '../track/fbq';\nimport type { GaId, GtmId } from '../track/gtag';\nimport { track } from '../track/index';\nimport type { PixelId as RedditPixelId } from '../track/rdt';\n\ninterface Props {\n gaId?: GaId;\n gaSrc?: string;\n gtmId?: GtmId;\n hotjarId?: `${number}`;\n metaPixelId?: MetaPixelId;\n redditPixelId?: RedditPixelId;\n linkedInPartnerId?: `${number}`;\n /** Microsoft Advertising UET tag id. The tag reports page loads itself (`enableAutoSpaTracking`). */\n uetTagId?: `${number}`;\n /** Microsoft Advertising customer id (`cid` in the ads UI's URLs): enables the Conversions API's ID Sync pixel. */\n uetCustomerId?: `${number}`;\n facebookAppId?: string;\n nonce?: string;\n debugMode?: boolean;\n reportWebVitals?: boolean;\n}\n\n/**\n * Its own component under a Suspense boundary: `useSearchParams` in a statically rendered route\n * bails the whole tree out to client rendering unless something above it suspends, and Next\n * fails the build for a page that lets it. The boundary is here so a host does not have to know.\n */\nfunction PageViews() {\n const pathname = usePathname();\n const searchParams = useSearchParams();\n useWebAnalytics(pathname, searchParams.toString());\n return null;\n}\n\nexport function Analytics({\n gaId,\n gaSrc,\n nonce,\n debugMode,\n metaPixelId,\n hotjarId,\n redditPixelId,\n linkedInPartnerId,\n uetTagId,\n uetCustomerId,\n facebookAppId,\n reportWebVitals = true,\n}: Props) {\n useOutboundClickAnalytics();\n useUETIdSync(uetCustomerId);\n\n useReportWebVitals((metric) => {\n if (!reportWebVitals) return;\n const properties = {\n value: metric.delta,\n metric_id: metric.id,\n metric_value: metric.value,\n metric_delta: metric.delta,\n metric_rating: metric.rating,\n metric_navigation_type: metric.navigationType,\n non_interaction: true, // avoids affecting bounce rate.\n };\n track(metric.name, properties);\n });\n\n return (\n <>\n <Suspense fallback={null}>\n <PageViews />\n </Suspense>\n {facebookAppId && <meta property=\"fb:app_id\" content={facebookAppId} />}\n {gaId && (\n <>\n <Script\n id=\"gtag\"\n nonce={nonce}\n src={gaSrc ?? `https://www.googletagmanager.com/gtag/js?id=${gaId}`}\n />\n <Script\n nonce={nonce}\n id=\"gtag-init\"\n dangerouslySetInnerHTML={{\n __html: `\n window.dataLayer = window.dataLayer || [];\n function gtag(){dataLayer.push(arguments);}\n gtag('js', new Date());\n gtag('config', '${gaId}'${debugMode ? \" ,{ 'debug_mode': true }\" : ''});\n `,\n }}\n />\n </>\n )}\n {metaPixelId && (\n <Script\n id=\"meta-pixel\"\n strategy=\"afterInteractive\"\n dangerouslySetInnerHTML={{\n __html: `\n !(function (f, b, e, v, n, t, s) {\n if (f.fbq) return;\n n = f.fbq = function () {\n n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments);\n };\n if (!f._fbq) f._fbq = n;\n n.push = n;\n n.loaded = !0;\n n.version = '2.0';\n n.queue = [];\n t = b.createElement(e);\n t.async = !0;\n t.src = v;\n s = b.getElementsByTagName(e)[0];\n s.parentNode.insertBefore(t, s);\n })(window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js');\n fbq('init', '${metaPixelId}');\n fbq('track', 'PageView');`,\n }}\n />\n )}\n {redditPixelId && (\n <Script\n id=\"reddit-pixel\"\n strategy=\"afterInteractive\"\n dangerouslySetInnerHTML={{\n __html: `\n !function(w,d) {\n if(!w.rdt) {\n var p = w.rdt = function() {\n p.sendEvent ? p.sendEvent.apply(p,arguments) : p.callQueue.push(arguments)\n };\n p.callQueue = [];\n var t = d.createElement(\"script\");\n t.src = \"https://www.redditstatic.com/ads/pixel.js\";\n t.async = !0;\n var s = d.getElementsByTagName(\"script\")[0];\n s.parentNode.insertBefore(t,s)\n }\n }(window, document);\n rdt('init', '${redditPixelId}');\n rdt('track', 'PageVisit');`,\n }}\n />\n )}\n {linkedInPartnerId && (\n <Script\n id=\"linkedin-insight-tag\"\n strategy=\"afterInteractive\"\n dangerouslySetInnerHTML={{\n __html: `\n _linkedin_partner_id = \"${linkedInPartnerId}\";\n window._linkedin_data_partner_ids = window._linkedin_data_partner_ids || [];\n window._linkedin_data_partner_ids.push(_linkedin_partner_id);\n\n (function(l) {\n if (!l){\n window.lintrk = function(a,b){ \n window.lintrk.q.push([a,b])\n };\n window.lintrk.q=[]\n }\n var s = document.getElementsByTagName(\"script\")[0];\n var b = document.createElement(\"script\");\n b.type = \"text/javascript\";b.async = true;\n b.src = \"https://snap.licdn.com/li.lms-analytics/insight.min.js\";\n s.parentNode.insertBefore(b, s);\n })(window.lintrk);\n `,\n }}\n />\n )}\n {uetTagId && (\n <Script\n strategy=\"afterInteractive\"\n id=\"uet-tag\"\n dangerouslySetInnerHTML={{\n __html: `\n (function (w, d, t, u, o) {\n w[u] = w[u] || [], o.ts = (new Date).getTime();\n var n = d.createElement(t);\n n.src = \"https://bat.bing.net/bat.js?ti=\" + o.ti + (\"uetq\" != u ? \"&q=\" + u : \"\"),\n n.async = 1, n.onload = n.onreadystatechange = function() {\n var s = this.readyState;\n s && \"loaded\" !== s && \"complete\" !== s ||\n (o.q = w[u], w[u] = new UET(o), w[u].push(\"pageLoad\"),\n n.onload = n.onreadystatechange = null)\n };\n var i = d.getElementsByTagName(t)[0];\n i.parentNode.insertBefore(n, i);\n })(window, document, \"script\", \"uetq\", {\n ti: \"${uetTagId}\",\n enableAutoSpaTracking: true\n });\n `,\n }}\n />\n )}\n {hotjarId && (\n <Script\n id=\"hotjar\"\n strategy=\"afterInteractive\"\n dangerouslySetInnerHTML={{\n __html: `\n (function(h,o,t,j,a,r){\n h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};\n h._hjSettings={hjid:${hotjarId},hjsv:6};\n a=o.getElementsByTagName('head')[0];\n r=o.createElement('script');r.async=1;\n r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;\n a.appendChild(r);\n })(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');\n `,\n }}\n />\n )}\n </>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqCA,SAAS,YAAY;CACnB,MAAM,YAAA,GAAWA,mBAAAA,YAAAA,CAAY;CAC7B,MAAM,gBAAA,GAAeC,mBAAAA,gBAAAA,CAAgB;CACrC,gCAAA,gBAAgB,UAAU,aAAa,SAAS,CAAC;CACjD,OAAO;AACT;AAEA,SAAgB,UAAU,EACxB,MACA,OACA,OACA,WACA,aACA,UACA,eACA,mBACA,UACA,eACA,eACA,kBAAkB,QACV;CACR,2CAAA,0BAA0B;CAC1B,8BAAA,aAAa,aAAa;CAE1B,CAAA,GAAA,mBAAA,mBAAA,EAAoB,WAAW;EAC7B,IAAI,CAAC,iBAAiB;EACtB,MAAM,aAAa;GACjB,OAAO,OAAO;GACd,WAAW,OAAO;GAClB,cAAc,OAAO;GACrB,cAAc,OAAO;GACrB,eAAe,OAAO;GACtB,wBAAwB,OAAO;GAC/B,iBAAiB;EACnB;EACA,oBAAA,MAAM,OAAO,MAAM,UAAU;CAC/B,CAAC;CAED,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAA,kBAAA,UAAA,EAAA,UAAA;EACE,iBAAA,GAAA,kBAAA,IAAA,CAACC,MAAAA,UAAD;GAAU,UAAU;GAClB,UAAA,iBAAA,GAAA,kBAAA,IAAA,CAAC,WAAD,CAAY,CAAA;EACJ,CAAA;EACT,iBAAiB,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;GAAM,UAAS;GAAY,SAAS;EAAgB,CAAA;EACrE,QACC,iBAAA,GAAA,kBAAA,KAAA,CAAA,kBAAA,UAAA,EAAA,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAACC,eAAAA,SAAD;GACE,IAAG;GACI;GACP,KAAK,SAAS,+CAA+C;EAC9D,CAAA,GACD,iBAAA,GAAA,kBAAA,IAAA,CAACA,eAAAA,SAAD;GACS;GACP,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;gCAIU,KAAK,GAAG,YAAY,6BAA6B,GAAG;cAExE;EACD,CAAA,CACD,EAAA,CAAA;EAEH,eACC,iBAAA,GAAA,kBAAA,IAAA,CAACA,eAAAA,SAAD;GACE,IAAG;GACH,UAAS;GACT,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;;;;2BAiBO,YAAY;uCAE7B;EACD,CAAA;EAEF,iBACC,iBAAA,GAAA,kBAAA,IAAA,CAACA,eAAAA,SAAD;GACE,IAAG;GACH,UAAS;GACT,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;2BAcO,cAAc;wCAE/B;EACD,CAAA;EAEF,qBACC,iBAAA,GAAA,kBAAA,IAAA,CAACA,eAAAA,SAAD;GACE,IAAG;GACH,UAAS;GACT,yBAAyB,EACvB,QAAQ;wCACoB,kBAAkB;;;;;;;;;;;;;;;;;gBAkBhD;EACD,CAAA;EAEF,YACC,iBAAA,GAAA,kBAAA,IAAA,CAACA,eAAAA,SAAD;GACE,UAAS;GACT,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;uBAcG,SAAS;;;cAItB;EACD,CAAA;EAEF,YACC,iBAAA,GAAA,kBAAA,IAAA,CAACA,eAAAA,SAAD;GACE,IAAG;GACH,UAAS;GACT,yBAAyB,EACvB,QAAQ;;;oCAGgB,SAAS;;;;;;cAOnC;EACD,CAAA;CAEH,EAAA,CAAA;AAEN"}
@@ -10,12 +10,16 @@ interface Props {
10
10
  metaPixelId?: PixelId;
11
11
  redditPixelId?: PixelId$1;
12
12
  linkedInPartnerId?: `${number}`;
13
+ /** Microsoft Advertising UET tag id. The tag reports page loads itself (`enableAutoSpaTracking`). */
14
+ uetTagId?: `${number}`;
15
+ /** Microsoft Advertising customer id (`cid` in the ads UI's URLs): enables the Conversions API's ID Sync pixel. */
16
+ uetCustomerId?: `${number}`;
13
17
  facebookAppId?: string;
14
18
  nonce?: string;
15
19
  debugMode?: boolean;
16
20
  reportWebVitals?: boolean;
17
21
  }
18
- declare function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, hotjarId, redditPixelId, linkedInPartnerId, facebookAppId, reportWebVitals }: Props): import("react").JSX.Element;
22
+ declare function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, hotjarId, redditPixelId, linkedInPartnerId, uetTagId, uetCustomerId, facebookAppId, reportWebVitals }: Props): import("react").JSX.Element;
19
23
  //#endregion
20
24
  export { Analytics };
21
25
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/next/index.tsx"],"mappings":";;;;UAYU;EACR,OAAO;EACP;EACA,QAAQ;EACR;EACA,cAAc;EACd,gBAAgB;EAChB;EACA;EACA;EACA;EACA;;iBAGc,YACd,MACA,OACA,OACA,WACA,aACA,UACA,eACA,mBACA,eACA,mBACC,wBAAK,IAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/next/index.tsx"],"mappings":";;;;UAcU;EACR,OAAO;EACP;EACA,QAAQ;EACR;EACA,cAAc;EACd,gBAAgB;EAChB;;EAEA;;EAEA;EACA;EACA;EACA;EACA;;iBAec,YACd,MACA,OACA,OACA,WACA,aACA,UACA,eACA,mBACA,UACA,eACA,eACA,mBACC,wBAAK,IAAA"}
@@ -10,12 +10,16 @@ interface Props {
10
10
  metaPixelId?: PixelId;
11
11
  redditPixelId?: PixelId$1;
12
12
  linkedInPartnerId?: `${number}`;
13
+ /** Microsoft Advertising UET tag id. The tag reports page loads itself (`enableAutoSpaTracking`). */
14
+ uetTagId?: `${number}`;
15
+ /** Microsoft Advertising customer id (`cid` in the ads UI's URLs): enables the Conversions API's ID Sync pixel. */
16
+ uetCustomerId?: `${number}`;
13
17
  facebookAppId?: string;
14
18
  nonce?: string;
15
19
  debugMode?: boolean;
16
20
  reportWebVitals?: boolean;
17
21
  }
18
- declare function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, hotjarId, redditPixelId, linkedInPartnerId, facebookAppId, reportWebVitals }: Props): import("react").JSX.Element;
22
+ declare function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, hotjarId, redditPixelId, linkedInPartnerId, uetTagId, uetCustomerId, facebookAppId, reportWebVitals }: Props): import("react").JSX.Element;
19
23
  //#endregion
20
24
  export { Analytics };
21
25
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/next/index.tsx"],"mappings":";;;;UAYU;EACR,OAAO;EACP;EACA,QAAQ;EACR;EACA,cAAc;EACd,gBAAgB;EAChB;EACA;EACA;EACA;EACA;;iBAGc,YACd,MACA,OACA,OACA,WACA,aACA,UACA,eACA,mBACA,eACA,mBACC,wBAAK,IAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/next/index.tsx"],"mappings":";;;;UAcU;EACR,OAAO;EACP;EACA,QAAQ;EACR;EACA,cAAc;EACd,gBAAgB;EAChB;;EAEA;;EAEA;EACA;EACA;EACA;EACA;;iBAec,YACd,MACA,OACA,OACA,WACA,aACA,UACA,eACA,mBACA,UACA,eACA,eACA,mBACC,wBAAK,IAAA"}
@@ -1,16 +1,28 @@
1
1
  "use client";
2
2
  import { track } from "../track/index.mjs";
3
3
  import { useOutboundClickAnalytics } from "../hooks/use-outbound-click-analytics.mjs";
4
+ import { useUETIdSync } from "../hooks/use-uet-id-sync.mjs";
4
5
  import { useWebAnalytics } from "../hooks/use-web-analytics.mjs";
5
- import { usePathname } from "next/navigation.js";
6
+ import { Suspense } from "react";
7
+ import { usePathname, useSearchParams } from "next/navigation.js";
6
8
  import Script from "next/script.js";
7
9
  import { useReportWebVitals } from "next/web-vitals.js";
8
10
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
11
  //#region src/next/index.tsx
10
- function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, hotjarId, redditPixelId, linkedInPartnerId, facebookAppId, reportWebVitals = true }) {
12
+ /**
13
+ * Its own component under a Suspense boundary: `useSearchParams` in a statically rendered route
14
+ * bails the whole tree out to client rendering unless something above it suspends, and Next
15
+ * fails the build for a page that lets it. The boundary is here so a host does not have to know.
16
+ */
17
+ function PageViews() {
11
18
  const pathname = usePathname();
12
- useWebAnalytics(pathname);
19
+ const searchParams = useSearchParams();
20
+ useWebAnalytics(pathname, searchParams.toString());
21
+ return null;
22
+ }
23
+ function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, hotjarId, redditPixelId, linkedInPartnerId, uetTagId, uetCustomerId, facebookAppId, reportWebVitals = true }) {
13
24
  useOutboundClickAnalytics();
25
+ useUETIdSync(uetCustomerId);
14
26
  useReportWebVitals((metric) => {
15
27
  if (!reportWebVitals) return;
16
28
  const properties = {
@@ -25,6 +37,10 @@ function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, hotjarId, reddi
25
37
  track(metric.name, properties);
26
38
  });
27
39
  return /* @__PURE__ */ jsxs(Fragment, { children: [
40
+ /* @__PURE__ */ jsx(Suspense, {
41
+ fallback: null,
42
+ children: /* @__PURE__ */ jsx(PageViews, {})
43
+ }),
28
44
  facebookAppId && /* @__PURE__ */ jsx("meta", {
29
45
  property: "fb:app_id",
30
46
  content: facebookAppId
@@ -108,6 +124,28 @@ function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, hotjarId, reddi
108
124
  s.parentNode.insertBefore(b, s);
109
125
  })(window.lintrk);
110
126
  ` }
127
+ }),
128
+ uetTagId && /* @__PURE__ */ jsx(Script, {
129
+ strategy: "afterInteractive",
130
+ id: "uet-tag",
131
+ dangerouslySetInnerHTML: { __html: `
132
+ (function (w, d, t, u, o) {
133
+ w[u] = w[u] || [], o.ts = (new Date).getTime();
134
+ var n = d.createElement(t);
135
+ n.src = "https://bat.bing.net/bat.js?ti=" + o.ti + ("uetq" != u ? "&q=" + u : ""),
136
+ n.async = 1, n.onload = n.onreadystatechange = function() {
137
+ var s = this.readyState;
138
+ s && "loaded" !== s && "complete" !== s ||
139
+ (o.q = w[u], w[u] = new UET(o), w[u].push("pageLoad"),
140
+ n.onload = n.onreadystatechange = null)
141
+ };
142
+ var i = d.getElementsByTagName(t)[0];
143
+ i.parentNode.insertBefore(n, i);
144
+ })(window, document, "script", "uetq", {
145
+ ti: "${uetTagId}",
146
+ enableAutoSpaTracking: true
147
+ });
148
+ ` }
111
149
  }),
112
150
  hotjarId && /* @__PURE__ */ jsx(Script, {
113
151
  id: "hotjar",