@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/dist/index.cjs CHANGED
@@ -20,25 +20,33 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ BOOKING_LINK_HOSTS: () => BOOKING_LINK_HOSTS,
23
24
  DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS: () => DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,
25
+ JMP_ANALYTICS_SDK_VERSION: () => JMP_ANALYTICS_SDK_VERSION,
26
+ JMP_TRACKING_CONSENT_STORAGE_KEY: () => JMP_TRACKING_CONSENT_STORAGE_KEY,
24
27
  buildLandingCaptureInlineScript: () => buildLandingCaptureInlineScript,
28
+ classifyContactHref: () => classifyContactHref,
25
29
  createJmpAnalytics: () => createJmpAnalytics,
26
30
  createJmpContentClient: () => createJmpContentClient,
27
31
  createJmpContentClientFromServerEnv: () => createJmpContentClientFromServerEnv,
32
+ defineJmpSite: () => defineJmpSite,
28
33
  getJmpBlogPost: () => getJmpBlogPost,
29
34
  getJmpBlogPosts: () => getJmpBlogPosts,
30
35
  getJmpBrowserAnalyticsConfigFromEnv: () => getJmpBrowserAnalyticsConfigFromEnv,
36
+ getJmpContentConfig: () => getJmpContentConfig,
31
37
  getJmpFaqs: () => getJmpFaqs,
32
38
  getJmpRobotsTxt: () => getJmpRobotsTxt,
33
39
  getJmpServerContentConfigFromEnv: () => getJmpServerContentConfigFromEnv,
40
+ getJmpSiteDefinition: () => getJmpSiteDefinition,
34
41
  getJmpSiteProfile: () => getJmpSiteProfile,
35
42
  getJmpTrustBundle: () => getJmpTrustBundle,
36
43
  getOrCreateJmpAnalyticsFromBrowserEnv: () => getOrCreateJmpAnalyticsFromBrowserEnv,
37
44
  isJmpBrowserAnalyticsEnvConfigured: () => isJmpBrowserAnalyticsEnvConfigured,
38
45
  isJmpServerContentEnvConfigured: () => isJmpServerContentEnvConfigured,
46
+ isJmpTrackingConsentGranted: () => isJmpTrackingConsentGranted,
39
47
  registerJmpContentWrappers: () => registerJmpContentWrappers,
40
- registerJmpTrustCapabilities: () => registerJmpTrustCapabilities,
41
- sendEarlyPageViewBeacon: () => sendEarlyPageViewBeacon,
48
+ setJmpTrackingConsent: () => setJmpTrackingConsent,
49
+ subscribeJmpTrackingConsent: () => subscribeJmpTrackingConsent,
42
50
  trackJmpServerPageView: () => trackJmpServerPageView
43
51
  });
44
52
  module.exports = __toCommonJS(index_exports);
@@ -48,7 +56,6 @@ var JMP_SESSION_ID_KEY = "jmp_analytics_sid";
48
56
  var JMP_LANDING_REFERER_KEY = "jmp_analytics_landing_referer";
49
57
  var JMP_LANDING_REFERER_COOKIE = "jmp_lr";
50
58
  var JMP_UTM_SESSION_KEY = "jmp_analytics_utm";
51
- var JMP_EARLY_BEACON_PATH_KEY = "jmp_analytics_early_beacon_path";
52
59
  var LANDING_REFERER_COOKIE_MAX_AGE_SEC = 30 * 60;
53
60
  var JMP_UTM_PARAM_KEYS = [
54
61
  "utm_source",
@@ -181,84 +188,207 @@ function getOrCreateSessionIdFromStorage() {
181
188
  return void 0;
182
189
  }
183
190
  }
