@jmp-technologies/analytics 0.1.19 → 0.1.21

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