@jmp-technologies/analytics 0.1.22 → 0.1.24

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 (73) hide show
  1. package/dist/astro/client.cjs +36 -18
  2. package/dist/astro/client.cjs.map +1 -1
  3. package/dist/astro/client.js +2 -2
  4. package/dist/astro/content.cjs.map +1 -1
  5. package/dist/astro/content.d.cts +1 -1
  6. package/dist/astro/content.d.ts +1 -1
  7. package/dist/astro/content.js +3 -3
  8. package/dist/astro/integration.cjs +1 -1
  9. package/dist/astro/integration.cjs.map +1 -1
  10. package/dist/astro/integration.js +3 -3
  11. package/dist/astro/middleware.cjs +39 -0
  12. package/dist/astro/middleware.cjs.map +1 -1
  13. package/dist/astro/middleware.d.cts +1 -1
  14. package/dist/astro/middleware.d.ts +1 -1
  15. package/dist/astro/middleware.js +2 -2
  16. package/dist/astro/register.cjs +1 -1
  17. package/dist/astro/register.cjs.map +1 -1
  18. package/dist/astro/register.js +3 -3
  19. package/dist/{chunk-AHRVNEFV.js → chunk-2AUF7ENL.js} +42 -22
  20. package/dist/chunk-2AUF7ENL.js.map +1 -0
  21. package/dist/chunk-7MLPSL6T.js +59 -0
  22. package/dist/chunk-7MLPSL6T.js.map +1 -0
  23. package/dist/chunk-CDORZ2YJ.js +154 -0
  24. package/dist/chunk-CDORZ2YJ.js.map +1 -0
  25. package/dist/{chunk-3U2BSIYX.js → chunk-GPPQ3TYI.js} +3 -3
  26. package/dist/{chunk-4OOWA55U.js → chunk-MOX7RJUU.js} +2 -1
  27. package/dist/{chunk-4OOWA55U.js.map → chunk-MOX7RJUU.js.map} +1 -1
  28. package/dist/{chunk-IYFVJ2EY.js → chunk-WCI6VOVY.js} +3 -3
  29. package/dist/{chunk-DQ6A6HTY.js → chunk-ZP4L5DCM.js} +4 -2
  30. package/dist/chunk-ZP4L5DCM.js.map +1 -0
  31. package/dist/index.cjs +61 -42
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +3 -3
  34. package/dist/index.d.ts +3 -3
  35. package/dist/index.js +2 -2
  36. package/dist/next/content.cjs.map +1 -1
  37. package/dist/next/content.d.cts +1 -1
  38. package/dist/next/content.d.ts +1 -1
  39. package/dist/next/content.js +3 -3
  40. package/dist/next/instrumentation.cjs +1 -1
  41. package/dist/next/instrumentation.cjs.map +1 -1
  42. package/dist/next/instrumentation.js +2 -2
  43. package/dist/next/middleware.cjs +39 -0
  44. package/dist/next/middleware.cjs.map +1 -1
  45. package/dist/next/middleware.d.cts +1 -1
  46. package/dist/next/middleware.d.ts +1 -1
  47. package/dist/next/middleware.js +2 -2
  48. package/dist/next/site-root-server.cjs +1001 -0
  49. package/dist/next/site-root-server.cjs.map +1 -0
  50. package/dist/next/site-root-server.d.cts +16 -0
  51. package/dist/next/site-root-server.d.ts +16 -0
  52. package/dist/next/site-root-server.js +32 -0
  53. package/dist/next/site-root-server.js.map +1 -0
  54. package/dist/next.cjs +94 -36
  55. package/dist/next.cjs.map +1 -1
  56. package/dist/next.d.cts +13 -5
  57. package/dist/next.d.ts +13 -5
  58. package/dist/next.js +11 -108
  59. package/dist/next.js.map +1 -1
  60. package/dist/react.cjs +46 -19
  61. package/dist/react.cjs.map +1 -1
  62. package/dist/react.js +3 -3
  63. package/dist/{site-config-BRuZWGSW.d.cts → site-config-BXs91Yex.d.cts} +5 -0
  64. package/dist/{site-config-hkGMSeT7.d.ts → site-config-DpMgrfeN.d.ts} +5 -0
  65. package/dist/{track-page-view-BLS0YfL-.d.ts → track-page-view-CcPlzQNB.d.ts} +3 -0
  66. package/dist/{track-page-view-DfCyWHqe.d.cts → track-page-view-Cr8I4Unm.d.cts} +3 -0
  67. package/package.json +7 -2
  68. package/dist/chunk-AHRVNEFV.js.map +0 -1
  69. package/dist/chunk-DQ6A6HTY.js.map +0 -1
  70. package/dist/chunk-TEFKM3GW.js +0 -19
  71. package/dist/chunk-TEFKM3GW.js.map +0 -1
  72. /package/dist/{chunk-3U2BSIYX.js.map → chunk-GPPQ3TYI.js.map} +0 -0
  73. /package/dist/{chunk-IYFVJ2EY.js.map → chunk-WCI6VOVY.js.map} +0 -0