184
- function wasEarlyBeaconSentForPath(path) {
185
- if (typeof window === "undefined") {
186
- return false;
191
+ function buildLandingCaptureInlineScript() {
192
+ const cookieMaxAge = String(LANDING_REFERER_COOKIE_MAX_AGE_SEC);
193
+ 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){}})();`;
194
+ }
195
+
196
+ // src/contact-link-classifier.ts
197
+ var BOOKING_LINK_HOSTS = [
198
+ "calendly.com",
199
+ "cal.com",
200
+ "acuityscheduling.com",
201
+ "calendar.google.com",
202
+ "calendar.app.google",
203
+ "bookings.microsoft.com",
204
+ "outlook.office.com",
205
+ "outlook.office365.com",
206
+ "book.ms",
207
+ "setmore.com",
208
+ "simplybook.me",
209
+ "oncehub.com",
210
+ "scheduleonce.com",
211
+ "youcanbook.me",
212
+ "savvycal.com",
213
+ "tidycal.com",
214
+ "zohobookings.com",
215
+ "square.site",
216
+ "squareup.com",
217
+ "meetings.hubspot.com",
218
+ "jobber.com",
219
+ "housecallpro.com",
220
+ "servicetitan.com",
221
+ "thryv.com",
222
+ "honeybook.com",
223
+ "dubsado.com",
224
+ "fresha.com",
225
+ "booksy.com",
226
+ "vagaro.com",
227
+ "mindbodyonline.com",
228
+ "mindbody.io",
229
+ "janeapp.com",
230
+ "cliniko.com",
231
+ "practicebetter.io"
232
+ ];
233
+ var WHATSAPP_HOSTS = ["wa.me", "api.whatsapp.com", "whatsapp.com"];
234
+ function hostnameMatches(host, pattern) {
235
+ const h = host.toLowerCase();
236
+ const p = pattern.toLowerCase();
237
+ return h === p || h.endsWith(`.${p}`);
238
+ }
239
+ function isMapsHost(host, pathname) {
240
+ if (hostnameMatches(host, "maps.apple.com")) {
241
+ return true;
187
242
  }
188
- try {
189
- return sessionStorage.getItem(JMP_EARLY_BEACON_PATH_KEY) === path;
190
- } catch {
191
- return false;
243
+ if (hostnameMatches(host, "maps.app.goo.gl")) {
244
+ return true;
245
+ }
246
+ if (hostnameMatches(host, "google.com") || hostnameMatches(host, "google.co")) {
247
+ return pathname === "/maps" || pathname.startsWith("/maps/") || pathname.startsWith("/local");
192
248
  }
249
+ return false;
193
250
  }
194
- function markEarlyBeaconSentForPath(path) {
195
- if (typeof window === "undefined") {
196
- return;
251
+ function isWhatsAppHost(host, pathname) {
252
+ for (const pattern of WHATSAPP_HOSTS) {
253
+ if (!hostnameMatches(host, pattern)) {
254
+ continue;
255
+ }
256
+ if (pattern === "whatsapp.com") {
257
+ return pathname.startsWith("/send");
258
+ }
259
+ return true;
197
260
  }
261
+ return false;
262
+ }
263
+ function isBookingHost(host) {
264
+ for (const pattern of BOOKING_LINK_HOSTS) {
265
+ if (hostnameMatches(host, pattern)) {
266
+ return true;
267
+ }
268
+ }
269
+ return false;
270
+ }
271
+ function classifyContactHref(href) {
272
+ const raw = href.trim();
273
+ if (!raw || raw.startsWith("#") || raw.startsWith("javascript:")) {
274
+ return null;
275
+ }
276
+ const lower = raw.toLowerCase();
277
+ if (lower.startsWith("tel:")) {
278
+ return { kind: "call" };
279
+ }
280
+ if (lower.startsWith("mailto:")) {
281
+ return { kind: "email" };
282
+ }
283
+ if (lower.startsWith("sms:") || lower.startsWith("smsto:")) {
284
+ return { kind: "contact_click", channel: "sms", linkHost: null };
285
+ }
286
+ if (lower.startsWith("geo:")) {
287
+ return { kind: "contact_click", channel: "maps", linkHost: null };
288
+ }
289
+ if (raw.startsWith("/") || raw.startsWith("?")) {
290
+ return null;
291
+ }
292
+ let url;
198
293
  try {
199
- sessionStorage.setItem(JMP_EARLY_BEACON_PATH_KEY, path);
294
+ url = new URL(raw);
200
295
  } catch {
296
+ return null;
297
+ }
298
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
299
+ return null;
300
+ }
301
+ const host = url.hostname.toLowerCase();
302
+ const pathname = url.pathname.toLowerCase();
303
+ if (isWhatsAppHost(host, pathname)) {
304
+ return { kind: "contact_click", channel: "whatsapp", linkHost: host };
201
305
  }
306
+ if (isMapsHost(host, pathname)) {
307
+ return { kind: "contact_click", channel: "maps", linkHost: host };
308
+ }
309
+ if (isBookingHost(host)) {
310
+ return { kind: "contact_click", channel: "booking", linkHost: host };
311
+ }
312
+ return null;
202
313
  }
203
- function sendEarlyPageViewBeacon(args) {
204
- if (typeof window === "undefined" || typeof fetch === "undefined") {
205
- return false;
314
+
315
+ // src/site-config.ts
316
+ var registeredSite = null;
317
+ function readNextPublicEnv(key) {
318
+ if (typeof process === "undefined" || !process.env) return void 0;
319
+ const v = key === "NEXT_PUBLIC_JMP_ANALYTICS_URL" ? process.env.NEXT_PUBLIC_JMP_ANALYTICS_URL : process.env.NEXT_PUBLIC_JMP_TRACKING_KEY;
320
+ return typeof v === "string" && v.length > 0 ? v : void 0;
321
+ }
322
+ function getServerConfigFromEnv() {
323
+ const baseUrl = readNextPublicEnv("NEXT_PUBLIC_JMP_ANALYTICS_URL");
324
+ const trackingKey = readNextPublicEnv("NEXT_PUBLIC_JMP_TRACKING_KEY");
325
+ if (!baseUrl || !trackingKey) return null;
326
+ return { baseUrl, trackingKey };
327
+ }
328
+ function defineJmpSite(definition) {
329
+ registeredSite = definition;
330
+ return definition;
331
+ }
332
+ function getJmpSiteDefinition() {
333
+ return registeredSite;
334
+ }
335
+ function getJmpContentConfig() {
336
+ const fromEnv = getServerConfigFromEnv();
337
+ if (!fromEnv) {
338
+ return null;
206
339
  }
207
- const path = `${window.location.pathname}${window.location.search}` || "/";
208
- if (wasEarlyBeaconSentForPath(path)) {
340
+ const site = getJmpSiteDefinition();
341
+ return {
342
+ ...fromEnv,
343
+ contentWrappers: site?.contentWrappers ?? fromEnv.contentWrappers
344
+ };
345
+ }
346
+
347
+ // src/index.ts
348
+ var JMP_ANALYTICS_SDK_VERSION = "0.1.19";
349
+ var JMP_TRACKING_CONSENT_STORAGE_KEY = "jmp_analytics_consent_v1";
350
+ var moduleTrackingConsentGranted = true;
351
+ var trackingConsentListeners = /* @__PURE__ */ new Set();
352
+ function readStoredTrackingConsent() {
353
+ if (typeof window === "undefined") {
209
354
  return false;
210
355
  }
211
- const { landingReferer, utm } = captureLandingAttribution(window.location.hostname);
212
- const sessionId = getOrCreateSessionIdFromStorage();
213
- const metadata = {
214
- pageHostname: window.location.hostname,
215
- earlyBeacon: true,
216
- ...utm
217
- };
218
- if (landingReferer) {
219
- metadata.referer = landingReferer;
356
+ try {
357
+ return localStorage.getItem(JMP_TRACKING_CONSENT_STORAGE_KEY) === "granted";
358
+ } catch {
359
+ return false;
220
360
  }
221
- const payload = {
222
- type: "page_view",
223
- path,
224
- trackingKey: args.trackingKey,
225
- ...sessionId ? { sessionId } : {},
226
- metadata
227
- };
228
- const headers = {
229
- "Content-Type": "application/json",
230
- "X-JMP-Tracking-Key": args.trackingKey
361
+ }
362
+ function isJmpTrackingConsentGranted() {
363
+ return moduleTrackingConsentGranted;
364
+ }
365
+ function subscribeJmpTrackingConsent(listener) {
366
+ trackingConsentListeners.add(listener);
367
+ return () => {
368
+ trackingConsentListeners.delete(listener);
231
369
  };
232
- if (landingReferer) {
233
- headers["X-JMP-Visitor-Referer"] = landingReferer;
370
+ }
371
+ function setJmpTrackingConsent(granted) {
372
+ if (moduleTrackingConsentGranted === granted) {
373
+ return;
234
374
  }
235
- markEarlyBeaconSentForPath(path);
236
- void fetch(`${args.baseUrl.replace(/\/$/, "")}/api/events`, {
237
- method: "POST",
238
- headers,
239
- body: JSON.stringify(payload),
240
- keepalive: true,
241
- mode: "cors",
242
- credentials: "omit"
243
- }).catch(() => {
375
+ moduleTrackingConsentGranted = granted;
376
+ if (typeof window !== "undefined") {
244
377
  try {
245
- sessionStorage.removeItem(JMP_EARLY_BEACON_PATH_KEY);
378
+ localStorage.setItem(
379
+ JMP_TRACKING_CONSENT_STORAGE_KEY,
380
+ granted ? "granted" : "denied"
381
+ );
246
382
  } catch {
247
383
  }
248
- });
249
- return true;
250
- }
251
- function escapeInlineScript(value) {
252
- return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
253
- }
254
- function buildLandingCaptureInlineScript(args) {
255
- const baseUrl = escapeInlineScript(args.baseUrl.replace(/\/$/, ""));
256
- const trackingKey = escapeInlineScript(args.trackingKey);
257
- const cookieMaxAge = String(LANDING_REFERER_COOKIE_MAX_AGE_SEC);
258
- 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){}})();`;
384
+ if (granted) {
385
+ captureLandingAttribution(window.location.hostname);
386
+ }
387
+ }
388
+ for (const listener of trackingConsentListeners) {
389
+ listener();
390
+ }
259
391
  }
