@jmp-technologies/analytics 0.1.17 → 0.1.19

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 (40) hide show
  1. package/README.md +3 -1
  2. package/dist/{chunk-3ELQETAA.js → chunk-EPNF76VK.js} +2 -2
  3. package/dist/{chunk-MUHLIN7P.js → chunk-GB37RVJJ.js} +339 -103
  4. package/dist/chunk-GB37RVJJ.js.map +1 -0
  5. package/dist/index.cjs +348 -104
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +112 -35
  8. package/dist/index.d.ts +112 -35
  9. package/dist/index.js +21 -5
  10. package/dist/next/content.cjs +322 -0
  11. package/dist/next/content.cjs.map +1 -0
  12. package/dist/next/content.d.cts +37 -0
  13. package/dist/next/content.d.ts +37 -0
  14. package/dist/next/content.js +172 -0
  15. package/dist/next/content.js.map +1 -0
  16. package/dist/next/instrumentation.cjs +131 -0
  17. package/dist/next/instrumentation.cjs.map +1 -0
  18. package/dist/next/instrumentation.d.cts +7 -0
  19. package/dist/next/instrumentation.d.ts +7 -0
  20. package/dist/next/instrumentation.js +18 -0
  21. package/dist/next/instrumentation.js.map +1 -0
  22. package/dist/next/middleware.cjs +147 -0
  23. package/dist/next/middleware.cjs.map +1 -0
  24. package/dist/next/middleware.d.cts +13 -0
  25. package/dist/next/middleware.d.ts +13 -0
  26. package/dist/next/middleware.js +22 -0
  27. package/dist/next/middleware.js.map +1 -0
  28. package/dist/next.cjs +322 -86
  29. package/dist/next.cjs.map +1 -1
  30. package/dist/next.d.cts +24 -8
  31. package/dist/next.d.ts +24 -8
  32. package/dist/next.js +41 -13
  33. package/dist/next.js.map +1 -1
  34. package/dist/react.cjs +279 -69
  35. package/dist/react.cjs.map +1 -1
  36. package/dist/react.js +2 -2
  37. package/docs/legal-and-privacy.md +1 -0
  38. package/package.json +17 -2
  39. package/dist/chunk-MUHLIN7P.js.map +0 -1
  40. /package/dist/{chunk-3ELQETAA.js.map → chunk-EPNF76VK.js.map} +0 -0
package/README.md CHANGED
@@ -73,6 +73,8 @@ import {
73
73
 
74
74
  Or call **`trackEmailClick(path)`** / **`trackCallClick(path)`** from `useJmpAnalytics()`.
75
75
 
76
+ For **SMS / WhatsApp / maps / scheduling** link taps, call **`trackContactClick(path, { channel, linkHost })`** — or set **`autoTrackContactLinks: true`** in your config (or **`NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS=true`**) to register one delegated `click` listener that classifies outbound links for you. Auto-tracking is **off by default**; per-link opt-out via `data-jmp-no-track` on the `<a>` (or any ancestor).
77
+
76
78
  **Hooks:** `useJmpAnalytics()` from `@jmp-technologies/analytics/next` or `@jmp-technologies/analytics/react` for forms and custom events.
77
79
 
78
80
  ---
@@ -243,7 +245,7 @@ When present, `category` is an optional section label you can use to group FAQs
243
245
 
244
246
  **Tracking events**
245
247
 
246
- - `trackPageView`, `trackFormSubmit`, `trackCallClick`, `trackEmailClick` (since **0.1.10**), `trackLead`
248
+ - `trackPageView`, `trackFormSubmit`, `trackCallClick`, `trackEmailClick` (since **0.1.10**), `trackContactClick` (since **0.1.18** — SMS, WhatsApp, maps, scheduling), `trackLead`
247
249
 
248
250
  **Content**
249
251
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getOrCreateJmpAnalyticsFromBrowserEnv
3
- } from "./chunk-MUHLIN7P.js";
3
+ } from "./chunk-GB37RVJJ.js";
4
4
 