@@ -0,0 +1,1001 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/next/site-root-server.tsx
31
+ var site_root_server_exports = {};
32
+ __export(site_root_server_exports, {
33
+ JmpSiteRootServer: () => JmpSiteRootServer
34
+ });
35
+ module.exports = __toCommonJS(site_root_server_exports);
36
+
37
+ // src/env-public.ts
38
+ function firstDefined(...values) {
39
+ for (const v of values) {
40
+ if (v) return v;
41
+ }
42
+ return void 0;
43
+ }
44
+ function readJmpPublicEnvFromProcessEnv() {
45
+ if (typeof process === "undefined" || !process.env) {
46
+ return {};
47
+ }
48
+ const { env } = process;
49
+ const analyticsUrl = firstDefined(
50
+ env.NEXT_PUBLIC_JMP_ANALYTICS_URL,
51
+ env.PUBLIC_JMP_ANALYTICS_URL
52
+ );
53
+ const trackingKey = firstDefined(
54
+ env.NEXT_PUBLIC_JMP_TRACKING_KEY,
55
+ env.PUBLIC_JMP_TRACKING_KEY
56
+ );
57
+ const requireConsentRaw = firstDefined(
58
+ env.NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT,
59
+ env.PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT
60
+ );
61
+ const autoContactRaw = firstDefined(
62
+ env.NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS,
63
+ env.PUBLIC_JMP_AUTO_CONTACT_LINKS
64
+ );
65
+ return {
66
+ analyticsUrl,
67
+ trackingKey,
68
+ requireConsent: requireConsentRaw === "true",
69
+ autoContactLinks: autoContactRaw === "true"
70
+ };
71
+ }
72
+ function readJmpPublicEnvFromProcess(overrides) {
73
+ const fromEnv = readJmpPublicEnvFromProcessEnv();
74
+ return {
75
+ analyticsUrl: firstDefined(overrides?.analyticsUrl, fromEnv.analyticsUrl),
76
+ trackingKey: firstDefined(overrides?.trackingKey, fromEnv.trackingKey),
77
+ requireConsent: overrides?.requireConsent ?? fromEnv.requireConsent,
78
+ autoContactLinks: overrides?.autoContactLinks ?? fromEnv.autoContactLinks
79
+ };
80
+ }
81
+
82
+ // src/site-config.ts
83
+ var registeredSite = null;
84
+ function getJmpSiteDefinition() {
85
+ return registeredSite;
86
+ }
87
+
88
+ // src/next.tsx
89
+ var import_script = __toESM(require("next/script"), 1);
90
+ var import_react2 = require("react");
91
+ var import_navigation = require("next/navigation");
92
+
93
+ // src/landing-capture.ts
94
+ var JMP_SESSION_ID_KEY = "jmp_analytics_sid";
95
+ var JMP_LANDING_REFERER_KEY = "jmp_analytics_landing_referer";
96
+ var JMP_LANDING_REFERER_COOKIE = "jmp_lr";
97
+ var JMP_UTM_SESSION_KEY = "jmp_analytics_utm";
98
+ var LANDING_REFERER_COOKIE_MAX_AGE_SEC = 30 * 60;
99
+ var JMP_UTM_PARAM_KEYS = [
100
+ "utm_source",
101
+ "utm_medium",
102
+ "utm_campaign",
103
+ "utm_content",
104
+ "utm_term"
105
+ ];
106
+ function refererHostFromUrl(referer) {
107
+ const trimmed = referer.trim();
108
+ try {
109
+ return new URL(trimmed).hostname.replace(/^www\./i, "").toLowerCase();
110
+ } catch {
111
+ try {
112
+ return new URL(`https://${trimmed}`).hostname.replace(/^www\./i, "").toLowerCase();
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
117
+ }
118
+ function isExternalReferrerUrl(referer, siteHostname) {
119
+ const refHost = refererHostFromUrl(referer);
120
+ if (!refHost) {
121
+ return false;
122
+ }
123
+ const site = siteHostname.replace(/^www\./i, "").toLowerCase();
124
+ return refHost !== site && !refHost.endsWith(`.${site}`);
125
+ }
126
+ function registrableSiteDomain(siteHostname) {
127
+ const site = siteHostname.replace(/^www\./i, "").toLowerCase();
128
+ if (!site || site === "localhost" || site.endsWith(".local")) {
129
+ return null;
130
+ }
131
+ if (!site.includes(".")) {
132
+ return null;
133
+ }
134
+ return site;
135
+ }
136
+ function readLandingRefererCookie(siteHostname) {
137
+ if (typeof document === "undefined") {
138
+ return null;
139
+ }
140
+ const apex = registrableSiteDomain(siteHostname);
141
+ if (!apex) {
142
+ return null;
143
+ }
144
+ const prefix = `${JMP_LANDING_REFERER_COOKIE}=`;
145
+ for (const part of document.cookie.split(";")) {
146
+ const trimmed = part.trim();
147
+ if (!trimmed.startsWith(prefix)) {
148
+ continue;
149
+ }
150
+ try {
151
+ const value = decodeURIComponent(trimmed.slice(prefix.length)).trim();
152
+ return value.length > 0 ? value : null;
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+ return null;
158
+ }
159
+ function writeLandingRefererCookie(referer, siteHostname) {
160
+ if (typeof document === "undefined") {
161
+ return;
162
+ }
163
+ const apex = registrableSiteDomain(siteHostname);
164
+ if (!apex) {
165
+ return;
166
+ }
167
+ const value = encodeURIComponent(referer.trim().slice(0, 2048));
168
+ const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
169
+ document.cookie = `${JMP_LANDING_REFERER_COOKIE}=${value}; Domain=.${apex}; Path=/; Max-Age=${LANDING_REFERER_COOKIE_MAX_AGE_SEC}; SameSite=Lax${secure}`;
170
+ }
171
+ function captureLandingAttribution(siteHostname) {
172
+ if (typeof document === "undefined" || typeof window === "undefined") {
173
+ return { landingReferer: null, utm: {} };
174
+ }
175
+ let landingReferer = null;
176
+ let utm = {};
177
+ try {
178
+ const storedReferer = sessionStorage.getItem(JMP_LANDING_REFERER_KEY)?.trim() || null;
179
+ const cookieReferer = readLandingRefererCookie(siteHostname);
180
+ const preservedReferer = storedReferer || cookieReferer;
181
+ const currentReferer = document.referrer?.trim() || null;
182
+ if (currentReferer && isExternalReferrerUrl(currentReferer, siteHostname)) {
183
+ sessionStorage.setItem(JMP_LANDING_REFERER_KEY, currentReferer);
184
+ writeLandingRefererCookie(currentReferer, siteHostname);
185
+ landingReferer = currentReferer;
186
+ } else if (preservedReferer) {
187
+ landingReferer = preservedReferer;
188
+ if (!storedReferer) {
189
+ sessionStorage.setItem(JMP_LANDING_REFERER_KEY, preservedReferer);
190
+ }
191
+ }
192
+ const params = new URLSearchParams(window.location.search);
193
+ const rawUtm = sessionStorage.getItem(JMP_UTM_SESSION_KEY);
194
+ if (rawUtm) {
195
+ const parsed = JSON.parse(rawUtm);
196
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
197
+ utm = parsed;
198
+ }
199
+ }
200
+ let updated = false;
201
+ for (const key of JMP_UTM_PARAM_KEYS) {
202
+ const value = params.get(key)?.trim();
203
+ if (value) {
204
+ utm[key] = value;
205
+ updated = true;
206
+ }
207
+ }
208
+ if (updated) {
209
+ sessionStorage.setItem(JMP_UTM_SESSION_KEY, JSON.stringify(utm));
210
+ }
211
+ } catch {
212
+ }
213
+ return { landingReferer, utm };
214
+ }
215
+ function getOrCreateSessionIdFromStorage() {
216
+ if (typeof window === "undefined" || !globalThis.crypto?.randomUUID) {
217
+ return void 0;
218
+ }
219
+ try {
220
+ let id = sessionStorage.getItem(JMP_SESSION_ID_KEY);
221
+ if (!id) {
222
+ id = crypto.randomUUID();
223
+ sessionStorage.setItem(JMP_SESSION_ID_KEY, id);
224
+ }
225
+ return id;
226
+ } catch {
227
+ return void 0;
228
+ }
229
+ }
230
+ function buildLandingCaptureInlineScript() {
231
+ const cookieMaxAge = String(LANDING_REFERER_COOKIE_MAX_AGE_SEC);
232
+ return `(function(){try{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)}function apexDom(hn){var s=hn.replace(/^www\\./i,'').toLowerCase();if(!s||s==='localhost'||/\\.local$/i.test(s)||s.indexOf('.')<0)return null;return s}function readCk(hn){var apex=apexDom(hn);if(!apex)return '';var pre='${JMP_LANDING_REFERER_COOKIE}=';var parts=document.cookie.split(';');for(var i=0;i<parts.length;i++){var t=parts[i].trim();if(t.indexOf(pre)===0){try{return decodeURIComponent(t.slice(pre.length)).trim()}catch(e){return ''}}}return ''}function writeCk(r,hn){var apex=apexDom(hn);if(!apex)return;var sec=location.protocol==='https:'?'; Secure':'';document.cookie='${JMP_LANDING_REFERER_COOKIE}='+encodeURIComponent(r.slice(0,2048))+'; Domain=.'+apex+'; Path=/; Max-Age=${cookieMaxAge}; SameSite=Lax'+sec}var stored=sessionStorage.getItem('${JMP_LANDING_REFERER_KEY}')||'';var ck=readCk(h);var preserved=stored||ck;var lr='';if(ref&&ext(ref)){sessionStorage.setItem('${JMP_LANDING_REFERER_KEY}',ref);writeCk(ref,h);lr=ref}else{lr=preserved||'';if(lr&&!stored)sessionStorage.setItem('${JMP_LANDING_REFERER_KEY}',lr)}var sid=sessionStorage.getItem('${JMP_SESSION_ID_KEY}');if(!sid&&crypto&&crypto.randomUUID){sid=crypto.randomUUID();sessionStorage.setItem('${JMP_SESSION_ID_KEY}',sid)}try{var sp=new URLSearchParams(location.search);var utm={};var updated=false;['utm_source','utm_medium','utm_campaign','utm_content','utm_term'].forEach(function(n){var v=sp.get(n);if(v){utm[n]=v;updated=true}});if(updated)sessionStorage.setItem('${JMP_UTM_SESSION_KEY}',JSON.stringify(utm))}catch(e){}}catch(e){}})();`;
233
+ }
234
+
235
+ // src/constants.ts
236
+ var JMP_TRACKING_CONSENT_STORAGE_KEY = "jmp_analytics_consent_v1";
237
+ var DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS = 2500;
238
+
239
+ // src/tracker/page-view-dedupe.ts
240
+ var JMP_PAGE_VIEW_DEDUPE_STORAGE_KEY = "jmp_analytics_last_pv";
241
+ var moduleLastPath = "";
242
+ var moduleLastAt = 0;
243
+ function shouldDedupeBrowserPageView(pathname, dedupeMs, storage = null) {
244
+ if (dedupeMs <= 0) {
245
+ return false;
246
+ }
247
+ const now = Date.now();
248
+ if (pathname === moduleLastPath && now - moduleLastAt < dedupeMs) {
249
+ return true;
250
+ }
251
+ try {
252
+ const raw = storage?.getItem(JMP_PAGE_VIEW_DEDUPE_STORAGE_KEY);
253
+ if (raw) {
254
+ const prev = JSON.parse(raw);
255
+ if (prev.path === pathname && now - prev.at < dedupeMs) {
256
+ return true;
257
+ }
258
+ }
259
+ storage?.setItem(
260
+ JMP_PAGE_VIEW_DEDUPE_STORAGE_KEY,
261
+ JSON.stringify({ path: pathname, at: now })
262
+ );
263
+ } catch {
264
+ }
265
+ moduleLastPath = pathname;
266
+ moduleLastAt = now;
267
+ return false;
268
+ }
269
+
270
+ // src/contact-link-classifier.ts
271
+ var BOOKING_LINK_HOSTS = [
272
+ "calendly.com",
273
+ "cal.com",
274
+ "acuityscheduling.com",
275
+ "calendar.google.com",
276
+ "calendar.app.google",
277
+ "bookings.microsoft.com",
278
+ "outlook.office.com",
279
+ "outlook.office365.com",
280
+ "book.ms",
281
+ "setmore.com",
282
+ "simplybook.me",
283
+ "oncehub.com",
284
+ "scheduleonce.com",
285
+ "youcanbook.me",
286
+ "savvycal.com",
287
+ "tidycal.com",
288
+ "zohobookings.com",
289
+ "square.site",
290
+ "squareup.com",
291
+ "meetings.hubspot.com",
292
+ "jobber.com",
293
+ "housecallpro.com",
294
+ "servicetitan.com",
295
+ "thryv.com",
296
+ "honeybook.com",
297
+ "dubsado.com",
298
+ "fresha.com",
299
+ "booksy.com",
300
+ "vagaro.com",
301
+ "mindbodyonline.com",
302
+ "mindbody.io",
303
+ "janeapp.com",
304
+ "cliniko.com",
305
+ "practicebetter.io"
306
+ ];
307
+ var WHATSAPP_HOSTS = ["wa.me", "api.whatsapp.com", "whatsapp.com"];
308
+ function hostnameMatches(host, pattern) {
309
+ const h = host.toLowerCase();
310
+ const p = pattern.toLowerCase();
311
+ return h === p || h.endsWith(`.${p}`);
312
+ }
313
+ function isMapsHost(host, pathname) {
314
+ if (hostnameMatches(host, "maps.apple.com")) {
315
+ return true;
316
+ }
317
+ if (hostnameMatches(host, "maps.app.goo.gl")) {
318
+ return true;
319
+ }
320
+ if (hostnameMatches(host, "google.com") || hostnameMatches(host, "google.co")) {
321
+ return pathname === "/maps" || pathname.startsWith("/maps/") || pathname.startsWith("/local");
322
+ }
323
+ return false;
324
+ }
325
+ function isWhatsAppHost(host, pathname) {
326
+ for (const pattern of WHATSAPP_HOSTS) {
327
+ if (!hostnameMatches(host, pattern)) {
328
+ continue;
329
+ }
330
+ if (pattern === "whatsapp.com") {
331
+ return pathname.startsWith("/send");
332
+ }
333
+ return true;
334
+ }
335
+ return false;
336
+ }
337
+ function isBookingHost(host) {
338
+ for (const pattern of BOOKING_LINK_HOSTS) {
339
+ if (hostnameMatches(host, pattern)) {
340
+ return true;
341
+ }
342
+ }
343
+ return false;
344
+ }
345
+ function classifyContactHref(href) {
346
+ const raw = href.trim();
347
+ if (!raw || raw.startsWith("#") || raw.startsWith("javascript:")) {
348
+ return null;
349
+ }
350
+ const lower = raw.toLowerCase();
351
+ if (lower.startsWith("tel:")) {
352
+ return { kind: "call" };
353
+ }
354
+ if (lower.startsWith("mailto:")) {
355
+ return { kind: "email" };
356
+ }
357
+ if (lower.startsWith("sms:") || lower.startsWith("smsto:")) {
358
+ return { kind: "contact_click", channel: "sms", linkHost: null };
359
+ }
360
+ if (lower.startsWith("geo:")) {
361
+ return { kind: "contact_click", channel: "maps", linkHost: null };
362
+ }
363
+ if (raw.startsWith("/") || raw.startsWith("?")) {
364
+ return null;
365
+ }
366
+ let url;
367
+ try {
368
+ url = new URL(raw);
369
+ } catch {
370
+ return null;
371
+ }
372
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
373
+ return null;
374
+ }
375
+ const host = url.hostname.toLowerCase();
376
+ const pathname = url.pathname.toLowerCase();
377
+ if (isWhatsAppHost(host, pathname)) {
378
+ return { kind: "contact_click", channel: "whatsapp", linkHost: host };
379
+ }
380
+ if (isMapsHost(host, pathname)) {
381
+ return { kind: "contact_click", channel: "maps", linkHost: host };
382
+ }
383
+ if (isBookingHost(host)) {
384
+ return { kind: "contact_click", channel: "booking", linkHost: host };
385
+ }
386
+ return null;
387
+ }
388
+
389
+ // src/consent.ts
390
+ var moduleTrackingConsentGranted = true;
391
+ var trackingConsentListeners = /* @__PURE__ */ new Set();
392
+ function readStoredTrackingConsent() {
393
+ if (typeof window === "undefined") {
394
+ return false;
395
+ }
396
+ try {
397
+ return localStorage.getItem(JMP_TRACKING_CONSENT_STORAGE_KEY) === "granted";
398
+ } catch {
399
+ return false;
400
+ }
401
+ }
402
+ function initJmpTrackingConsent(requireConsent) {
403
+ moduleTrackingConsentGranted = requireConsent ? readStoredTrackingConsent() : true;
404
+ }
405
+ function isJmpTrackingConsentGranted() {
406
+ return moduleTrackingConsentGranted;
407
+ }
408
+ function subscribeJmpTrackingConsent(listener) {
409
+ trackingConsentListeners.add(listener);
410
+ return () => {
411
+ trackingConsentListeners.delete(listener);
412
+ };
413
+ }
414
+ function setJmpTrackingConsent(granted) {
415
+ if (moduleTrackingConsentGranted === granted) {
416
+ return;
417
+ }
418
+ moduleTrackingConsentGranted = granted;
419
+ if (typeof window !== "undefined") {
420
+ try {
421
+ localStorage.setItem(
422
+ JMP_TRACKING_CONSENT_STORAGE_KEY,
423
+ granted ? "granted" : "denied"
424
+ );
425
+ } catch {
426
+ }
427
+ if (granted) {
428
+ captureLandingAttribution(window.location.hostname);
429
+ }
430
+ }
431
+ for (const listener of trackingConsentListeners) {
432
+ listener();
433
+ }
434
+ }
435
+
436
+ // src/http.ts
437
+ function responseLooksLikeHtml(body) {
438
+ const s = body.trimStart();
439
+ return s.startsWith("<!") || s.toLowerCase().startsWith("<html") || s.startsWith("<") && /<html[\s>]/i.test(s.slice(0, 500));
440
+ }
441
+ function normalizeBaseUrl(baseUrl) {
442
+ return baseUrl.replace(/\/$/, "");
443
+ }
444
+ function resolveFetch(fetchImpl, context) {
445
+ const resolved = fetchImpl ?? (typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
446
+ if (!resolved) {
447
+ throw new Error(`${context}: fetch is not available; pass fetchImpl in config`);
448
+ }
449
+ return resolved;
450
+ }
451
+ function formatApiErrorMessage(status, text, prefix) {
452
+ if (responseLooksLikeHtml(text)) {
453
+ return `${prefix} HTTP ${status} \u2014 response was HTML (not the JSON API). Check baseUrl points at the JMP Analytics dashboard.`;
454
+ }
455
+ let parsed = {};
456
+ try {
457
+ parsed = JSON.parse(text);
458
+ } catch {
459
+ parsed = { error: text };
460
+ }
461
+ const detail = parsed.error ?? text;
462
+ return [
463
+ `${prefix} HTTP ${status}`,
464
+ parsed.code ? `[${parsed.code}]` : null,
465
+ detail,
466
+ parsed.hint ? `Hint: ${parsed.hint}` : null,
467
+ parsed.expectedDomain ? `Domain in JMP admin: ${parsed.expectedDomain}` : null
468
+ ].filter(Boolean).join(" \u2014 ");
469
+ }
470
+
471
+ // src/tracker/helpers.ts
472
+ function captureAndGetLandingReferer() {
473
+ if (typeof window === "undefined") {
474
+ return null;
475
+ }
476
+ return captureLandingAttribution(window.location.hostname).landingReferer;
477
+ }
478
+ function mergeSessionUtms(metadata) {
479
+ if (typeof window === "undefined") {
480
+ return;
481
+ }
482
+ const { utm } = captureLandingAttribution(window.location.hostname);
483
+ for (const key of JMP_UTM_PARAM_KEYS) {
484
+ if (metadata[key] === void 0 && utm[key]) {
485
+ metadata[key] = utm[key];
486
+ }
487
+ }
488
+ }
489
+ function splitPathAndSearch(pathInput) {
490
+ const qIndex = pathInput.indexOf("?");
491
+ if (qIndex === -1) {
492
+ return { pathname: pathInput || "/", search: "" };
493
+ }
494
+ return {
495
+ pathname: pathInput.slice(0, qIndex) || "/",
496
+ search: pathInput.slice(qIndex + 1)
497
+ };
498
+ }
499
+ function inferDevice() {
500
+ if (typeof window === "undefined") {
501
+ return "unknown";
502
+ }
503
+ return window.innerWidth < 768 ? "mobile" : "desktop";
504
+ }
505
+
506
+ // src/tracker/create-jmp-analytics.ts
507
+ function createJmpAnalytics(config) {
508
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
509
+ const fetchFn = resolveFetch(config.fetchImpl, "createJmpAnalytics");
510
+ initJmpTrackingConsent(config.requireConsent);
511
+ function canSend() {
512
+ return isJmpTrackingConsentGranted();
513
+ }
514
+ async function send(body) {
515
+ if (!canSend()) {
516
+ return;
517
+ }
518
+ const url = `${baseUrl}/api/events`;
519
+ const headers = {
520
+ "Content-Type": "application/json",
521
+ "X-JMP-Tracking-Key": config.trackingKey
522
+ };
523
+ if (body.type === "page_view" && typeof window !== "undefined") {
524
+ const landingReferer = captureAndGetLandingReferer();
525
+ if (landingReferer) {
526
+ headers["X-JMP-Visitor-Referer"] = landingReferer;
527
+ }
528
+ }
529
+ const sid = getOrCreateSessionIdFromStorage();
530
+ const payload = { ...body, ...sid ? { sessionId: sid } : {} };
531
+ if (config.debug) {
532
+ console.debug("[@jmp-technologies/analytics]", "POST", url, payload);
533
+ }
534
+ const res = await fetchFn(url, {
535
+ method: "POST",
536
+ headers,
537
+ body: JSON.stringify(payload),
538
+ keepalive: true
539
+ });
540
+ if (!res.ok) {
541
+ const text = await res.text();
542
+ const message = formatApiErrorMessage(res.status, text, "JMP Analytics");
543
+ if (config.debug) {
544
+ console.debug("[@jmp-technologies/analytics]", "error response", { status: res.status, raw: text });
545
+ }
546
+ throw new Error(message);
547
+ }
548
+ }
549
+ async function sendLead(body) {
550
+ if (!canSend()) {
551
+ return;
552
+ }
553
+ const url = `${baseUrl}/api/leads`;
554
+ const headers = {
555
+ "Content-Type": "application/json",
556
+ "X-JMP-Tracking-Key": config.trackingKey
557
+ };
558
+ const sid = getOrCreateSessionIdFromStorage();
559
+ const payload = { ...body, ...sid ? { sessionId: sid } : {} };
560
+ if (config.debug) {
561
+ console.debug("[@jmp-technologies/analytics]", "POST", url, payload);
562
+ }
563
+ const res = await fetchFn(url, {
564
+ method: "POST",
565
+ headers,
566
+ body: JSON.stringify(payload),
567
+ keepalive: true
568
+ });
569
+ if (!res.ok) {
570
+ const text = await res.text();
571
+ const message = formatApiErrorMessage(res.status, text, "JMP Analytics");
572
+ if (config.debug) {
573
+ console.debug("[@jmp-technologies/analytics]", "error response", { status: res.status, raw: text });
574
+ }
575
+ throw new Error(message);
576
+ }
577
+ }
578
+ function trackPageView(options) {
579
+ if (!canSend()) {
580
+ return Promise.resolve();
581
+ }
582
+ const pathFromOptions = options?.path?.trim();
583
+ let pathname = "/";
584
+ let search = "";
585
+ if (pathFromOptions) {
586
+ const split = splitPathAndSearch(pathFromOptions);
587
+ pathname = split.pathname;
588
+ search = split.search;
589
+ } else if (typeof window !== "undefined") {
590
+ pathname = window.location.pathname;
591
+ search = window.location.search.replace(/^\?/, "");
592
+ }
593
+ if (!search && typeof window !== "undefined") {
594
+ search = window.location.search.replace(/^\?/, "");
595
+ }
596
+ const path = search ? `${pathname}?${search}` : pathname;
597
+ const dedupeMs = config.pageViewDedupeMs ?? DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS;
598
+ const storage = typeof window !== "undefined" ? window.sessionStorage : null;
599
+ if (shouldDedupeBrowserPageView(pathname, dedupeMs, storage)) {
600
+ return Promise.resolve();
601
+ }
602
+ const metadata = options?.metadata && typeof options.metadata === "object" ? { ...options.metadata } : {};
603
+ if (typeof window !== "undefined") {
604
+ metadata.pageHostname = window.location.hostname;
605
+ const params = new URLSearchParams(
606
+ search ? `?${search}` : window.location.search
607
+ );
608
+ for (const key of JMP_UTM_PARAM_KEYS) {
609
+ const v = params.get(key);
610
+ if (v && metadata[key] === void 0) {
611
+ metadata[key] = v;
612
+ }
613
+ }
614
+ mergeSessionUtms(metadata);
615
+ const landingReferer = captureAndGetLandingReferer();
616
+ if (landingReferer && metadata.referer === void 0) {
617
+ metadata.referer = landingReferer;
618
+ }
619
+ }
620
+ return send({
621
+ type: "page_view",
622
+ path: path || "/",
623
+ device: options?.device ?? inferDevice(),
624
+ country: options?.country,
625
+ metadata
626
+ });
627
+ }
628
+ function trackFormSubmit(path, metadata) {
629
+ return send({
630
+ type: "form_submit",
631
+ path: path || "/",
632
+ metadata: metadata ?? {}
633
+ });
634
+ }
635
+ function trackCallClick(path, metadata) {
636
+ return send({
637
+ type: "call_click",
638
+ path: path || "/",
639
+ metadata: metadata ?? {}
640
+ });
641
+ }
642
+ function trackEmailClick(path, metadata) {
643
+ return send({
644
+ type: "email_click",
645
+ path: path || "/",
646
+ metadata: metadata ?? {}
647
+ });
648
+ }
649
+ function trackContactClick(path, options) {
650
+ const metadata = {
651
+ ...options.metadata && typeof options.metadata === "object" ? options.metadata : {},
652
+ channel: options.channel
653
+ };
654
+ if (options.linkHost) {
655
+ metadata.linkHost = options.linkHost;
656
+ }
657
+ return send({
658
+ type: "contact_click",
659
+ path: path || "/",
660
+ metadata
661
+ });
662
+ }
663
+ function trackLead(options) {
664
+ const path = options?.path ?? (typeof window !== "undefined" ? window.location.pathname : "/");
665
+ const body = { path: path || "/" };
666
+ if (options?.name) body.name = options.name;
667
+ if (options?.email) body.email = options.email;
668
+ if (options?.phone) body.phone = options.phone;
669
+ if (options?.company) body.company = options.company;
670
+ if (options?.website) body.website = options.website;
671
+ if (options?.subject) body.subject = options.subject;
672
+ if (options?.message) body.message = options.message;
673
+ if (options?.metadata && typeof options.metadata === "object") {
674
+ body.metadata = options.metadata;
675
+ }
676
+ if (options?.sessionId) body.sessionId = options.sessionId;
677
+ return sendLead(body);
678
+ }
679
+ function track(type, payload) {
680
+ if (type === "page_view") {
681
+ return trackPageView({
682
+ path: payload.path,
683
+ source: payload.source,
684
+ device: payload.device,
685
+ country: payload.country,
686
+ metadata: payload.metadata
687
+ });
688
+ }
689
+ if (type === "form_submit") {
690
+ return trackFormSubmit(payload.path, payload.metadata);
691
+ }
692
+ if (type === "call_click") {
693
+ return trackCallClick(payload.path, payload.metadata);
694
+ }
695
+ if (type === "email_click") {
696
+ return trackEmailClick(payload.path, payload.metadata);
697
+ }
698
+ const meta = payload.metadata ?? {};
699
+ const channel = typeof meta.channel === "string" ? meta.channel : "";
700
+ const linkHost = typeof meta.linkHost === "string" ? meta.linkHost : null;
701
+ if (channel !== "sms" && channel !== "whatsapp" && channel !== "maps" && channel !== "booking") {
702
+ return Promise.resolve();
703
+ }
704
+ return trackContactClick(payload.path, {
705
+ channel,
706
+ linkHost,
707
+ metadata: meta
708
+ });
709
+ }
710
+ function subscribeToSpaNavigation() {
711
+ if (typeof window === "undefined") {
712
+ return () => {
713
+ };
714
+ }
715
+ let lastPath = window.location.pathname;
716
+ const run = () => {
717
+ const path = window.location.pathname;
718
+ if (path !== lastPath) {
719
+ lastPath = path;
720
+ void trackPageView({ path });
721
+ }
722
+ };
723
+ window.addEventListener("popstate", run);
724
+ const push = history.pushState.bind(history);
725
+ const replace = history.replaceState.bind(history);
726
+ history.pushState = (...args) => {
727
+ const r = push(...args);
728
+ queueMicrotask(run);
729
+ return r;
730
+ };
731
+ history.replaceState = (...args) => {
732
+ const r = replace(...args);
733
+ queueMicrotask(run);
734
+ return r;
735
+ };
736
+ return () => {
737
+ window.removeEventListener("popstate", run);
738
+ history.pushState = push;
739
+ history.replaceState = replace;
740
+ };
741
+ }
742
+ let lastContactClickKey = "";
743
+ let lastContactClickAt = 0;
744
+ function subscribeContactLinkAutoTrack() {
745
+ if (typeof window === "undefined" || typeof document === "undefined") {
746
+ return () => {
747
+ };
748
+ }
749
+ const handler = (event) => {
750
+ if (!canSend()) {
751
+ return;
752
+ }
753
+ const target = event.target;
754
+ if (!(target instanceof Element)) {
755
+ return;
756
+ }
757
+ const anchor = target.closest("a[href]");
758
+ if (!(anchor instanceof HTMLAnchorElement)) {
759
+ return;
760
+ }
761
+ if (anchor.closest("[data-jmp-no-track]") || anchor.closest("[data-jmp-tracked]")) {
762
+ return;
763
+ }
764
+ const href = anchor.getAttribute("href") ?? "";
765
+ const classified = classifyContactHref(href);
766
+ if (!classified) {
767
+ return;
768
+ }
769
+ const path = window.location.pathname;
770
+ if (classified.kind === "call") {
771
+ if (dedupeContactClick(`call_click:${path}`)) {
772
+ return;
773
+ }
774
+ void trackCallClick(path);
775
+ return;
776
+ }
777
+ if (classified.kind === "email") {
778
+ if (dedupeContactClick(`email_click:${path}`)) {
779
+ return;
780
+ }
781
+ void trackEmailClick(path);
782
+ return;
783
+ }
784
+ if (dedupeContactClick(`contact_click:${classified.channel}:${path}`)) {
785
+ return;
786
+ }
787
+ void trackContactClick(path, {
788
+ channel: classified.channel,
789
+ linkHost: classified.linkHost
790
+ });
791
+ };
792
+ document.addEventListener("click", handler, { capture: true });
793
+ return () => {
794
+ document.removeEventListener("click", handler, { capture: true });
795
+ };
796
+ }
797
+ function dedupeContactClick(key) {
798
+ const now = Date.now();
799
+ if (key === lastContactClickKey && now - lastContactClickAt < 1e3) {
800
+ return true;
801
+ }
802
+ lastContactClickKey = key;
803
+ lastContactClickAt = now;
804
+ return false;
805
+ }
806
+ if (typeof window !== "undefined" && canSend()) {
807
+ captureLandingAttribution(window.location.hostname);
808
+ }
809
+ if (typeof window !== "undefined" && config.autoTrackContactLinks && canSend()) {
810
+ subscribeContactLinkAutoTrack();
811
+ }
812
+ return {
813
+ track,
814
+ trackPageView,
815
+ trackFormSubmit,
816
+ trackCallClick,
817
+ trackEmailClick,
818
+ trackContactClick,
819
+ trackLead,
820
+ subscribeToSpaNavigation,
821
+ subscribeContactLinkAutoTrack,
822
+ setTrackingConsent: setJmpTrackingConsent,
823
+ isTrackingConsentGranted: isJmpTrackingConsentGranted
824
+ };
825
+ }
826
+
827
+ // src/env/browser.ts
828
+ function getJmpBrowserAnalyticsConfigFromEnv(overrides) {
829
+ const pub = readJmpPublicEnvFromProcess();
830
+ const site = getJmpSiteDefinition();
831
+ const baseUrl = overrides?.baseUrl ?? pub.analyticsUrl ?? site?.analyticsPortalUrl;
832
+ const trackingKey = overrides?.trackingKey ?? pub.trackingKey;
833
+ if (!baseUrl || !trackingKey) return null;
834
+ return {
835
+ baseUrl,
836
+ trackingKey,
837
+ pageViewDedupeMs: DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,
838
+ ...overrides,
839
+ requireConsent: overrides?.requireConsent ?? pub.requireConsent,
840
+ autoTrackContactLinks: overrides?.autoTrackContactLinks ?? pub.autoContactLinks
841
+ };
842
+ }
843
+ var jmpAnalyticsBrowserSingleton = null;
844
+ function getOrCreateJmpAnalyticsFromBrowserEnv(overrides) {
845
+ if (typeof window === "undefined") return null;
846
+ if (jmpAnalyticsBrowserSingleton) return jmpAnalyticsBrowserSingleton;
847
+ const config = getJmpBrowserAnalyticsConfigFromEnv(overrides);
848
+ if (!config) return null;
849
+ jmpAnalyticsBrowserSingleton = createJmpAnalytics(config);
850
+ return jmpAnalyticsBrowserSingleton;
851
+ }
852
+
853
+ // src/env/browser-next.ts
854
+ function readNextAutoContactLinksEnv() {
855
+ if (typeof process === "undefined" || !process.env) {
856
+ return void 0;
857
+ }
858
+ const { env } = process;
859
+ const raw = env.NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS ?? env.PUBLIC_JMP_AUTO_CONTACT_LINKS;
860
+ return typeof raw === "string" && raw.length > 0 ? raw : void 0;
861
+ }
862
+ function getJmpNextAnalyticsConfigOverrides() {
863
+ const autoEnv = readNextAutoContactLinksEnv();
864
+ const autoContactLinks = autoEnv === "false" ? false : autoEnv === "true" ? true : true;
865
+ return { autoTrackContactLinks: autoContactLinks };
866
+ }
867
+ function getOrCreateJmpAnalyticsForNextApp(overrides) {
868
+ return getOrCreateJmpAnalyticsFromBrowserEnv({
869
+ ...getJmpNextAnalyticsConfigOverrides(),
870
+ ...overrides
871
+ });
872
+ }
873
+
874
+ // src/react.tsx
875
+ var import_react = require("react");
876
+ var import_jsx_runtime = require("react/jsx-runtime");
877
+
878
+ // src/next.tsx
879
+ var import_jsx_runtime2 = require("react/jsx-runtime");
880
+ function JmpLandingCaptureScript({
881
+ trackingKey = readJmpPublicEnvFromProcess().trackingKey
882
+ } = {}) {
883
+ if (!trackingKey?.trim()) {
884
+ return null;
885
+ }
886
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
887
+ import_script.default,
888
+ {
889
+ id: "jmp-landing-capture",
890
+ strategy: "beforeInteractive",
891
+ dangerouslySetInnerHTML: {
892
+ __html: buildLandingCaptureInlineScript()
893
+ }
894
+ }
895
+ );
896
+ }
897
+ function JmpNextPageViewTrackerInner({
898
+ configOverrides
899
+ }) {
900
+ const pathname = (0, import_navigation.usePathname)();
901
+ const searchParams = (0, import_navigation.useSearchParams)();
902
+ const query = searchParams?.toString() ?? "";
903
+ const path = query ? `${pathname}?${query}` : pathname;
904
+ (0, import_react2.useLayoutEffect)(() => {
905
+ const client = getOrCreateJmpAnalyticsForNextApp(configOverrides);
906
+ void client?.trackPageView({ path });
907
+ }, [path, configOverrides]);
908
+ return null;
909
+ }
910
+ function JmpNextPageViewTracker({
911
+ configOverrides
912
+ }) {
913
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react2.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JmpNextPageViewTrackerInner, { configOverrides }) });
914
+ }
915
+ function useJmpTrackingActive(deferUntilConsent) {
916
+ const consentGranted = (0, import_react2.useSyncExternalStore)(
917
+ subscribeJmpTrackingConsent,
918
+ () => isJmpTrackingConsentGranted(),
919
+ () => false
920
+ );
921
+ return !deferUntilConsent || consentGranted;
922
+ }
923
+ function jmpConfigOverridesFromProps(props) {
924
+ const { analyticsUrl, trackingKey } = props;
925
+ if (!analyticsUrl?.trim() && !trackingKey?.trim()) {
926
+ return void 0;
927
+ }
928
+ return {
929
+ ...analyticsUrl?.trim() ? { baseUrl: analyticsUrl.trim() } : {},
930
+ ...trackingKey?.trim() ? { trackingKey: trackingKey.trim() } : {}
931
+ };
932
+ }
933
+ function JmpNextAnalyticsBootstrap({
934
+ configOverrides
935
+ }) {
936
+ (0, import_react2.useLayoutEffect)(() => {
937
+ getOrCreateJmpAnalyticsForNextApp(configOverrides);
938
+ }, [configOverrides]);
939
+ return null;
940
+ }
941
+ function JmpNextAnalytics({
942
+ deferUntilConsent = readJmpPublicEnvFromProcess().requireConsent === true,
943
+ analyticsUrl,
944
+ trackingKey
945
+ } = {}) {
946
+ const active = useJmpTrackingActive(deferUntilConsent);
947
+ const configOverrides = (0, import_react2.useMemo)(
948
+ () => jmpConfigOverridesFromProps({ analyticsUrl, trackingKey }),
949
+ [analyticsUrl, trackingKey]
950
+ );
951
+ if (!active) {
952
+ return null;
953
+ }
954
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
955
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JmpNextAnalyticsBootstrap, { configOverrides }),
956
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JmpLandingCaptureScript, { trackingKey }),
957
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JmpNextPageViewTracker, { configOverrides })
958
+ ] });
959
+ }
960
+ function JmpSiteRoot({
961
+ children,
962
+ deferUntilConsent,
963
+ analyticsUrl,
964
+ trackingKey
965
+ }) {
966
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
967
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
968
+ JmpNextAnalytics,
969
+ {
970
+ deferUntilConsent,
971
+ analyticsUrl,
972
+ trackingKey
973
+ }
974
+ ),
975
+ children
976
+ ] });
977
+ }
978
+
979
+ // src/next/site-root-server.tsx
980
+ var import_jsx_runtime3 = require("react/jsx-runtime");
981
+ function JmpSiteRootServer({
982
+ children,
983
+ deferUntilConsent
984
+ }) {
985
+ const pub = readJmpPublicEnvFromProcess();
986
+ const site = getJmpSiteDefinition();
987
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
988
+ JmpSiteRoot,
989
+ {
990
+ analyticsUrl: pub.analyticsUrl ?? site?.analyticsPortalUrl,
991
+ trackingKey: pub.trackingKey,
992
+ deferUntilConsent,
993
+ children
994
+ }
995
+ );
996
+ }
997
+ // Annotate the CommonJS export names for ESM import in node:
998
+ 0 && (module.exports = {
999
+ JmpSiteRootServer
1000
+ });
1001
+ //# sourceMappingURL=site-root-server.cjs.map