260
-
261
- // src/index.ts
262
392
  function captureAndGetLandingReferer() {
263
393
  if (typeof window === "undefined") {
264
394
  return null;
@@ -333,9 +463,16 @@ function createJmpAnalytics(config) {
333
463
  );
334
464
  }
335
465
  const fetchFn = resolvedFetch;
466
+ moduleTrackingConsentGranted = config.requireConsent ? readStoredTrackingConsent() : true;
467
+ function canSend() {
468
+ return moduleTrackingConsentGranted;
469
+ }
336
470
  let lastDedupePath = "";
337
471
  let lastDedupeAt = 0;
338
472
  async function send(body) {
473
+ if (!canSend()) {
474
+ return;
475
+ }
339
476
  const url = `${baseUrl}/api/events`;
340
477
  const headers = {
341
478
  "Content-Type": "application/json",
@@ -391,6 +528,9 @@ function createJmpAnalytics(config) {
391
528
  }
392
529
  }
393
530
  async function sendLead(body) {
531
+ if (!canSend()) {
532
+ return;
533
+ }
394
534
  const url = `${baseUrl}/api/leads`;
395
535
  const headers = {
396
536
  "Content-Type": "application/json",
@@ -453,6 +593,9 @@ function createJmpAnalytics(config) {
453
593
  return false;
454
594
  }
455
595
  function trackPageView(options) {
596
+ if (!canSend()) {
597
+ return Promise.resolve();
598
+ }
456
599
  const pathFromOptions = options?.path?.trim();
457
600
  let pathname = "/";
458
601
  let search = "";
@@ -468,9 +611,6 @@ function createJmpAnalytics(config) {
468
611
  search = window.location.search.replace(/^\?/, "");
469
612
  }
470
613
  const path = search ? `${pathname}?${search}` : pathname;
471
- if (wasEarlyBeaconSentForPath(path)) {
472
- return Promise.resolve();
473
- }
474
614
  if (shouldDedupePageView(pathname)) {
475
615
  return Promise.resolve();
476
616
  }
@@ -521,6 +661,20 @@ function createJmpAnalytics(config) {
521
661
  metadata: metadata ?? {}
522
662
  });
523
663
  }
664
+ function trackContactClick(path, options) {
665
+ const metadata = {
666
+ ...options.metadata && typeof options.metadata === "object" ? options.metadata : {},
667
+ channel: options.channel
668
+ };
669
+ if (options.linkHost) {
670
+ metadata.linkHost = options.linkHost;
671
+ }
672
+ return send({
673
+ type: "contact_click",
674
+ path: path || "/",
675
+ metadata
676
+ });
677
+ }
524
678
  function trackLead(options) {
525
679
  const path = options?.path ?? (typeof window !== "undefined" ? window.location.pathname : "/");
526
680
  const body = { path: path || "/" };
@@ -553,7 +707,20 @@ function createJmpAnalytics(config) {
553
707
  if (type === "call_click") {
554
708
  return trackCallClick(payload.path, payload.metadata);
555
709
  }
556
- return trackEmailClick(payload.path, payload.metadata);
710
+ if (type === "email_click") {
711
+ return trackEmailClick(payload.path, payload.metadata);
712
+ }
713
+ const meta = payload.metadata ?? {};
714
+ const channel = typeof meta.channel === "string" ? meta.channel : "";
715
+ const linkHost = typeof meta.linkHost === "string" ? meta.linkHost : null;
716
+ if (channel !== "sms" && channel !== "whatsapp" && channel !== "maps" && channel !== "booking") {
717
+ return Promise.resolve();
718
+ }
719
+ return trackContactClick(payload.path, {
720
+ channel,
721
+ linkHost,
722
+ metadata: meta
723
+ });
557
724
  }
558
725
  function subscribeToSpaNavigation() {
559
726
  if (typeof window === "undefined") {
@@ -587,12 +754,75 @@ function createJmpAnalytics(config) {
587
754
  history.replaceState = replace;
588
755
  };
589
756
  }
590
- if (typeof window !== "undefined") {
757
+ let lastContactClickKey = "";
758
+ let lastContactClickAt = 0;
759
+ function subscribeContactLinkAutoTrack() {
760
+ if (typeof window === "undefined" || typeof document === "undefined") {
761
+ return () => {
762
+ };
763
+ }
764
+ const handler = (event) => {
765
+ if (!canSend()) {
766
+ return;
767
+ }
768
+ const target = event.target;
769
+ if (!(target instanceof Element)) {
770
+ return;
771
+ }
772
+ const anchor = target.closest("a[href]");
773
+ if (!(anchor instanceof HTMLAnchorElement)) {
774
+ return;
775
+ }
776
+ if (anchor.closest("[data-jmp-no-track]")) {
777
+ return;
778
+ }
779
+ const href = anchor.getAttribute("href") ?? "";
780
+ const classified = classifyContactHref(href);
781
+ if (!classified) {
782
+ return;
783
+ }
784
+ const path = window.location.pathname;
785
+ if (classified.kind === "call") {
786
+ if (dedupeContactClick(`call_click:${path}`)) {
787
+ return;
788
+ }
789
+ void trackCallClick(path);
790
+ return;
791
+ }
792
+ if (classified.kind === "email") {
793
+ if (dedupeContactClick(`email_click:${path}`)) {
794
+ return;
795
+ }
796
+ void trackEmailClick(path);
797
+ return;
798
+ }
799
+ if (dedupeContactClick(`contact_click:${classified.channel}:${path}`)) {
800
+ return;
801
+ }
802
+ void trackContactClick(path, {
803
+ channel: classified.channel,
804
+ linkHost: classified.linkHost
805
+ });
806
+ };
807
+ document.addEventListener("click", handler, { capture: true });
808
+ return () => {
809
+ document.removeEventListener("click", handler, { capture: true });
810
+ };
811
+ }
812
+ function dedupeContactClick(key) {
813
+ const now = Date.now();
814
+ if (key === lastContactClickKey && now - lastContactClickAt < 1e3) {
815
+ return true;
816
+ }
817
+ lastContactClickKey = key;
818
+ lastContactClickAt = now;
819
+ return false;
820
+ }
821
+ if (typeof window !== "undefined" && canSend()) {
591
822
  captureLandingAttribution(window.location.hostname);
592
- sendEarlyPageViewBeacon({
593
- baseUrl,
594
- trackingKey: config.trackingKey
595
- });
823
+ }
824
+ if (typeof window !== "undefined" && config.autoTrackContactLinks && canSend()) {
825
+ subscribeContactLinkAutoTrack();
596
826
  }
597
827
  return {
598
828
  track,
@@ -600,8 +830,12 @@ function createJmpAnalytics(config) {
600
830
  trackFormSubmit,
601
831
  trackCallClick,
602
832
  trackEmailClick,
833
+ trackContactClick,
603
834
  trackLead,
604
- subscribeToSpaNavigation
835
+ subscribeToSpaNavigation,
836
+ subscribeContactLinkAutoTrack,
837
+ setTrackingConsent: setJmpTrackingConsent,
838
+ isTrackingConsentGranted: isJmpTrackingConsentGranted
605
839
  };
606
840
  }
607
841
  async function trackJmpServerPageView(config, options = {}) {
@@ -691,11 +925,6 @@ function resolveContentWrappersFromConfig(config) {
691
925
  if (config.contentWrappers?.length) {
692
926
  return [...config.contentWrappers];
693
927
  }
694
- if (config.trustSlots?.length) {
695
- return config.trustSlots.map(
696
- (slot) => `trust.${slot}`
697
- );
698
- }
699
928
  return [];
700
929
  }
701
930
  async function registerJmpContentWrappers(config) {
@@ -724,7 +953,10 @@ async function registerJmpContentWrappers(config) {
724
953
  "Content-Type": "application/json",
725
954
  "X-JMP-Tracking-Key": config.trackingKey
726
955
  },
727
- body: JSON.stringify({ wrappers })
956
+ body: JSON.stringify({
957
+ wrappers,
958
+ sdkVersion: JMP_ANALYTICS_SDK_VERSION
959
+ })
728
960
  });
729
961
  const text = await res.text();
730
962
  if (!res.ok) {
@@ -742,18 +974,6 @@ async function registerJmpContentWrappers(config) {
742
974
  const data = JSON.parse(text);
743
975
  return { ok: true, wrappers: data.wrappers ?? wrappers };
744
976
  }
745
- async function registerJmpTrustCapabilities(config) {
746
- const result = await registerJmpContentWrappers(config);
747
- if (!result.ok) {
748
- return result;
749
- }
750
- const slots = result.wrappers.filter((w) => w === "trust" || w.startsWith("trust.")).map((w) => w === "trust" ? "summary" : w.slice("trust.".length)).filter(
751
- (s) => ["summary", "testimonials", "reviews", "stats", "logos", "credentials"].includes(
752
- s
753
- )
754
- );
755
- return { ok: true, slots };
756
- }
757
977
  function createJmpContentClient(config) {
758
978
  return {
759
979
  getBlogPosts: () => getJmpBlogPosts(config),
@@ -762,9 +982,7 @@ function createJmpContentClient(config) {
762
982
  getTrustBundle: () => getJmpTrustBundle(config),
763
983
  getSiteProfile: () => getJmpSiteProfile(config),
764
984
  getRobotsTxt: () => getJmpRobotsTxt(config),
765
- registerContentWrappers: () => registerJmpContentWrappers(config),
766
- /** @deprecated Use registerContentWrappers */
767
- registerTrustCapabilities: () => registerJmpTrustCapabilities(config)
985
+ registerContentWrappers: () => registerJmpContentWrappers(config)
768
986
  };
769
987
  }
770
988
  async function getContent(config, path) {
@@ -813,31 +1031,49 @@ async function getContentText(config, path) {
813
1031
  return text;
814
1032
  }
815
1033
  var DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS = 800;
816
- function readProcessEnv(key) {
1034
+ function readNextPublicEnv2(key) {
817
1035
  if (typeof process === "undefined" || !process.env) return void 0;
818
- const v = process.env[key];
1036
+ let v;
1037
+ switch (key) {
1038
+ case "NEXT_PUBLIC_JMP_ANALYTICS_URL":
1039
+ v = process.env.NEXT_PUBLIC_JMP_ANALYTICS_URL;
1040
+ break;
1041
+ case "NEXT_PUBLIC_JMP_TRACKING_KEY":
1042
+ v = process.env.NEXT_PUBLIC_JMP_TRACKING_KEY;
1043
+ break;
1044
+ case "NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT":
1045
+ v = process.env.NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT;
1046
+ break;
1047
+ case "NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS":
1048
+ v = process.env.NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS;
1049
+ break;
1050
+ }
819
1051
  return typeof v === "string" && v.length > 0 ? v : void 0;
820
1052
  }
821
1053
  function getJmpBrowserAnalyticsConfigFromEnv(overrides) {
822
- const baseUrl = readProcessEnv("NEXT_PUBLIC_JMP_ANALYTICS_URL");
823
- const trackingKey = readProcessEnv("NEXT_PUBLIC_JMP_TRACKING_KEY");
1054
+ const baseUrl = readNextPublicEnv2("NEXT_PUBLIC_JMP_ANALYTICS_URL");
1055
+ const trackingKey = readNextPublicEnv2("NEXT_PUBLIC_JMP_TRACKING_KEY");
824
1056
  if (!baseUrl || !trackingKey) return null;
1057
+ const requireConsentFromEnv = readNextPublicEnv2("NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT") === "true";
1058
+ const autoTrackContactLinksFromEnv = readNextPublicEnv2("NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS") === "true";
825
1059
  return {
826
1060
  baseUrl,
827
1061
  trackingKey,
828
1062
  pageViewDedupeMs: DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,
829
- ...overrides
1063
+ ...overrides,
1064
+ requireConsent: overrides?.requireConsent ?? requireConsentFromEnv,
1065
+ autoTrackContactLinks: overrides?.autoTrackContactLinks ?? autoTrackContactLinksFromEnv
830
1066
  };
831
1067
  }
832
1068
  function getJmpServerContentConfigFromEnv(overrides) {
833
- const baseUrl = readProcessEnv("NEXT_PUBLIC_JMP_ANALYTICS_URL");
834
- const trackingKey = readProcessEnv("NEXT_PUBLIC_JMP_TRACKING_KEY");
1069
+ const baseUrl = readNextPublicEnv2("NEXT_PUBLIC_JMP_ANALYTICS_URL");
1070
+ const trackingKey = readNextPublicEnv2("NEXT_PUBLIC_JMP_TRACKING_KEY");
835
1071
  if (!baseUrl || !trackingKey) return null;
836
1072
  return { baseUrl, trackingKey, ...overrides };
837
1073
  }
838
1074
  function isJmpBrowserAnalyticsEnvConfigured() {
839
1075
  return Boolean(
840
- readProcessEnv("NEXT_PUBLIC_JMP_ANALYTICS_URL") && readProcessEnv("NEXT_PUBLIC_JMP_TRACKING_KEY")
1076
+ readNextPublicEnv2("NEXT_PUBLIC_JMP_ANALYTICS_URL") && readNextPublicEnv2("NEXT_PUBLIC_JMP_TRACKING_KEY")
841
1077
  );
842
1078
  }
843
1079
  function isJmpServerContentEnvConfigured() {
@@ -859,25 +1095,33 @@ function createJmpContentClientFromServerEnv(overrides) {
859
1095
  }
860
1096
  // Annotate the CommonJS export names for ESM import in node:
861
1097
  0 && (module.exports = {
1098
+ BOOKING_LINK_HOSTS,
862
1099
  DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,
1100
+ JMP_ANALYTICS_SDK_VERSION,
1101
+ JMP_TRACKING_CONSENT_STORAGE_KEY,
863
1102
  buildLandingCaptureInlineScript,
1103
+ classifyContactHref,
864
1104
  createJmpAnalytics,
865
1105
  createJmpContentClient,
866
1106
  createJmpContentClientFromServerEnv,
1107
+ defineJmpSite,
867
1108
  getJmpBlogPost,
868
1109
  getJmpBlogPosts,
869
1110
  getJmpBrowserAnalyticsConfigFromEnv,
1111
+ getJmpContentConfig,
870
1112
  getJmpFaqs,
871
1113
  getJmpRobotsTxt,
872
1114
  getJmpServerContentConfigFromEnv,
1115
+ getJmpSiteDefinition,
873
1116
  getJmpSiteProfile,
874
1117
  getJmpTrustBundle,
875
1118
  getOrCreateJmpAnalyticsFromBrowserEnv,
876
1119
  isJmpBrowserAnalyticsEnvConfigured,
877
1120
  isJmpServerContentEnvConfigured,
1121
+ isJmpTrackingConsentGranted,
878
1122
  registerJmpContentWrappers,
879
- registerJmpTrustCapabilities,
880
- sendEarlyPageViewBeacon,
1123
+ setJmpTrackingConsent,
1124
+ subscribeJmpTrackingConsent,
881
1125
  trackJmpServerPageView
882
1126
  });
883
1127
  //# sourceMappingURL=index.cjs.map