5
5
  // src/react.tsx
6
6
  import { useMemo } from "react";
@@ -52,4 +52,4 @@ export {
52
52
  JmpTrackedTelLink,
53
53
  JmpTrackedMailtoLink
54
54
  };
55
- //# sourceMappingURL=chunk-3ELQETAA.js.map
55
+ //# sourceMappingURL=chunk-EPNF76VK.js.map
@@ -3,7 +3,6 @@ var JMP_SESSION_ID_KEY = "jmp_analytics_sid";
3
3
  var JMP_LANDING_REFERER_KEY = "jmp_analytics_landing_referer";
4
4
  var JMP_LANDING_REFERER_COOKIE = "jmp_lr";
5
5
  var JMP_UTM_SESSION_KEY = "jmp_analytics_utm";
6
- var JMP_EARLY_BEACON_PATH_KEY = "jmp_analytics_early_beacon_path";
7
6
  var LANDING_REFERER_COOKIE_MAX_AGE_SEC = 30 * 60;
8
7
  var JMP_UTM_PARAM_KEYS = [
9
8
  "utm_source",
@@ -136,84 +135,207 @@ function getOrCreateSessionIdFromStorage() {
136
135
  return void 0;
137
136
  }
138
137
  }
139
- function wasEarlyBeaconSentForPath(path) {
140
- if (typeof window === "undefined") {
141
- return false;
138
+ function buildLandingCaptureInlineScript() {
139
+ const cookieMaxAge = String(LANDING_REFERER_COOKIE_MAX_AGE_SEC);
140
+ 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){}})();`;
141
+ }
142
+
143
+ // src/contact-link-classifier.ts
144
+ var BOOKING_LINK_HOSTS = [
145
+ "calendly.com",
146
+ "cal.com",
147
+ "acuityscheduling.com",
148
+ "calendar.google.com",
149
+ "calendar.app.google",
150
+ "bookings.microsoft.com",
151
+ "outlook.office.com",
152
+ "outlook.office365.com",
153
+ "book.ms",
154
+ "setmore.com",
155
+ "simplybook.me",
156
+ "oncehub.com",
157
+ "scheduleonce.com",
158
+ "youcanbook.me",
159
+ "savvycal.com",
160
+ "tidycal.com",
161
+ "zohobookings.com",
162
+ "square.site",
163
+ "squareup.com",
164
+ "meetings.hubspot.com",
165
+ "jobber.com",
166
+ "housecallpro.com",
167
+ "servicetitan.com",
168
+ "thryv.com",
169
+ "honeybook.com",
170
+ "dubsado.com",
171
+ "fresha.com",
172
+ "booksy.com",
173
+ "vagaro.com",
174
+ "mindbodyonline.com",
175
+ "mindbody.io",
176
+ "janeapp.com",
177
+ "cliniko.com",
178
+ "practicebetter.io"
179
+ ];
180
+ var WHATSAPP_HOSTS = ["wa.me", "api.whatsapp.com", "whatsapp.com"];
181
+ function hostnameMatches(host, pattern) {
182
+ const h = host.toLowerCase();
183
+ const p = pattern.toLowerCase();
184
+ return h === p || h.endsWith(`.${p}`);
185
+ }
186
+ function isMapsHost(host, pathname) {
187
+ if (hostnameMatches(host, "maps.apple.com")) {
188
+ return true;
142
189
  }
143
- try {
144
- return sessionStorage.getItem(JMP_EARLY_BEACON_PATH_KEY) === path;
145
- } catch {
146
- return false;
190
+ if (hostnameMatches(host, "maps.app.goo.gl")) {
191
+ return true;
192
+ }
193
+ if (hostnameMatches(host, "google.com") || hostnameMatches(host, "google.co")) {
194
+ return pathname === "/maps" || pathname.startsWith("/maps/") || pathname.startsWith("/local");
147
195
  }
196
+ return false;
148
197
  }
149
- function markEarlyBeaconSentForPath(path) {
150
- if (typeof window === "undefined") {
151
- return;
198
+ function isWhatsAppHost(host, pathname) {
199
+ for (const pattern of WHATSAPP_HOSTS) {
200
+ if (!hostnameMatches(host, pattern)) {
201
+ continue;
202
+ }
203
+ if (pattern === "whatsapp.com") {
204
+ return pathname.startsWith("/send");
205
+ }
206
+ return true;
152
207
  }
208
+ return false;
209
+ }
210
+ function isBookingHost(host) {
211
+ for (const pattern of BOOKING_LINK_HOSTS) {
212
+ if (hostnameMatches(host, pattern)) {
213
+ return true;
214
+ }
215
+ }
216
+ return false;
217
+ }
218
+ function classifyContactHref(href) {
219
+ const raw = href.trim();
220
+ if (!raw || raw.startsWith("#") || raw.startsWith("javascript:")) {
221
+ return null;
222
+ }
223
+ const lower = raw.toLowerCase();
224
+ if (lower.startsWith("tel:")) {
225
+ return { kind: "call" };
226
+ }
227
+ if (lower.startsWith("mailto:")) {
228
+ return { kind: "email" };
229
+ }
230
+ if (lower.startsWith("sms:") || lower.startsWith("smsto:")) {
231
+ return { kind: "contact_click", channel: "sms", linkHost: null };
232
+ }
233
+ if (lower.startsWith("geo:")) {
234
+ return { kind: "contact_click", channel: "maps", linkHost: null };
235
+ }
236
+ if (raw.startsWith("/") || raw.startsWith("?")) {
237
+ return null;
238
+ }
239
+ let url;
153
240
  try {
154
- sessionStorage.setItem(JMP_EARLY_BEACON_PATH_KEY, path);
241
+ url = new URL(raw);
155
242
  } catch {
243
+ return null;
244
+ }
245
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
246
+ return null;
247
+ }
248
+ const host = url.hostname.toLowerCase();
249
+ const pathname = url.pathname.toLowerCase();
250
+ if (isWhatsAppHost(host, pathname)) {
251
+ return { kind: "contact_click", channel: "whatsapp", linkHost: host };
156
252
  }
253
+ if (isMapsHost(host, pathname)) {
254
+ return { kind: "contact_click", channel: "maps", linkHost: host };
255
+ }
256
+ if (isBookingHost(host)) {
257
+ return { kind: "contact_click", channel: "booking", linkHost: host };
258
+ }
259
+ return null;
157
260
  }
158
- function sendEarlyPageViewBeacon(args) {
159
- if (typeof window === "undefined" || typeof fetch === "undefined") {
160
- return false;
261
+
262
+ // src/site-config.ts
263
+ var registeredSite = null;
264
+ function readNextPublicEnv(key) {
265
+ if (typeof process === "undefined" || !process.env) return void 0;
266
+ const v = key === "NEXT_PUBLIC_JMP_ANALYTICS_URL" ? process.env.NEXT_PUBLIC_JMP_ANALYTICS_URL : process.env.NEXT_PUBLIC_JMP_TRACKING_KEY;
267
+ return typeof v === "string" && v.length > 0 ? v : void 0;
268
+ }
269
+ function getServerConfigFromEnv() {
270
+ const baseUrl = readNextPublicEnv("NEXT_PUBLIC_JMP_ANALYTICS_URL");
271
+ const trackingKey = readNextPublicEnv("NEXT_PUBLIC_JMP_TRACKING_KEY");
272
+ if (!baseUrl || !trackingKey) return null;
273
+ return { baseUrl, trackingKey };
274
+ }
275
+ function defineJmpSite(definition) {
276
+ registeredSite = definition;
277
+ return definition;
278
+ }
279
+ function getJmpSiteDefinition() {
280
+ return registeredSite;
281
+ }
282
+ function getJmpContentConfig() {
283
+ const fromEnv = getServerConfigFromEnv();
284
+ if (!fromEnv) {
285
+ return null;
161
286
  }
162
- const path = `${window.location.pathname}${window.location.search}` || "/";
163
- if (wasEarlyBeaconSentForPath(path)) {
287
+ const site = getJmpSiteDefinition();
288
+ return {
289
+ ...fromEnv,
290
+ contentWrappers: site?.contentWrappers ?? fromEnv.contentWrappers
291
+ };
292
+ }
293
+
294
+ // src/index.ts
295
+ var JMP_ANALYTICS_SDK_VERSION = "0.1.19";
296
+ var JMP_TRACKING_CONSENT_STORAGE_KEY = "jmp_analytics_consent_v1";
297
+ var moduleTrackingConsentGranted = true;
298
+ var trackingConsentListeners = /* @__PURE__ */ new Set();
299
+ function readStoredTrackingConsent() {
300
+ if (typeof window === "undefined") {
164
301
  return false;
165
302
  }
166
- const { landingReferer, utm } = captureLandingAttribution(window.location.hostname);
167
- const sessionId = getOrCreateSessionIdFromStorage();
168
- const metadata = {
169
- pageHostname: window.location.hostname,
170
- earlyBeacon: true,
171
- ...utm
172
- };
173
- if (landingReferer) {
174
- metadata.referer = landingReferer;
303
+ try {
304
+ return localStorage.getItem(JMP_TRACKING_CONSENT_STORAGE_KEY) === "granted";
305
+ } catch {
306
+ return false;
175
307
  }
176
- const payload = {
177
- type: "page_view",
178
- path,
179
- trackingKey: args.trackingKey,
180
- ...sessionId ? { sessionId } : {},
181
- metadata
182
- };
183
- const headers = {
184
- "Content-Type": "application/json",
185
- "X-JMP-Tracking-Key": args.trackingKey
308
+ }
309
+ function isJmpTrackingConsentGranted() {
310
+ return moduleTrackingConsentGranted;
311
+ }
312
+ function subscribeJmpTrackingConsent(listener) {
313
+ trackingConsentListeners.add(listener);
314
+ return () => {
315
+ trackingConsentListeners.delete(listener);
186
316
  };
187
- if (landingReferer) {
188
- headers["X-JMP-Visitor-Referer"] = landingReferer;
317
+ }
318
+ function setJmpTrackingConsent(granted) {
319
+ if (moduleTrackingConsentGranted === granted) {
320
+ return;
189
321
  }
190
- markEarlyBeaconSentForPath(path);
191
- void fetch(`${args.baseUrl.replace(/\/$/, "")}/api/events`, {
192
- method: "POST",
193
- headers,
194
- body: JSON.stringify(payload),
195
- keepalive: true,
196
- mode: "cors",
197
- credentials: "omit"
198
- }).catch(() => {
322
+ moduleTrackingConsentGranted = granted;
323
+ if (typeof window !== "undefined") {
199
324
  try {
200
- sessionStorage.removeItem(JMP_EARLY_BEACON_PATH_KEY);
325
+ localStorage.setItem(
326
+ JMP_TRACKING_CONSENT_STORAGE_KEY,
327
+ granted ? "granted" : "denied"
328
+ );
201
329
  } catch {
202
330
  }
203
- });
204
- return true;
205
- }
206
- function escapeInlineScript(value) {
207
- return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
208
- }
209
- function buildLandingCaptureInlineScript(args) {
210
- const baseUrl = escapeInlineScript(args.baseUrl.replace(/\/$/, ""));
211
- const trackingKey = escapeInlineScript(args.trackingKey);
212
- const cookieMaxAge = String(LANDING_REFERER_COOKIE_MAX_AGE_SEC);
213
- 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)}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)}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){}})();`;
331
+ if (granted) {
332
+ captureLandingAttribution(window.location.hostname);
333
+ }
334
+ }
335
+ for (const listener of trackingConsentListeners) {
336
+ listener();
337
+ }
214
338
  }
