@jmp-technologies/analytics 0.1.12 → 0.1.14

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/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,
3
+ buildLandingCaptureInlineScript,
3
4
  createJmpAnalytics,
4
5
  createJmpContentClient,
5
6
  createJmpContentClientFromServerEnv,
@@ -16,10 +17,12 @@ import {
16
17
  isJmpServerContentEnvConfigured,
17
18
  registerJmpContentWrappers,
18
19
  registerJmpTrustCapabilities,
20
+ sendEarlyPageViewBeacon,
19
21
  trackJmpServerPageView
20
- } from "./chunk-ST5BERIA.js";
22
+ } from "./chunk-CCCYRBYK.js";
21
23
  export {
22
24
  DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,
25
+ buildLandingCaptureInlineScript,
23
26
  createJmpAnalytics,
24
27
  createJmpContentClient,
25
28
  createJmpContentClientFromServerEnv,
@@ -36,6 +39,7 @@ export {
36
39
  isJmpServerContentEnvConfigured,
37
40
  registerJmpContentWrappers,
38
41
  registerJmpTrustCapabilities,
42
+ sendEarlyPageViewBeacon,
39
43
  trackJmpServerPageView
40
44
  };
41
45
  //# sourceMappingURL=index.js.map
package/dist/next.cjs CHANGED
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  "use client";
3
+ var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
6
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
9
  var __export = (target, all) => {
8
10
  for (var name in all)
@@ -16,85 +18,214 @@ var __copyProps = (to, from, except, desc) => {
16
18
  }
17
19
  return to;
18
20
  };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
19
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
30
 
21
31
  // src/next.tsx
22
32
  var next_exports = {};
23
33
  __export(next_exports, {
34
+ JmpLandingCaptureScript: () => JmpLandingCaptureScript,
24
35
  JmpNextAnalytics: () => JmpNextAnalytics,
25
36
  JmpNextPageViewTracker: () => JmpNextPageViewTracker,
26
37
  JmpTrackedMailtoLink: () => JmpTrackedMailtoLink,
27
38
  JmpTrackedTelLink: () => JmpTrackedTelLink,
39
+ buildLandingCaptureInlineScript: () => buildLandingCaptureInlineScript,
28
40
  useJmpAnalytics: () => useJmpAnalytics
29
41
  });
30
42
  module.exports = __toCommonJS(next_exports);
43
+ var import_script = __toESM(require("next/script"), 1);
31
44
  var import_react2 = require("react");
32
45
  var import_navigation = require("next/navigation");
33
46
 
34
- // src/index.ts
35
- var DEFAULT_SOURCE = "direct";
36
- var SESSION_STORAGE_KEY = "jmp_analytics_sid";
37
- var LANDING_REFERRER_STORAGE_KEY = "jmp_analytics_landing_referer";
38
- var UTM_SESSION_STORAGE_KEY = "jmp_analytics_utm";
39
- var UTM_PARAM_KEYS = [
47
+ // src/landing-capture.ts
48
+ var JMP_SESSION_ID_KEY = "jmp_analytics_sid";
49
+ var JMP_LANDING_REFERER_KEY = "jmp_analytics_landing_referer";
50
+ var JMP_UTM_SESSION_KEY = "jmp_analytics_utm";
51
+ var JMP_EARLY_BEACON_PATH_KEY = "jmp_analytics_early_beacon_path";
52
+ var JMP_UTM_PARAM_KEYS = [
40
53
  "utm_source",
41
54
  "utm_medium",
42
55
  "utm_campaign",
43
56
  "utm_content",
44
57
  "utm_term"
45
58
  ];
46
- function captureAndGetLandingReferer() {
47
- if (typeof window === "undefined") {
48
- return null;
49
- }
59
+ function refererHostFromUrl(referer) {
60
+ const trimmed = referer.trim();
50
61
  try {
51
- const stored = sessionStorage.getItem(LANDING_REFERRER_STORAGE_KEY);
52
- if (stored?.trim()) {
53
- return stored.trim();
54
- }
55
- const current = document.referrer?.trim();
56
- if (!current) {
62
+ return new URL(trimmed).hostname.replace(/^www\./i, "").toLowerCase();
63
+ } catch {
64
+ try {
65
+ return new URL(`https://${trimmed}`).hostname.replace(/^www\./i, "").toLowerCase();
66
+ } catch {
57
67
  return null;
58
68
  }
59
- sessionStorage.setItem(LANDING_REFERRER_STORAGE_KEY, current);
60
- return current;
61
- } catch {
62
- return document.referrer?.trim() || null;
63
69
  }
64
70
  }
65
- function mergeSessionUtms(metadata) {
66
- if (typeof window === "undefined") {
67
- return;
71
+ function isExternalReferrerUrl(referer, siteHostname) {
72
+ const refHost = refererHostFromUrl(referer);
73
+ if (!refHost) {
74
+ return false;
75
+ }
76
+ const site = siteHostname.replace(/^www\./i, "").toLowerCase();
77
+ return refHost !== site && !refHost.endsWith(`.${site}`);
78
+ }
79
+ function captureLandingAttribution(siteHostname) {
80
+ if (typeof document === "undefined" || typeof window === "undefined") {
81
+ return { landingReferer: null, utm: {} };
68
82
  }
83
+ let landingReferer = null;
84
+ let utm = {};
69
85
  try {
86
+ const storedReferer = sessionStorage.getItem(JMP_LANDING_REFERER_KEY)?.trim();
87
+ const currentReferer = document.referrer?.trim();
88
+ if (currentReferer && isExternalReferrerUrl(currentReferer, siteHostname)) {
89
+ if (!storedReferer) {
90
+ sessionStorage.setItem(JMP_LANDING_REFERER_KEY, currentReferer);
91
+ }
92
+ landingReferer = storedReferer || currentReferer;
93
+ } else if (storedReferer) {
94
+ landingReferer = storedReferer;
95
+ }
70
96
  const params = new URLSearchParams(window.location.search);
71
- let stored = {};
72
- const raw = sessionStorage.getItem(UTM_SESSION_STORAGE_KEY);
73
- if (raw) {
74
- const parsed = JSON.parse(raw);
97
+ const rawUtm = sessionStorage.getItem(JMP_UTM_SESSION_KEY);
98
+ if (rawUtm) {
99
+ const parsed = JSON.parse(rawUtm);
75
100
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
76
- stored = parsed;
101
+ utm = parsed;
77
102
  }
78
103
  }
79
104
  let updated = false;
80
- for (const key of UTM_PARAM_KEYS) {
105
+ for (const key of JMP_UTM_PARAM_KEYS) {
81
106
  const value = params.get(key)?.trim();
82
107
  if (value) {
83
- stored[key] = value;
108
+ utm[key] = value;
84
109
  updated = true;
85
110
  }
86
111
  }
87
112
  if (updated) {
88
- sessionStorage.setItem(UTM_SESSION_STORAGE_KEY, JSON.stringify(stored));
113
+ sessionStorage.setItem(JMP_UTM_SESSION_KEY, JSON.stringify(utm));
89
114
  }
90
- for (const key of UTM_PARAM_KEYS) {
91
- if (metadata[key] === void 0 && stored[key]) {
92
- metadata[key] = stored[key];
93
- }
115
+ } catch {
116
+ }
117
+ return { landingReferer, utm };
118
+ }
119
+ function getOrCreateSessionIdFromStorage() {
120
+ if (typeof window === "undefined" || !globalThis.crypto?.randomUUID) {
121
+ return void 0;
122
+ }
123
+ try {
124
+ let id = sessionStorage.getItem(JMP_SESSION_ID_KEY);
125
+ if (!id) {
126
+ id = crypto.randomUUID();
127
+ sessionStorage.setItem(JMP_SESSION_ID_KEY, id);
94
128
  }
129
+ return id;
130
+ } catch {
131
+ return void 0;
132
+ }
133
+ }
134
+ function wasEarlyBeaconSentForPath(path) {
135
+ if (typeof window === "undefined") {
136
+ return false;
137
+ }
138
+ try {
139
+ return sessionStorage.getItem(JMP_EARLY_BEACON_PATH_KEY) === path;
140
+ } catch {
141
+ return false;
142
+ }
143
+ }
144
+ function markEarlyBeaconSentForPath(path) {
145
+ if (typeof window === "undefined") {
146
+ return;
147
+ }
148
+ try {
149
+ sessionStorage.setItem(JMP_EARLY_BEACON_PATH_KEY, path);
95
150
  } catch {
96
151
  }
97
152
  }
153
+ function sendEarlyPageViewBeacon(args) {
154
+ if (typeof window === "undefined" || typeof fetch === "undefined") {
155
+ return false;
156
+ }
157
+ const path = `${window.location.pathname}${window.location.search}` || "/";
158
+ if (wasEarlyBeaconSentForPath(path)) {
159
+ return false;
160
+ }
161
+ const { landingReferer, utm } = captureLandingAttribution(window.location.hostname);
162
+ const sessionId = getOrCreateSessionIdFromStorage();
163
+ const metadata = {
164
+ pageHostname: window.location.hostname,
165
+ earlyBeacon: true,
166
+ ...utm
167
+ };
168
+ if (landingReferer) {
169
+ metadata.referer = landingReferer;
170
+ }
171
+ const payload = {
172
+ type: "page_view",
173
+ path,
174
+ trackingKey: args.trackingKey,
175
+ ...sessionId ? { sessionId } : {},
176
+ metadata
177
+ };
178
+ const headers = {
179
+ "Content-Type": "application/json",
180
+ "X-JMP-Tracking-Key": args.trackingKey
181
+ };
182
+ if (landingReferer) {
183
+ headers["X-JMP-Visitor-Referer"] = landingReferer;
184
+ }
185
+ markEarlyBeaconSentForPath(path);
186
+ void fetch(`${args.baseUrl.replace(/\/$/, "")}/api/events`, {
187
+ method: "POST",
188
+ headers,
189
+ body: JSON.stringify(payload),
190
+ keepalive: true,
191
+ mode: "cors",
192
+ credentials: "omit"
193
+ }).catch(() => {
194
+ try {
195
+ sessionStorage.removeItem(JMP_EARLY_BEACON_PATH_KEY);
196
+ } catch {
197
+ }
198
+ });
199
+ return true;
200
+ }
201
+ function escapeInlineScript(value) {
202
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
203
+ }
204
+ function buildLandingCaptureInlineScript(args) {
205
+ const baseUrl = escapeInlineScript(args.baseUrl.replace(/\/$/, ""));
206
+ const trackingKey = escapeInlineScript(args.trackingKey);
207
+ return `(function(){try{var k='${trackingKey}',b='${baseUrl}';if(!k||!b)return;var p=location.pathname+location.search||'/';var sentKey='${JMP_EARLY_BEACON_PATH_KEY}';if(sessionStorage.getItem(sentKey)===p)return;var h=location.hostname;var ref=document.referrer||'';function rh(u){try{return new URL(u).hostname.replace(/^www\\./i,'').toLowerCase()}catch(e){try{return new URL('https://'+u).hostname.replace(/^www\\./i,'').toLowerCase()}catch(e2){return null}}}function ext(r){var x=rh(r);if(!x)return false;var s=h.replace(/^www\\./i,'').toLowerCase();return x!==s&&x.slice(-('.'+s).length)!==('.'+s)}if(ref&&ext(ref)&&!sessionStorage.getItem('${JMP_LANDING_REFERER_KEY}'))sessionStorage.setItem('${JMP_LANDING_REFERER_KEY}',ref);var lr=sessionStorage.getItem('${JMP_LANDING_REFERER_KEY}')||'';var sid=sessionStorage.getItem('${JMP_SESSION_ID_KEY}');if(!sid&&crypto&&crypto.randomUUID){sid=crypto.randomUUID();sessionStorage.setItem('${JMP_SESSION_ID_KEY}',sid)}var md={pageHostname:h,earlyBeacon:true};try{var sp=new URLSearchParams(location.search);['utm_source','utm_medium','utm_campaign','utm_content','utm_term'].forEach(function(n){var v=sp.get(n);if(v)md[n]=v})}catch(e){}if(lr)md.referer=lr;var body=JSON.stringify({type:'page_view',path:p,trackingKey:k,sessionId:sid||undefined,metadata:md});var hd={'Content-Type':'application/json','X-JMP-Tracking-Key':k};if(lr)hd['X-JMP-Visitor-Referer']=lr;sessionStorage.setItem(sentKey,p);fetch(b+'/api/events',{method:'POST',headers:hd,body:body,keepalive:true,mode:'cors',credentials:'omit'}).catch(function(){sessionStorage.removeItem(sentKey)})}catch(e){}})();`;
208
+ }
209
+
210
+ // src/index.ts
211
+ var DEFAULT_SOURCE = "direct";
212
+ function captureAndGetLandingReferer() {
213
+ if (typeof window === "undefined") {
214
+ return null;
215
+ }
216
+ return captureLandingAttribution(window.location.hostname).landingReferer;
217
+ }
218
+ function mergeSessionUtms(metadata) {
219
+ if (typeof window === "undefined") {
220
+ return;
221
+ }
222
+ const { utm } = captureLandingAttribution(window.location.hostname);
223
+ for (const key of JMP_UTM_PARAM_KEYS) {
224
+ if (metadata[key] === void 0 && utm[key]) {
225
+ metadata[key] = utm[key];
226
+ }
227
+ }
228
+ }
98
229
  function splitPathAndSearch(pathInput) {
99
230
  const qIndex = pathInput.indexOf("?");
100
231
  if (qIndex === -1) {
@@ -118,21 +249,6 @@ function inferDevice() {
118
249
  }
119
250
  return window.innerWidth < 768 ? "mobile" : "desktop";
120
251
  }
121
- function getOrCreateSessionId() {
122
- if (typeof window === "undefined" || !globalThis.crypto?.randomUUID) {
123
- return void 0;
124
- }
125
- try {
126
- let id = sessionStorage.getItem(SESSION_STORAGE_KEY);
127
- if (!id) {
128
- id = crypto.randomUUID();
129
- sessionStorage.setItem(SESSION_STORAGE_KEY, id);
130
- }
131
- return id;
132
- } catch {
133
- return void 0;
134
- }
135
- }
136
252
  function createJmpAnalytics(config) {
137
253
  const baseUrl = normalizeBaseUrl(config.baseUrl);
138
254
  const resolvedFetch = config.fetchImpl ?? (typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
@@ -156,7 +272,7 @@ function createJmpAnalytics(config) {
156
272
  headers["X-JMP-Visitor-Referer"] = landingReferer;
157
273
  }
158
274
  }
159
- const sid = getOrCreateSessionId();
275
+ const sid = getOrCreateSessionIdFromStorage();
160
276
  const payload = { ...body, ...sid ? { sessionId: sid } : {} };
161
277
  if (config.debug) {
162
278
  console.debug("[@jmp-technologies/analytics]", "POST", url, payload);
@@ -205,7 +321,7 @@ function createJmpAnalytics(config) {
205
321
  "Content-Type": "application/json",
206
322
  "X-JMP-Tracking-Key": config.trackingKey
207
323
  };
208
- const sid = getOrCreateSessionId();
324
+ const sid = getOrCreateSessionIdFromStorage();
209
325
  const payload = { ...body, ...sid ? { sessionId: sid } : {} };
210
326
  if (config.debug) {
211
327
  console.debug("[@jmp-technologies/analytics]", "POST", url, payload);
@@ -277,6 +393,9 @@ function createJmpAnalytics(config) {
277
393
  search = window.location.search.replace(/^\?/, "");
278
394
  }
279
395
  const path = search ? `${pathname}?${search}` : pathname;
396
+ if (wasEarlyBeaconSentForPath(path)) {
397
+ return Promise.resolve();
398
+ }
280
399
  if (shouldDedupePageView(pathname)) {
281
400
  return Promise.resolve();
282
401
  }
@@ -286,7 +405,7 @@ function createJmpAnalytics(config) {
286
405
  const params = new URLSearchParams(
287
406
  search ? `?${search}` : window.location.search
288
407
  );
289
- for (const key of UTM_PARAM_KEYS) {
408
+ for (const key of JMP_UTM_PARAM_KEYS) {
290
409
  const v = params.get(key);
291
410
  if (v && metadata[key] === void 0) {
292
411
  metadata[key] = v;
@@ -394,6 +513,13 @@ function createJmpAnalytics(config) {
394
513
  history.replaceState = replace;
395
514
  };
396
515
  }
516
+ if (typeof window !== "undefined") {
517
+ captureLandingAttribution(window.location.hostname);
518
+ sendEarlyPageViewBeacon({
519
+ baseUrl,
520
+ trackingKey: config.trackingKey
521
+ });
522
+ }
397
523
  return {
398
524
  track,
399
525
  trackPageView,
@@ -478,12 +604,35 @@ function JmpTrackedMailtoLink({
478
604
 
479
605
  // src/next.tsx
480
606
  var import_jsx_runtime2 = require("react/jsx-runtime");
607
+ function JmpLandingCaptureScript({
608
+ baseUrl = process.env.NEXT_PUBLIC_JMP_ANALYTICS_URL,
609
+ trackingKey = process.env.NEXT_PUBLIC_JMP_TRACKING_KEY
610
+ } = {}) {
611
+ const resolvedBase = baseUrl?.trim();
612
+ const resolvedKey = trackingKey?.trim();
613
+ if (!resolvedBase || !resolvedKey) {
614
+ return null;
615
+ }
616
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
617
+ import_script.default,
618
+ {
619
+ id: "jmp-landing-capture",
620
+ strategy: "beforeInteractive",
621
+ dangerouslySetInnerHTML: {
622
+ __html: buildLandingCaptureInlineScript({
623
+ baseUrl: resolvedBase,
624
+ trackingKey: resolvedKey
625
+ })
626
+ }
627
+ }
628
+ );
629
+ }
481
630
  function JmpNextPageViewTrackerInner() {
482
631
  const pathname = (0, import_navigation.usePathname)();
483
632
  const searchParams = (0, import_navigation.useSearchParams)();
484
633
  const query = searchParams?.toString() ?? "";
485
634
  const path = query ? `${pathname}?${query}` : pathname;
486
- (0, import_react2.useEffect)(() => {
635
+ (0, import_react2.useLayoutEffect)(() => {
487
636
  const client = getOrCreateJmpAnalyticsFromBrowserEnv();
488
637
  void client?.trackPageView({ path });
489
638
  }, [path]);
@@ -493,14 +642,19 @@ function JmpNextPageViewTracker() {
493
642
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react2.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JmpNextPageViewTrackerInner, {}) });
494
643
  }
495
644
  function JmpNextAnalytics() {
496
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JmpNextPageViewTracker, {});
645
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
646
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JmpLandingCaptureScript, {}),
647
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JmpNextPageViewTracker, {})
648
+ ] });
497
649
  }
498
650
  // Annotate the CommonJS export names for ESM import in node:
499
651
  0 && (module.exports = {
652
+ JmpLandingCaptureScript,
500
653
  JmpNextAnalytics,
501
654
  JmpNextPageViewTracker,
502
655
  JmpTrackedMailtoLink,
503
656
  JmpTrackedTelLink,
657
+ buildLandingCaptureInlineScript,
504
658
  useJmpAnalytics
505
659
  });
506
660
  //# sourceMappingURL=next.cjs.map