215
-
216
- // src/index.ts
217
339
  function captureAndGetLandingReferer() {
218
340
  if (typeof window === "undefined") {
219
341
  return null;
@@ -288,9 +410,16 @@ function createJmpAnalytics(config) {
288
410
  );
289
411
  }
290
412
  const fetchFn = resolvedFetch;
413
+ moduleTrackingConsentGranted = config.requireConsent ? readStoredTrackingConsent() : true;
414
+ function canSend() {
415
+ return moduleTrackingConsentGranted;
416
+ }
291
417
  let lastDedupePath = "";
292
418
  let lastDedupeAt = 0;
293
419
  async function send(body) {
420
+ if (!canSend()) {
421
+ return;
422
+ }
294
423
  const url = `${baseUrl}/api/events`;
295
424
  const headers = {
296
425
  "Content-Type": "application/json",
@@ -346,6 +475,9 @@ function createJmpAnalytics(config) {
346
475
  }
347
476
  }
348
477
  async function sendLead(body) {
478
+ if (!canSend()) {
479
+ return;
480
+ }
349
481
  const url = `${baseUrl}/api/leads`;
350
482
  const headers = {
351
483
  "Content-Type": "application/json",
@@ -408,6 +540,9 @@ function createJmpAnalytics(config) {
408
540
  return false;
409
541
  }
410
542
  function trackPageView(options) {
543
+ if (!canSend()) {
544
+ return Promise.resolve();
545
+ }
411
546
  const pathFromOptions = options?.path?.trim();
412
547
  let pathname = "/";
413
548
  let search = "";
@@ -423,9 +558,6 @@ function createJmpAnalytics(config) {
423
558
  search = window.location.search.replace(/^\?/, "");
424
559
  }
425
560
  const path = search ? `${pathname}?${search}` : pathname;
426
- if (wasEarlyBeaconSentForPath(path)) {
427
- return Promise.resolve();
428
- }
429
561
  if (shouldDedupePageView(pathname)) {
430
562
  return Promise.resolve();
431
563
  }
@@ -476,6 +608,20 @@ function createJmpAnalytics(config) {
476
608
  metadata: metadata ?? {}
477
609
  });
478
610
  }
611
+ function trackContactClick(path, options) {
612
+ const metadata = {
613
+ ...options.metadata && typeof options.metadata === "object" ? options.metadata : {},
614
+ channel: options.channel
615
+ };
616
+ if (options.linkHost) {
617
+ metadata.linkHost = options.linkHost;
618
+ }
619
+ return send({
620
+ type: "contact_click",
621
+ path: path || "/",
622
+ metadata
623
+ });
624
+ }
479
625
  function trackLead(options) {
480
626
  const path = options?.path ?? (typeof window !== "undefined" ? window.location.pathname : "/");
481
627
  const body = { path: path || "/" };
@@ -508,7 +654,20 @@ function createJmpAnalytics(config) {
508
654
  if (type === "call_click") {
509
655
  return trackCallClick(payload.path, payload.metadata);
510
656
  }
511
- return trackEmailClick(payload.path, payload.metadata);
657
+ if (type === "email_click") {
658
+ return trackEmailClick(payload.path, payload.metadata);
659
+ }
660
+ const meta = payload.metadata ?? {};
661
+ const channel = typeof meta.channel === "string" ? meta.channel : "";
662
+ const linkHost = typeof meta.linkHost === "string" ? meta.linkHost : null;
663
+ if (channel !== "sms" && channel !== "whatsapp" && channel !== "maps" && channel !== "booking") {
664
+ return Promise.resolve();
665
+ }
666
+ return trackContactClick(payload.path, {
667
+ channel,
668
+ linkHost,
669
+ metadata: meta
670
+ });
512
671
  }
513
672
  function subscribeToSpaNavigation() {
514
673
  if (typeof window === "undefined") {
@@ -542,12 +701,75 @@ function createJmpAnalytics(config) {
542
701
  history.replaceState = replace;
543
702
  };
544
703
  }
545
- if (typeof window !== "undefined") {
704
+ let lastContactClickKey = "";
705
+ let lastContactClickAt = 0;
706
+ function subscribeContactLinkAutoTrack() {
707
+ if (typeof window === "undefined" || typeof document === "undefined") {
708
+ return () => {
709
+ };
710
+ }
711
+ const handler = (event) => {
712
+ if (!canSend()) {
713
+ return;
714
+ }
715
+ const target = event.target;
716
+ if (!(target instanceof Element)) {
717
+ return;
718
+ }
719
+ const anchor = target.closest("a[href]");
720
+ if (!(anchor instanceof HTMLAnchorElement)) {
721
+ return;
722
+ }
723
+ if (anchor.closest("[data-jmp-no-track]")) {
724
+ return;
725
+ }
726
+ const href = anchor.getAttribute("href") ?? "";
727
+ const classified = classifyContactHref(href);
728
+ if (!classified) {
729
+ return;
730
+ }
731
+ const path = window.location.pathname;
732
+ if (classified.kind === "call") {
733
+ if (dedupeContactClick(`call_click:${path}`)) {
734
+ return;
735
+ }
736
+ void trackCallClick(path);
737
+ return;
738
+ }
739
+ if (classified.kind === "email") {
740
+ if (dedupeContactClick(`email_click:${path}`)) {
741
+ return;
742
+ }
743
+ void trackEmailClick(path);
744
+ return;
745
+ }
746
+ if (dedupeContactClick(`contact_click:${classified.channel}:${path}`)) {
747
+ return;
748
+ }
749
+ void trackContactClick(path, {
750
+ channel: classified.channel,
751
+ linkHost: classified.linkHost
752
+ });
753
+ };
754
+ document.addEventListener("click", handler, { capture: true });
755
+ return () => {
756
+ document.removeEventListener("click", handler, { capture: true });
757
+ };
758
+ }
759
+ function dedupeContactClick(key) {
760
+ const now = Date.now();
761
+ if (key === lastContactClickKey && now - lastContactClickAt < 1e3) {
762
+ return true;
763
+ }
764
+ lastContactClickKey = key;
765
+ lastContactClickAt = now;
766
+ return false;
767
+ }
768
+ if (typeof window !== "undefined" && canSend()) {
546
769
  captureLandingAttribution(window.location.hostname);
547
- sendEarlyPageViewBeacon({
548
- baseUrl,
549
- trackingKey: config.trackingKey
550
- });
770
+ }
771
+ if (typeof window !== "undefined" && config.autoTrackContactLinks && canSend()) {
772
+ subscribeContactLinkAutoTrack();
551
773
  }
552
774
  return {
553
775
  track,
@@ -555,8 +777,12 @@ function createJmpAnalytics(config) {
555
777
  trackFormSubmit,
556
778
  trackCallClick,
557
779
  trackEmailClick,
780
+ trackContactClick,
558
781
  trackLead,
559
- subscribeToSpaNavigation
782
+ subscribeToSpaNavigation,
783
+ subscribeContactLinkAutoTrack,
784
+ setTrackingConsent: setJmpTrackingConsent,
785
+ isTrackingConsentGranted: isJmpTrackingConsentGranted
560
786
  };
561
787
  }
562
788
  async function trackJmpServerPageView(config, options = {}) {
@@ -646,11 +872,6 @@ function resolveContentWrappersFromConfig(config) {
646
872
  if (config.contentWrappers?.length) {
647
873
  return [...config.contentWrappers];
648
874
  }
649
- if (config.trustSlots?.length) {
650
- return config.trustSlots.map(
651
- (slot) => `trust.${slot}`
652
- );
653
- }
654
875
  return [];
655
876
  }
656
877
  async function registerJmpContentWrappers(config) {
@@ -679,7 +900,10 @@ async function registerJmpContentWrappers(config) {
679
900
  "Content-Type": "application/json",
680
901
  "X-JMP-Tracking-Key": config.trackingKey
681
902
  },
682
- body: JSON.stringify({ wrappers })
903
+ body: JSON.stringify({
904
+ wrappers,
905
+ sdkVersion: JMP_ANALYTICS_SDK_VERSION
906
+ })
683
907
  });
684
908
  const text = await res.text();
685
909
  if (!res.ok) {
@@ -697,18 +921,6 @@ async function registerJmpContentWrappers(config) {
697
921
  const data = JSON.parse(text);
698
922
  return { ok: true, wrappers: data.wrappers ?? wrappers };
699
923
  }
700
- async function registerJmpTrustCapabilities(config) {
701
- const result = await registerJmpContentWrappers(config);
702
- if (!result.ok) {
703
- return result;
704
- }
705
- const slots = result.wrappers.filter((w) => w === "trust" || w.startsWith("trust.")).map((w) => w === "trust" ? "summary" : w.slice("trust.".length)).filter(
706
- (s) => ["summary", "testimonials", "reviews", "stats", "logos", "credentials"].includes(
707
- s
708
- )
709
- );
710
- return { ok: true, slots };
711
- }
712
924
  function createJmpContentClient(config) {
713
925
  return {
714
926
  getBlogPosts: () => getJmpBlogPosts(config),
@@ -717,9 +929,7 @@ function createJmpContentClient(config) {
717
929
  getTrustBundle: () => getJmpTrustBundle(config),
718
930
  getSiteProfile: () => getJmpSiteProfile(config),
719
931
  getRobotsTxt: () => getJmpRobotsTxt(config),
720
- registerContentWrappers: () => registerJmpContentWrappers(config),
721
- /** @deprecated Use registerContentWrappers */
722
- registerTrustCapabilities: () => registerJmpTrustCapabilities(config)
932
+ registerContentWrappers: () => registerJmpContentWrappers(config)
723
933
  };
724
934
  }
725
935
  async function getContent(config, path) {
@@ -768,31 +978,49 @@ async function getContentText(config, path) {
768
978
  return text;
769
979
  }
770
980
  var DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS = 800;
771
- function readProcessEnv(key) {
981
+ function readNextPublicEnv2(key) {
772
982
  if (typeof process === "undefined" || !process.env) return void 0;
773
- const v = process.env[key];
983
+ let v;
984
+ switch (key) {
985
+ case "NEXT_PUBLIC_JMP_ANALYTICS_URL":
986
+ v = process.env.NEXT_PUBLIC_JMP_ANALYTICS_URL;
987
+ break;
988
+ case "NEXT_PUBLIC_JMP_TRACKING_KEY":
989
+ v = process.env.NEXT_PUBLIC_JMP_TRACKING_KEY;
990
+ break;
991
+ case "NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT":
992
+ v = process.env.NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT;
993
+ break;
994
+ case "NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS":
995
+ v = process.env.NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS;
996
+ break;
997
+ }
774
998
  return typeof v === "string" && v.length > 0 ? v : void 0;
775
999
  }
776
1000
  function getJmpBrowserAnalyticsConfigFromEnv(overrides) {
777
- const baseUrl = readProcessEnv("NEXT_PUBLIC_JMP_ANALYTICS_URL");
778
- const trackingKey = readProcessEnv("NEXT_PUBLIC_JMP_TRACKING_KEY");
1001
+ const baseUrl = readNextPublicEnv2("NEXT_PUBLIC_JMP_ANALYTICS_URL");
1002
+ const trackingKey = readNextPublicEnv2("NEXT_PUBLIC_JMP_TRACKING_KEY");
779
1003
  if (!baseUrl || !trackingKey) return null;
1004
+ const requireConsentFromEnv = readNextPublicEnv2("NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT") === "true";
1005
+ const autoTrackContactLinksFromEnv = readNextPublicEnv2("NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS") === "true";
780
1006
  return {
781
1007
  baseUrl,
782
1008
  trackingKey,
783
1009
  pageViewDedupeMs: DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,
784
- ...overrides
1010
+ ...overrides,
1011
+ requireConsent: overrides?.requireConsent ?? requireConsentFromEnv,
1012
+ autoTrackContactLinks: overrides?.autoTrackContactLinks ?? autoTrackContactLinksFromEnv
785
1013
  };
786
1014
  }
787
1015
  function getJmpServerContentConfigFromEnv(overrides) {
788
- const baseUrl = readProcessEnv("NEXT_PUBLIC_JMP_ANALYTICS_URL");
789
- const trackingKey = readProcessEnv("NEXT_PUBLIC_JMP_TRACKING_KEY");
1016
+ const baseUrl = readNextPublicEnv2("NEXT_PUBLIC_JMP_ANALYTICS_URL");
1017
+ const trackingKey = readNextPublicEnv2("NEXT_PUBLIC_JMP_TRACKING_KEY");
790
1018
  if (!baseUrl || !trackingKey) return null;
791
1019
  return { baseUrl, trackingKey, ...overrides };
792
1020
  }
793
1021
  function isJmpBrowserAnalyticsEnvConfigured() {
794
1022
  return Boolean(
795
- readProcessEnv("NEXT_PUBLIC_JMP_ANALYTICS_URL") && readProcessEnv("NEXT_PUBLIC_JMP_TRACKING_KEY")
1023
+ readNextPublicEnv2("NEXT_PUBLIC_JMP_ANALYTICS_URL") && readNextPublicEnv2("NEXT_PUBLIC_JMP_TRACKING_KEY")
796
1024
  );
797
1025
  }
798
1026
  function isJmpServerContentEnvConfigured() {
@@ -814,8 +1042,17 @@ function createJmpContentClientFromServerEnv(overrides) {
814
1042
  }
815
1043
 
816
1044
  export {
817
- sendEarlyPageViewBeacon,
818
1045
  buildLandingCaptureInlineScript,
1046
+ BOOKING_LINK_HOSTS,
1047
+ classifyContactHref,
1048
+ defineJmpSite,
1049
+ getJmpSiteDefinition,
1050
+ getJmpContentConfig,
1051
+ JMP_ANALYTICS_SDK_VERSION,
1052
+ JMP_TRACKING_CONSENT_STORAGE_KEY,
1053
+ isJmpTrackingConsentGranted,
1054
+ subscribeJmpTrackingConsent,
1055
+ setJmpTrackingConsent,
819
1056
  createJmpAnalytics,
820
1057
  trackJmpServerPageView,
821
1058
  getJmpBlogPosts,
@@ -825,7 +1062,6 @@ export {
825
1062
  getJmpSiteProfile,
826
1063
  getJmpRobotsTxt,
827
1064
  registerJmpContentWrappers,
828
- registerJmpTrustCapabilities,
829
1065
  createJmpContentClient,
830
1066
  DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,
831
1067
  getJmpBrowserAnalyticsConfigFromEnv,
@@ -835,4 +1071,4 @@ export {
835
1071
  getOrCreateJmpAnalyticsFromBrowserEnv,
836
1072
  createJmpContentClientFromServerEnv
837
1073
  };
838
- //# sourceMappingURL=chunk-MUHLIN7P.js.map
1074
+ //# sourceMappingURL=chunk-GB37RVJJ.js.map