@jmp-technologies/analytics 0.1.21 → 0.1.23

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 (67) hide show
  1. package/README.md +7 -7
  2. package/dist/astro/client.cjs +36 -18
  3. package/dist/astro/client.cjs.map +1 -1
  4. package/dist/astro/client.d.cts +1 -11
  5. package/dist/astro/client.d.ts +1 -11
  6. package/dist/astro/client.js +2 -2
  7. package/dist/astro/content.cjs +22 -18
  8. package/dist/astro/content.cjs.map +1 -1
  9. package/dist/astro/content.js +3 -3
  10. package/dist/astro/integration.cjs +23 -19
  11. package/dist/astro/integration.cjs.map +1 -1
  12. package/dist/astro/integration.js +3 -3
  13. package/dist/astro/middleware.cjs +61 -18
  14. package/dist/astro/middleware.cjs.map +1 -1
  15. package/dist/astro/middleware.d.cts +1 -1
  16. package/dist/astro/middleware.d.ts +1 -1
  17. package/dist/astro/middleware.js +2 -2
  18. package/dist/astro/register.cjs +23 -19
  19. package/dist/astro/register.cjs.map +1 -1
  20. package/dist/astro/register.js +3 -3
  21. package/dist/chunk-776LXUWH.js +59 -0
  22. package/dist/chunk-776LXUWH.js.map +1 -0
  23. package/dist/{chunk-GLEOJIKL.js → chunk-B3FYJIXY.js} +24 -19
  24. package/dist/chunk-B3FYJIXY.js.map +1 -0
  25. package/dist/{chunk-DXHJGKIL.js → chunk-G3SNRG7J.js} +14 -8
  26. package/dist/chunk-G3SNRG7J.js.map +1 -0
  27. package/dist/{chunk-6TDPOZMJ.js → chunk-HIJ6OV4S.js} +3 -3
  28. package/dist/{chunk-XA55GBSU.js → chunk-QUNCGUEE.js} +3 -3
  29. package/dist/{chunk-PDOQ2XBW.js → chunk-W6VYMD2P.js} +48 -26
  30. package/dist/chunk-W6VYMD2P.js.map +1 -0
  31. package/dist/env-public-B6Y0DG68.d.cts +14 -0
  32. package/dist/env-public-B6Y0DG68.d.ts +14 -0
  33. package/dist/index.cjs +70 -42
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +3 -2
  36. package/dist/index.d.ts +3 -2
  37. package/dist/index.js +4 -2
  38. package/dist/next/content.cjs +22 -18
  39. package/dist/next/content.cjs.map +1 -1
  40. package/dist/next/content.js +3 -3
  41. package/dist/next/instrumentation.cjs +23 -19
  42. package/dist/next/instrumentation.cjs.map +1 -1
  43. package/dist/next/instrumentation.js +2 -2
  44. package/dist/next/middleware.cjs +61 -18
  45. package/dist/next/middleware.cjs.map +1 -1
  46. package/dist/next/middleware.d.cts +1 -1
  47. package/dist/next/middleware.d.ts +1 -1
  48. package/dist/next/middleware.js +2 -2
  49. package/dist/next.cjs +138 -76
  50. package/dist/next.cjs.map +1 -1
  51. package/dist/next.d.cts +13 -3
  52. package/dist/next.d.ts +13 -3
  53. package/dist/next.js +45 -8
  54. package/dist/next.js.map +1 -1
  55. package/dist/react.cjs +75 -45
  56. package/dist/react.cjs.map +1 -1
  57. package/dist/react.js +3 -3
  58. package/dist/{track-page-view-BLS0YfL-.d.ts → track-page-view-CcPlzQNB.d.ts} +3 -0
  59. package/dist/{track-page-view-DfCyWHqe.d.cts → track-page-view-Cr8I4Unm.d.cts} +3 -0
  60. package/package.json +1 -1
  61. package/dist/chunk-DXHJGKIL.js.map +0 -1
  62. package/dist/chunk-GLEOJIKL.js.map +0 -1
  63. package/dist/chunk-PDOQ2XBW.js.map +0 -1
  64. package/dist/chunk-TK4VRDMO.js +0 -19
  65. package/dist/chunk-TK4VRDMO.js.map +0 -1
  66. /package/dist/{chunk-6TDPOZMJ.js.map → chunk-HIJ6OV4S.js.map} +0 -0
  67. /package/dist/{chunk-XA55GBSU.js.map → chunk-QUNCGUEE.js.map} +0 -0
@@ -4,7 +4,7 @@ import {
4
4
  readJmpPublicEnvFromProcess,
5
5
  resolveFetch,
6
6
  responseLooksLikeHtml
7
- } from "./chunk-GLEOJIKL.js";
7
+ } from "./chunk-B3FYJIXY.js";
8
8
 
9
9
  // src/landing-capture.ts
10
10
  var JMP_SESSION_ID_KEY = "jmp_analytics_sid";
@@ -268,9 +268,9 @@ function classifyContactHref(href) {
268
268
  }
269
269
 
270
270
  // src/constants.ts
271
- var JMP_ANALYTICS_SDK_VERSION = "0.1.21";
271
+ var JMP_ANALYTICS_SDK_VERSION = "0.1.23";
272
272
  var JMP_TRACKING_CONSENT_STORAGE_KEY = "jmp_analytics_consent_v1";
273
- var DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS = 800;
273
+ var DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS = 2500;
274
274
 
275
275
  // src/consent.ts
276
276
  var moduleTrackingConsentGranted = true;
@@ -319,6 +319,37 @@ function setJmpTrackingConsent(granted) {
319
319
  }
320
320
  }
321
321
 
322
+ // src/tracker/page-view-dedupe.ts
323
+ var JMP_PAGE_VIEW_DEDUPE_STORAGE_KEY = "jmp_analytics_last_pv";
324
+ var moduleLastPath = "";
325
+ var moduleLastAt = 0;
326
+ function shouldDedupeBrowserPageView(pathname, dedupeMs, storage = null) {
327
+ if (dedupeMs <= 0) {
328
+ return false;
329
+ }
330
+ const now = Date.now();
331
+ if (pathname === moduleLastPath && now - moduleLastAt < dedupeMs) {
332
+ return true;
333
+ }
334
+ try {
335
+ const raw = storage?.getItem(JMP_PAGE_VIEW_DEDUPE_STORAGE_KEY);
336
+ if (raw) {
337
+ const prev = JSON.parse(raw);
338
+ if (prev.path === pathname && now - prev.at < dedupeMs) {
339
+ return true;
340
+ }
341
+ }
342
+ storage?.setItem(
343
+ JMP_PAGE_VIEW_DEDUPE_STORAGE_KEY,
344
+ JSON.stringify({ path: pathname, at: now })
345
+ );
346
+ } catch {
347
+ }
348
+ moduleLastPath = pathname;
349
+ moduleLastAt = now;
350
+ return false;
351
+ }
352
+
322
353
  // src/tracker/helpers.ts
323
354
  function captureAndGetLandingReferer() {
324
355
  if (typeof window === "undefined") {
@@ -362,8 +393,6 @@ function createJmpAnalytics(config) {
362
393
  function canSend() {
363
394
  return isJmpTrackingConsentGranted();
364
395
  }
365
- let lastDedupePath = "";
366
- let lastDedupeAt = 0;
367
396
  async function send(body) {
368
397
  if (!canSend()) {
369
398
  return;
@@ -428,19 +457,6 @@ function createJmpAnalytics(config) {
428
457
  throw new Error(message);
429
458
  }
430
459
  }
431
- function shouldDedupePageView(path) {
432
- const ms = config.pageViewDedupeMs ?? 0;
433
- if (ms <= 0 || typeof window === "undefined") {
434
- return false;
435
- }
436
- const now = Date.now();
437
- if (path === lastDedupePath && now - lastDedupeAt < ms) {
438
- return true;
439
- }
440
- lastDedupePath = path;
441
- lastDedupeAt = now;
442
- return false;
443
- }
444
460
  function trackPageView(options) {
445
461
  if (!canSend()) {
446
462
  return Promise.resolve();
@@ -460,7 +476,9 @@ function createJmpAnalytics(config) {
460
476
  search = window.location.search.replace(/^\?/, "");
461
477
  }
462
478
  const path = search ? `${pathname}?${search}` : pathname;
463
- if (shouldDedupePageView(pathname)) {
479
+ const dedupeMs = config.pageViewDedupeMs ?? DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS;
480
+ const storage = typeof window !== "undefined" ? window.sessionStorage : null;
481
+ if (shouldDedupeBrowserPageView(pathname, dedupeMs, storage)) {
464
482
  return Promise.resolve();
465
483
  }
466
484
  const metadata = options?.metadata && typeof options.metadata === "object" ? { ...options.metadata } : {};
@@ -622,7 +640,7 @@ function createJmpAnalytics(config) {
622
640
  if (!(anchor instanceof HTMLAnchorElement)) {
623
641
  return;
624
642
  }
625
- if (anchor.closest("[data-jmp-no-track]")) {
643
+ if (anchor.closest("[data-jmp-no-track]") || anchor.closest("[data-jmp-tracked]")) {
626
644
  return;
627
645
  }
628
646
  const href = anchor.getAttribute("href") ?? "";
@@ -848,10 +866,12 @@ function createJmpContentClient(config) {
848
866
  // src/env/browser.ts
849
867
  function getJmpBrowserAnalyticsConfigFromEnv(overrides) {
850
868
  const pub = readJmpPublicEnvFromProcess();
851
- if (!pub.analyticsUrl || !pub.trackingKey) return null;
869
+ const baseUrl = overrides?.baseUrl ?? pub.analyticsUrl;
870
+ const trackingKey = overrides?.trackingKey ?? pub.trackingKey;
871
+ if (!baseUrl || !trackingKey) return null;
852
872
  return {
853
- baseUrl: pub.analyticsUrl,
854
- trackingKey: pub.trackingKey,
873
+ baseUrl,
874
+ trackingKey,
855
875
  pageViewDedupeMs: DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,
856
876
  ...overrides,
857
877
  requireConsent: overrides?.requireConsent ?? pub.requireConsent,
@@ -860,8 +880,10 @@ function getJmpBrowserAnalyticsConfigFromEnv(overrides) {
860
880
  }
861
881
  function getJmpServerContentConfigFromEnv(overrides) {
862
882
  const pub = readJmpPublicEnvFromProcess();
863
- if (!pub.analyticsUrl || !pub.trackingKey) return null;
864
- return { baseUrl: pub.analyticsUrl, trackingKey: pub.trackingKey, ...overrides };
883
+ const baseUrl = overrides?.baseUrl ?? pub.analyticsUrl;
884
+ const trackingKey = overrides?.trackingKey ?? pub.trackingKey;
885
+ if (!baseUrl || !trackingKey) return null;
886
+ return { baseUrl, trackingKey, ...overrides };
865
887
  }
866
888
  function isJmpBrowserAnalyticsEnvConfigured() {
867
889
  const pub = readJmpPublicEnvFromProcess();
@@ -911,4 +933,4 @@ export {
911
933
  getOrCreateJmpAnalyticsFromBrowserEnv,
912
934
  createJmpContentClientFromServerEnv
913
935
  };
914
- //# sourceMappingURL=chunk-PDOQ2XBW.js.map
936
+ //# sourceMappingURL=chunk-W6VYMD2P.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/landing-capture.ts","../src/contact-link-classifier.ts","../src/constants.ts","../src/consent.ts","../src/tracker/page-view-dedupe.ts","../src/tracker/helpers.ts","../src/tracker/create-jmp-analytics.ts","../src/content/fetch.ts","../src/content/api.ts","../src/content/client.ts","../src/env/browser.ts"],"sourcesContent":["/** First-party landing attribution captured before React / useEffect (sessionStorage). */\n\nexport const JMP_SESSION_ID_KEY = \"jmp_analytics_sid\"\nexport const JMP_LANDING_REFERER_KEY = \"jmp_analytics_landing_referer\"\n/** Cross-subdomain landing referrer (www ↔ apex share sessionStorage origins). */\nexport const JMP_LANDING_REFERER_COOKIE = \"jmp_lr\"\nexport const JMP_UTM_SESSION_KEY = \"jmp_analytics_utm\"\n\nconst LANDING_REFERER_COOKIE_MAX_AGE_SEC = 30 * 60\n\nexport const JMP_UTM_PARAM_KEYS = [\n \"utm_source\",\n \"utm_medium\",\n \"utm_campaign\",\n \"utm_content\",\n \"utm_term\",\n] as const\n\nfunction refererHostFromUrl(referer: string): string | null {\n const trimmed = referer.trim()\n try {\n return new URL(trimmed).hostname.replace(/^www\\./i, \"\").toLowerCase()\n } catch {\n try {\n return new URL(`https://${trimmed}`).hostname\n .replace(/^www\\./i, \"\")\n .toLowerCase()\n } catch {\n return null\n }\n }\n}\n\nexport function isExternalReferrerUrl(\n referer: string,\n siteHostname: string\n): boolean {\n const refHost = refererHostFromUrl(referer)\n if (!refHost) {\n return false\n }\n const site = siteHostname.replace(/^www\\./i, \"\").toLowerCase()\n return refHost !== site && !refHost.endsWith(`.${site}`)\n}\n\n/** Apex hostname used for `Domain=.example.com` cookies (www + apex). */\nexport function registrableSiteDomain(siteHostname: string): string | null {\n const site = siteHostname.replace(/^www\\./i, \"\").toLowerCase()\n if (!site || site === \"localhost\" || site.endsWith(\".local\")) {\n return null\n }\n if (!site.includes(\".\")) {\n return null\n }\n return site\n}\n\nexport function readLandingRefererCookie(siteHostname: string): string | null {\n if (typeof document === \"undefined\") {\n return null\n }\n\n const apex = registrableSiteDomain(siteHostname)\n if (!apex) {\n return null\n }\n\n const prefix = `${JMP_LANDING_REFERER_COOKIE}=`\n for (const part of document.cookie.split(\";\")) {\n const trimmed = part.trim()\n if (!trimmed.startsWith(prefix)) {\n continue\n }\n try {\n const value = decodeURIComponent(trimmed.slice(prefix.length)).trim()\n return value.length > 0 ? value : null\n } catch {\n return null\n }\n }\n\n return null\n}\n\nexport function writeLandingRefererCookie(\n referer: string,\n siteHostname: string\n): void {\n if (typeof document === \"undefined\") {\n return\n }\n\n const apex = registrableSiteDomain(siteHostname)\n if (!apex) {\n return\n }\n\n const value = encodeURIComponent(referer.trim().slice(0, 2048))\n const secure =\n typeof window !== \"undefined\" && window.location.protocol === \"https:\"\n ? \"; Secure\"\n : \"\"\n\n document.cookie = `${JMP_LANDING_REFERER_COOKIE}=${value}; Domain=.${apex}; Path=/; Max-Age=${LANDING_REFERER_COOKIE_MAX_AGE_SEC}; SameSite=Lax${secure}`\n}\n\nexport function captureLandingAttribution(siteHostname: string): {\n landingReferer: string | null\n utm: Record<string, string>\n} {\n if (typeof document === \"undefined\" || typeof window === \"undefined\") {\n return { landingReferer: null, utm: {} }\n }\n\n let landingReferer: string | null = null\n let utm: Record<string, string> = {}\n\n try {\n const storedReferer =\n sessionStorage.getItem(JMP_LANDING_REFERER_KEY)?.trim() || null\n const cookieReferer = readLandingRefererCookie(siteHostname)\n const preservedReferer = storedReferer || cookieReferer\n const currentReferer = document.referrer?.trim() || null\n\n if (\n currentReferer &&\n isExternalReferrerUrl(currentReferer, siteHostname)\n ) {\n sessionStorage.setItem(JMP_LANDING_REFERER_KEY, currentReferer)\n writeLandingRefererCookie(currentReferer, siteHostname)\n landingReferer = currentReferer\n } else if (preservedReferer) {\n landingReferer = preservedReferer\n if (!storedReferer) {\n sessionStorage.setItem(JMP_LANDING_REFERER_KEY, preservedReferer)\n }\n }\n\n const params = new URLSearchParams(window.location.search)\n const rawUtm = sessionStorage.getItem(JMP_UTM_SESSION_KEY)\n if (rawUtm) {\n const parsed = JSON.parse(rawUtm) as unknown\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n utm = parsed as Record<string, string>\n }\n }\n\n let updated = false\n for (const key of JMP_UTM_PARAM_KEYS) {\n const value = params.get(key)?.trim()\n if (value) {\n utm[key] = value\n updated = true\n }\n }\n\n if (updated) {\n sessionStorage.setItem(JMP_UTM_SESSION_KEY, JSON.stringify(utm))\n }\n } catch {\n }\n\n return { landingReferer, utm }\n}\n\nexport function getOrCreateSessionIdFromStorage(): string | undefined {\n if (typeof window === \"undefined\" || !globalThis.crypto?.randomUUID) {\n return undefined\n }\n try {\n let id = sessionStorage.getItem(JMP_SESSION_ID_KEY)\n if (!id) {\n id = crypto.randomUUID()\n sessionStorage.setItem(JMP_SESSION_ID_KEY, id)\n }\n return id\n } catch {\n return undefined\n }\n}\n\n/** Inline script for Next.js `beforeInteractive` — capture referrer/UTM only (no network). */\nexport function buildLandingCaptureInlineScript(): string {\n const cookieMaxAge = String(LANDING_REFERER_COOKIE_MAX_AGE_SEC)\n\n 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){}})();`\n}\n","/**\n * Classify outbound contact links for auto-tracking.\n *\n * Keep channel set in sync with `lib/ingest/contact-click.ts` on the dashboard side.\n *\n * Channels detected from `href` alone (no widget / iframe detection):\n *\n * - `call` — `tel:` (delegates to existing `trackCallClick`)\n * - `email` — `mailto:` (delegates to existing `trackEmailClick`)\n * - `sms` — `sms:` / `smsto:`\n * - `whatsapp` — `wa.me`, `api.whatsapp.com`, `whatsapp.com/send`\n * - `maps` — Google Maps, Apple Maps, `maps.app.goo.gl`, `geo:`\n * - `booking` — fixed allowlist of common scheduling hosts (see {@link BOOKING_LINK_HOSTS})\n *\n * `linkHost` returned in `contact_click` results is the hostname only — never the full URL\n * (avoids leaking phone numbers in `tel:`/`sms:` URLs and PII-laden booking query strings).\n */\n\nexport type ContactLinkChannel = \"sms\" | \"whatsapp\" | \"maps\" | \"booking\"\n\nexport type ClassifiedContactLink =\n | { kind: \"call\" }\n | { kind: \"email\" }\n | { kind: \"contact_click\"; channel: ContactLinkChannel; linkHost: string | null }\n\n/** v1 booking hosts (~80%+ SMB coverage). Add hosts here when a client uses something rare. */\nexport const BOOKING_LINK_HOSTS = [\n \"calendly.com\",\n \"cal.com\",\n \"acuityscheduling.com\",\n \"calendar.google.com\",\n \"calendar.app.google\",\n \"bookings.microsoft.com\",\n \"outlook.office.com\",\n \"outlook.office365.com\",\n \"book.ms\",\n \"setmore.com\",\n \"simplybook.me\",\n \"oncehub.com\",\n \"scheduleonce.com\",\n \"youcanbook.me\",\n \"savvycal.com\",\n \"tidycal.com\",\n \"zohobookings.com\",\n \"square.site\",\n \"squareup.com\",\n \"meetings.hubspot.com\",\n \"jobber.com\",\n \"housecallpro.com\",\n \"servicetitan.com\",\n \"thryv.com\",\n \"honeybook.com\",\n \"dubsado.com\",\n \"fresha.com\",\n \"booksy.com\",\n \"vagaro.com\",\n \"mindbodyonline.com\",\n \"mindbody.io\",\n \"janeapp.com\",\n \"cliniko.com\",\n \"practicebetter.io\",\n] as const\n\nconst WHATSAPP_HOSTS = [\"wa.me\", \"api.whatsapp.com\", \"whatsapp.com\"] as const\n\nexport function hostnameMatches(host: string, pattern: string): boolean {\n const h = host.toLowerCase()\n const p = pattern.toLowerCase()\n return h === p || h.endsWith(`.${p}`)\n}\n\nfunction isMapsHost(host: string, pathname: string): boolean {\n if (hostnameMatches(host, \"maps.apple.com\")) {\n return true\n }\n if (hostnameMatches(host, \"maps.app.goo.gl\")) {\n return true\n }\n if (hostnameMatches(host, \"google.com\") || hostnameMatches(host, \"google.co\")) {\n return pathname === \"/maps\" || pathname.startsWith(\"/maps/\") || pathname.startsWith(\"/local\")\n }\n return false\n}\n\nfunction isWhatsAppHost(host: string, pathname: string): boolean {\n for (const pattern of WHATSAPP_HOSTS) {\n if (!hostnameMatches(host, pattern)) {\n continue\n }\n if (pattern === \"whatsapp.com\") {\n return pathname.startsWith(\"/send\")\n }\n return true\n }\n return false\n}\n\nfunction isBookingHost(host: string): boolean {\n for (const pattern of BOOKING_LINK_HOSTS) {\n if (hostnameMatches(host, pattern)) {\n return true\n }\n }\n return false\n}\n\n/** Returns `null` when the link should not be tracked as a contact link. */\nexport function classifyContactHref(href: string): ClassifiedContactLink | null {\n const raw = href.trim()\n if (!raw || raw.startsWith(\"#\") || raw.startsWith(\"javascript:\")) {\n return null\n }\n\n const lower = raw.toLowerCase()\n\n if (lower.startsWith(\"tel:\")) {\n return { kind: \"call\" }\n }\n if (lower.startsWith(\"mailto:\")) {\n return { kind: \"email\" }\n }\n if (lower.startsWith(\"sms:\") || lower.startsWith(\"smsto:\")) {\n return { kind: \"contact_click\", channel: \"sms\", linkHost: null }\n }\n if (lower.startsWith(\"geo:\")) {\n return { kind: \"contact_click\", channel: \"maps\", linkHost: null }\n }\n\n // Skip anchors/relative paths that aren't a known contact scheme.\n if (raw.startsWith(\"/\") || raw.startsWith(\"?\")) {\n return null\n }\n\n let url: URL\n try {\n url = new URL(raw)\n } catch {\n return null\n }\n\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n return null\n }\n\n const host = url.hostname.toLowerCase()\n const pathname = url.pathname.toLowerCase()\n\n if (isWhatsAppHost(host, pathname)) {\n return { kind: \"contact_click\", channel: \"whatsapp\", linkHost: host }\n }\n if (isMapsHost(host, pathname)) {\n return { kind: \"contact_click\", channel: \"maps\", linkHost: host }\n }\n if (isBookingHost(host)) {\n return { kind: \"contact_click\", channel: \"booking\", linkHost: host }\n }\n\n return null\n}\n","/** Published package version — sent with capabilities for admin readiness. */\nexport const JMP_ANALYTICS_SDK_VERSION = \"0.1.23\"\n\n/** `localStorage` key when {@link JmpAnalyticsConfig.requireConsent} is enabled. */\nexport const JMP_TRACKING_CONSENT_STORAGE_KEY = \"jmp_analytics_consent_v1\"\n\n/** Default `pageViewDedupeMs` when resolving browser config from env (Strict Mode / SPA). */\nexport const DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS = 2500\n\n/** Marks links tracked explicitly — skips delegated `autoTrackContactLinks` handler. */\nexport const JMP_TRACKED_LINK_ATTR = \"data-jmp-tracked\"\n","import { captureLandingAttribution } from \"./landing-capture\"\nimport { JMP_TRACKING_CONSENT_STORAGE_KEY } from \"./constants\"\n\nlet moduleTrackingConsentGranted = true\nconst trackingConsentListeners = new Set<() => void>()\n\nfunction readStoredTrackingConsent(): boolean {\n if (typeof window === \"undefined\") {\n return false\n }\n try {\n return (\n localStorage.getItem(JMP_TRACKING_CONSENT_STORAGE_KEY) === \"granted\"\n )\n } catch {\n return false\n }\n}\n\n/** Initialize consent gate when creating a tracker (browser only). */\nexport function initJmpTrackingConsent(requireConsent: boolean | undefined): void {\n moduleTrackingConsentGranted = requireConsent\n ? readStoredTrackingConsent()\n : true\n}\n\n/** Whether analytics events may be sent (after CMP / cookie banner). */\nexport function isJmpTrackingConsentGranted(): boolean {\n return moduleTrackingConsentGranted\n}\n\n/** Subscribe to consent changes (for React `useSyncExternalStore`). */\nexport function subscribeJmpTrackingConsent(listener: () => void): () => void {\n trackingConsentListeners.add(listener)\n return () => {\n trackingConsentListeners.delete(listener)\n }\n}\n\n/**\n * Grant or deny analytics. Persists in `localStorage` when `requireConsent` is used.\n * Call from your cookie banner before tracking runs.\n */\nexport function setJmpTrackingConsent(granted: boolean): void {\n if (moduleTrackingConsentGranted === granted) {\n return\n }\n moduleTrackingConsentGranted = granted\n if (typeof window !== \"undefined\") {\n try {\n localStorage.setItem(\n JMP_TRACKING_CONSENT_STORAGE_KEY,\n granted ? \"granted\" : \"denied\"\n )\n } catch {\n // private mode\n }\n if (granted) {\n captureLandingAttribution(window.location.hostname)\n }\n }\n for (const listener of trackingConsentListeners) {\n listener()\n }\n}\n","export const JMP_PAGE_VIEW_DEDUPE_STORAGE_KEY = \"jmp_analytics_last_pv\"\n\nlet moduleLastPath = \"\"\nlet moduleLastAt = 0\n\n/**\n * Suppress duplicate browser `page_view` within `dedupeMs` (Strict Mode, App Router\n * searchParams resolution, multiple bundles). Uses pathname-only as the dedupe key.\n */\nexport function shouldDedupeBrowserPageView(\n pathname: string,\n dedupeMs: number,\n storage: Pick<Storage, \"getItem\" | \"setItem\"> | null = null,\n): boolean {\n if (dedupeMs <= 0) {\n return false\n }\n\n const now = Date.now()\n if (pathname === moduleLastPath && now - moduleLastAt < dedupeMs) {\n return true\n }\n\n try {\n const raw = storage?.getItem(JMP_PAGE_VIEW_DEDUPE_STORAGE_KEY)\n if (raw) {\n const prev = JSON.parse(raw) as { path: string; at: number }\n if (prev.path === pathname && now - prev.at < dedupeMs) {\n return true\n }\n }\n storage?.setItem(\n JMP_PAGE_VIEW_DEDUPE_STORAGE_KEY,\n JSON.stringify({ path: pathname, at: now }),\n )\n } catch {\n // private mode / disabled storage\n }\n\n moduleLastPath = pathname\n moduleLastAt = now\n return false\n}\n\n/** @internal test helper */\nexport function resetBrowserPageViewDedupeForTests(): void {\n moduleLastPath = \"\"\n moduleLastAt = 0\n}\n","import {\n captureLandingAttribution,\n JMP_UTM_PARAM_KEYS,\n} from \"../landing-capture\"\n\nexport function captureAndGetLandingReferer(): string | null {\n if (typeof window === \"undefined\") {\n return null\n }\n return captureLandingAttribution(window.location.hostname).landingReferer\n}\n\nexport function mergeSessionUtms(metadata: Record<string, unknown>) {\n if (typeof window === \"undefined\") {\n return\n }\n const { utm } = captureLandingAttribution(window.location.hostname)\n for (const key of JMP_UTM_PARAM_KEYS) {\n if (metadata[key] === undefined && utm[key]) {\n metadata[key] = utm[key]\n }\n }\n}\n\nexport function splitPathAndSearch(pathInput: string): {\n pathname: string\n search: string\n} {\n const qIndex = pathInput.indexOf(\"?\")\n if (qIndex === -1) {\n return { pathname: pathInput || \"/\", search: \"\" }\n }\n return {\n pathname: pathInput.slice(0, qIndex) || \"/\",\n search: pathInput.slice(qIndex + 1),\n }\n}\n\nexport function inferDevice(): string {\n if (typeof window === \"undefined\") {\n return \"unknown\"\n }\n return window.innerWidth < 768 ? \"mobile\" : \"desktop\"\n}\n","import { DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS } from \"../constants\"\nimport {\n captureLandingAttribution,\n getOrCreateSessionIdFromStorage,\n JMP_UTM_PARAM_KEYS,\n} from \"../landing-capture\"\nimport { shouldDedupeBrowserPageView } from \"./page-view-dedupe\"\nimport { classifyContactHref } from \"../contact-link-classifier\"\nimport type { ContactLinkChannel } from \"../contact-link-classifier\"\nimport {\n initJmpTrackingConsent,\n isJmpTrackingConsentGranted,\n setJmpTrackingConsent,\n} from \"../consent\"\nimport {\n formatApiErrorMessage,\n normalizeBaseUrl,\n resolveFetch,\n} from \"../http\"\nimport type {\n JmpAnalyticsConfig,\n JmpEventType,\n TrackLeadOptions,\n TrackPageViewOptions,\n} from \"../types\"\nimport {\n captureAndGetLandingReferer,\n inferDevice,\n mergeSessionUtms,\n splitPathAndSearch,\n} from \"./helpers\"\n\nexport function createJmpAnalytics(config: JmpAnalyticsConfig) {\n const baseUrl = normalizeBaseUrl(config.baseUrl)\n const fetchFn = resolveFetch(config.fetchImpl, \"createJmpAnalytics\")\n\n initJmpTrackingConsent(config.requireConsent)\n\n function canSend(): boolean {\n return isJmpTrackingConsentGranted()\n }\n\n async function send(body: Record<string, unknown>) {\n if (!canSend()) {\n return\n }\n\n const url = `${baseUrl}/api/events`\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-JMP-Tracking-Key\": config.trackingKey,\n }\n\n if (body.type === \"page_view\" && typeof window !== \"undefined\") {\n const landingReferer = captureAndGetLandingReferer()\n if (landingReferer) {\n headers[\"X-JMP-Visitor-Referer\"] = landingReferer\n }\n }\n\n const sid = getOrCreateSessionIdFromStorage()\n const payload = { ...body, ...(sid ? { sessionId: sid } : {}) }\n\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"POST\", url, payload)\n }\n\n const res = await fetchFn(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n keepalive: true,\n })\n\n if (!res.ok) {\n const text = await res.text()\n const message = formatApiErrorMessage(res.status, text, \"JMP Analytics\")\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"error response\", { status: res.status, raw: text })\n }\n throw new Error(message)\n }\n }\n\n async function sendLead(body: Record<string, unknown>) {\n if (!canSend()) {\n return\n }\n\n const url = `${baseUrl}/api/leads`\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-JMP-Tracking-Key\": config.trackingKey,\n }\n\n const sid = getOrCreateSessionIdFromStorage()\n const payload = { ...body, ...(sid ? { sessionId: sid } : {}) }\n\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"POST\", url, payload)\n }\n\n const res = await fetchFn(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n keepalive: true,\n })\n\n if (!res.ok) {\n const text = await res.text()\n const message = formatApiErrorMessage(res.status, text, \"JMP Analytics\")\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"error response\", { status: res.status, raw: text })\n }\n throw new Error(message)\n }\n }\n\n function trackPageView(options?: TrackPageViewOptions) {\n if (!canSend()) {\n return Promise.resolve()\n }\n\n const pathFromOptions = options?.path?.trim()\n let pathname = \"/\"\n let search = \"\"\n\n if (pathFromOptions) {\n const split = splitPathAndSearch(pathFromOptions)\n pathname = split.pathname\n search = split.search\n } else if (typeof window !== \"undefined\") {\n pathname = window.location.pathname\n search = window.location.search.replace(/^\\?/, \"\")\n }\n\n if (!search && typeof window !== \"undefined\") {\n search = window.location.search.replace(/^\\?/, \"\")\n }\n\n const path = search ? `${pathname}?${search}` : pathname\n\n const dedupeMs =\n config.pageViewDedupeMs ?? DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS\n const storage =\n typeof window !== \"undefined\" ? window.sessionStorage : null\n if (shouldDedupeBrowserPageView(pathname, dedupeMs, storage)) {\n return Promise.resolve()\n }\n\n const metadata: Record<string, unknown> =\n options?.metadata && typeof options.metadata === \"object\"\n ? { ...options.metadata }\n : {}\n\n if (typeof window !== \"undefined\") {\n metadata.pageHostname = window.location.hostname\n const params = new URLSearchParams(\n search ? `?${search}` : window.location.search\n )\n for (const key of JMP_UTM_PARAM_KEYS) {\n const v = params.get(key)\n if (v && metadata[key] === undefined) {\n metadata[key] = v\n }\n }\n mergeSessionUtms(metadata)\n const landingReferer = captureAndGetLandingReferer()\n if (landingReferer && metadata.referer === undefined) {\n metadata.referer = landingReferer\n }\n }\n\n return send({\n type: \"page_view\",\n path: path || \"/\",\n device: options?.device ?? inferDevice(),\n country: options?.country,\n metadata,\n })\n }\n\n function trackFormSubmit(\n path: string,\n metadata?: Record<string, unknown>\n ) {\n return send({\n type: \"form_submit\",\n path: path || \"/\",\n metadata: metadata ?? {},\n })\n }\n\n function trackCallClick(\n path: string,\n metadata?: Record<string, unknown>\n ) {\n return send({\n type: \"call_click\",\n path: path || \"/\",\n metadata: metadata ?? {},\n })\n }\n\n function trackEmailClick(\n path: string,\n metadata?: Record<string, unknown>\n ) {\n return send({\n type: \"email_click\",\n path: path || \"/\",\n metadata: metadata ?? {},\n })\n }\n\n function trackContactClick(\n path: string,\n options: {\n channel: ContactLinkChannel\n linkHost?: string | null\n metadata?: Record<string, unknown>\n }\n ) {\n const metadata: Record<string, unknown> = {\n ...(options.metadata && typeof options.metadata === \"object\"\n ? options.metadata\n : {}),\n channel: options.channel,\n }\n if (options.linkHost) {\n metadata.linkHost = options.linkHost\n }\n return send({\n type: \"contact_click\",\n path: path || \"/\",\n metadata,\n })\n }\n\n function trackLead(options?: TrackLeadOptions) {\n const path =\n options?.path ??\n (typeof window !== \"undefined\" ? window.location.pathname : \"/\")\n\n const body: Record<string, unknown> = { path: path || \"/\" }\n if (options?.name) body.name = options.name\n if (options?.email) body.email = options.email\n if (options?.phone) body.phone = options.phone\n if (options?.company) body.company = options.company\n if (options?.website) body.website = options.website\n if (options?.subject) body.subject = options.subject\n if (options?.message) body.message = options.message\n if (options?.metadata && typeof options.metadata === \"object\") {\n body.metadata = options.metadata\n }\n if (options?.sessionId) body.sessionId = options.sessionId\n\n return sendLead(body)\n }\n\n function track(\n type: JmpEventType,\n payload: {\n path: string\n metadata?: Record<string, unknown>\n source?: string\n device?: string\n country?: string\n }\n ) {\n if (type === \"page_view\") {\n return trackPageView({\n path: payload.path,\n source: payload.source,\n device: payload.device,\n country: payload.country,\n metadata: payload.metadata,\n })\n }\n if (type === \"form_submit\") {\n return trackFormSubmit(payload.path, payload.metadata)\n }\n if (type === \"call_click\") {\n return trackCallClick(payload.path, payload.metadata)\n }\n if (type === \"email_click\") {\n return trackEmailClick(payload.path, payload.metadata)\n }\n const meta = payload.metadata ?? {}\n const channel = typeof meta.channel === \"string\" ? meta.channel : \"\"\n const linkHost =\n typeof meta.linkHost === \"string\" ? (meta.linkHost as string) : null\n if (\n channel !== \"sms\" &&\n channel !== \"whatsapp\" &&\n channel !== \"maps\" &&\n channel !== \"booking\"\n ) {\n return Promise.resolve()\n }\n return trackContactClick(payload.path, {\n channel: channel as ContactLinkChannel,\n linkHost,\n metadata: meta,\n })\n }\n\n /**\n * Fire `page_view` on browser back/forward and when `history.pushState` /\n * `replaceState` are used (many SPAs). Call once after creating the tracker.\n * Returns an unsubscribe function.\n */\n function subscribeToSpaNavigation(): () => void {\n if (typeof window === \"undefined\") {\n return () => {}\n }\n\n let lastPath = window.location.pathname\n\n const run = () => {\n const path = window.location.pathname\n if (path !== lastPath) {\n lastPath = path\n void trackPageView({ path })\n }\n }\n\n window.addEventListener(\"popstate\", run)\n\n const push = history.pushState.bind(history)\n const replace = history.replaceState.bind(history)\n\n history.pushState = (...args: Parameters<History[\"pushState\"]>) => {\n const r = push(...args)\n queueMicrotask(run)\n return r\n }\n history.replaceState = (...args: Parameters<History[\"replaceState\"]>) => {\n const r = replace(...args)\n queueMicrotask(run)\n return r\n }\n\n return () => {\n window.removeEventListener(\"popstate\", run)\n history.pushState = push\n history.replaceState = replace\n }\n }\n\n let lastContactClickKey = \"\"\n let lastContactClickAt = 0\n\n /**\n * Capture-phase `click` listener that classifies outgoing links and records\n * the corresponding contact event. Returns an unsubscribe function. Safe to\n * call only in the browser; no-op on the server.\n */\n function subscribeContactLinkAutoTrack(): () => void {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n return () => {}\n }\n\n const handler = (event: MouseEvent) => {\n if (!canSend()) {\n return\n }\n const target = event.target\n if (!(target instanceof Element)) {\n return\n }\n const anchor = target.closest(\"a[href]\")\n if (!(anchor instanceof HTMLAnchorElement)) {\n return\n }\n if (\n anchor.closest(\"[data-jmp-no-track]\") ||\n anchor.closest(\"[data-jmp-tracked]\")\n ) {\n return\n }\n\n const href = anchor.getAttribute(\"href\") ?? \"\"\n const classified = classifyContactHref(href)\n if (!classified) {\n return\n }\n\n const path = window.location.pathname\n\n if (classified.kind === \"call\") {\n if (dedupeContactClick(`call_click:${path}`)) {\n return\n }\n void trackCallClick(path)\n return\n }\n if (classified.kind === \"email\") {\n if (dedupeContactClick(`email_click:${path}`)) {\n return\n }\n void trackEmailClick(path)\n return\n }\n if (dedupeContactClick(`contact_click:${classified.channel}:${path}`)) {\n return\n }\n void trackContactClick(path, {\n channel: classified.channel,\n linkHost: classified.linkHost,\n })\n }\n\n document.addEventListener(\"click\", handler, { capture: true })\n return () => {\n document.removeEventListener(\"click\", handler, { capture: true } as EventListenerOptions)\n }\n }\n\n function dedupeContactClick(key: string): boolean {\n const now = Date.now()\n if (key === lastContactClickKey && now - lastContactClickAt < 1000) {\n return true\n }\n lastContactClickKey = key\n lastContactClickAt = now\n return false\n }\n\n if (typeof window !== \"undefined\" && canSend()) {\n captureLandingAttribution(window.location.hostname)\n }\n\n if (\n typeof window !== \"undefined\" &&\n config.autoTrackContactLinks &&\n canSend()\n ) {\n subscribeContactLinkAutoTrack()\n }\n\n return {\n track,\n trackPageView,\n trackFormSubmit,\n trackCallClick,\n trackEmailClick,\n trackContactClick,\n trackLead,\n subscribeToSpaNavigation,\n subscribeContactLinkAutoTrack,\n setTrackingConsent: setJmpTrackingConsent,\n isTrackingConsentGranted: isJmpTrackingConsentGranted,\n }\n}\n\nexport type JmpAnalytics = ReturnType<typeof createJmpAnalytics>\n","import { normalizeBaseUrl, resolveFetch, responseLooksLikeHtml } from \"../http\"\nimport type { JmpContentConfig } from \"../types\"\n\nexport async function getContent<T>(\n config: JmpContentConfig,\n path: string\n): Promise<T> {\n const text = await getContentText(config, path)\n\n try {\n return JSON.parse(text) as T\n } catch {\n const baseUrl = normalizeBaseUrl(config.baseUrl)\n const url = `${baseUrl}${path}`\n throw new Error(`JMP content HTTP 200 — ${url} returned invalid JSON.`)\n }\n}\n\nexport async function getContentText(\n config: JmpContentConfig,\n path: string\n): Promise<string> {\n const baseUrl = normalizeBaseUrl(config.baseUrl)\n const resolvedFetch =\n config.fetchImpl ??\n (typeof globalThis !== \"undefined\" && globalThis.fetch\n ? globalThis.fetch.bind(globalThis)\n : undefined)\n\n if (!resolvedFetch) {\n throw new Error(\"JMP content: fetch is not available; pass fetchImpl in config\")\n }\n\n const url = `${baseUrl}${path}`\n\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"GET\", url)\n }\n\n const res = await resolvedFetch(url, {\n method: \"GET\",\n headers: {\n \"X-JMP-Tracking-Key\": config.trackingKey,\n },\n })\n\n const text = await res.text()\n\n if (!res.ok) {\n if (responseLooksLikeHtml(text)) {\n throw new Error(\n `JMP content HTTP ${res.status} — ${url} returned HTML (not the JSON API). ` +\n `Set baseUrl to your JMP Analytics dashboard origin.`\n )\n }\n\n let parsed: { error?: string; hint?: string } = {}\n try {\n parsed = JSON.parse(text) as typeof parsed\n } catch {\n parsed = { error: text }\n }\n\n throw new Error(\n [`JMP content HTTP ${res.status}`, parsed.error, parsed.hint]\n .filter(Boolean)\n .join(\" — \")\n )\n }\n\n return text\n}\n","import { JMP_ANALYTICS_SDK_VERSION } from \"../constants\"\nimport { normalizeBaseUrl, resolveFetch } from \"../http\"\nimport type {\n JmpBlogPost,\n JmpContentConfig,\n JmpFaqItem,\n JmpSiteProfile,\n JmpTrustBundle,\n} from \"../types\"\nimport { getContent, getContentText } from \"./fetch\"\n\nexport async function getJmpBlogPosts(\n config: JmpContentConfig\n): Promise<JmpBlogPost[]> {\n const data = await getContent<{ posts: JmpBlogPost[] }>(\n config,\n \"/api/content/blog\"\n )\n return data.posts\n}\n\nexport async function getJmpBlogPost(\n config: JmpContentConfig,\n slug: string\n): Promise<JmpBlogPost | null> {\n try {\n const data = await getContent<{ post: JmpBlogPost }>(\n config,\n `/api/content/blog/${encodeURIComponent(slug)}`\n )\n return data.post\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"HTTP 404\")) {\n return null\n }\n throw error\n }\n}\n\nexport async function getJmpFaqs(\n config: JmpContentConfig\n): Promise<JmpFaqItem[]> {\n const data = await getContent<{ faqs: JmpFaqItem[] }>(\n config,\n \"/api/content/faqs\"\n )\n return data.faqs\n}\n\nexport async function getJmpTrustBundle(\n config: JmpContentConfig\n): Promise<JmpTrustBundle> {\n return getContent<JmpTrustBundle>(config, \"/api/content/trust\")\n}\n\nexport async function getJmpSiteProfile(\n config: JmpContentConfig\n): Promise<JmpSiteProfile> {\n const data = await getContent<{ site: JmpSiteProfile }>(\n config,\n \"/api/content/site\"\n )\n return data.site\n}\n\nexport async function getJmpRobotsTxt(config: JmpContentConfig): Promise<string> {\n return getContentText(config, \"/api/content/robots\")\n}\n\nconst registeredContentWrappers = new Set<string>()\n\nfunction resolveContentWrappersFromConfig(\n config: JmpContentConfig\n): string[] {\n if (config.contentWrappers?.length) {\n return [...config.contentWrappers]\n }\n return []\n}\n\n/**\n * Tell the JMP dashboard which content wrappers this site wires (blog, faq, trust.*, contact).\n * Safe to call on every cold start; dedupes by tracking key + wrapper list.\n */\nexport async function registerJmpContentWrappers(\n config: JmpContentConfig\n): Promise<\n { ok: true; wrappers: string[] } | { ok: false; skipped: true }\n> {\n const wrappers = resolveContentWrappersFromConfig(config)\n if (!wrappers.length) {\n return { ok: false, skipped: true }\n }\n\n const dedupeKey = `${config.trackingKey}:${[...wrappers].sort().join(\",\")}`\n if (registeredContentWrappers.has(dedupeKey)) {\n return { ok: true, wrappers }\n }\n\n const baseUrl = normalizeBaseUrl(config.baseUrl)\n const resolvedFetch =\n config.fetchImpl ??\n (typeof globalThis !== \"undefined\" && globalThis.fetch\n ? globalThis.fetch.bind(globalThis)\n : undefined)\n\n if (!resolvedFetch) {\n throw new Error(\n \"JMP content wrappers: fetch is not available; pass fetchImpl in config\"\n )\n }\n\n const url = `${baseUrl}/api/content/capabilities`\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"POST\", url, { wrappers })\n }\n\n const res = await resolvedFetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-JMP-Tracking-Key\": config.trackingKey,\n },\n body: JSON.stringify({\n wrappers,\n sdkVersion: JMP_ANALYTICS_SDK_VERSION,\n }),\n })\n\n const text = await res.text()\n if (!res.ok) {\n let parsed: { error?: string; hint?: string } = {}\n try {\n parsed = JSON.parse(text) as typeof parsed\n } catch {\n parsed = { error: text }\n }\n throw new Error(\n [`JMP content wrappers HTTP ${res.status}`, parsed.error, parsed.hint]\n .filter(Boolean)\n .join(\" — \")\n )\n }\n\n registeredContentWrappers.add(dedupeKey)\n const data = JSON.parse(text) as { wrappers?: string[] }\n return { ok: true, wrappers: data.wrappers ?? wrappers }\n}\n","import {\n getJmpBlogPost,\n getJmpBlogPosts,\n getJmpFaqs,\n getJmpRobotsTxt,\n getJmpSiteProfile,\n getJmpTrustBundle,\n registerJmpContentWrappers,\n} from \"./api\"\nimport type { JmpContentConfig } from \"../types\"\n\nexport function createJmpContentClient(config: JmpContentConfig) {\n return {\n getBlogPosts: () => getJmpBlogPosts(config),\n getBlogPost: (slug: string) => getJmpBlogPost(config, slug),\n getFaqs: () => getJmpFaqs(config),\n getTrustBundle: () => getJmpTrustBundle(config),\n getSiteProfile: () => getJmpSiteProfile(config),\n getRobotsTxt: () => getJmpRobotsTxt(config),\n registerContentWrappers: () => registerJmpContentWrappers(config),\n }\n}\n\nexport type JmpContentClient = ReturnType<typeof createJmpContentClient>\n","import {\n createJmpAnalytics,\n type JmpAnalytics,\n} from \"../tracker/create-jmp-analytics\"\nimport { DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS } from \"../constants\"\nimport { readJmpPublicEnvFromProcess } from \"../env-public\"\nimport type { JmpAnalyticsConfig, JmpContentConfig } from \"../types\"\nimport { createJmpContentClient, type JmpContentClient } from \"../content/client\"\n\n/**\n * Browser / client bundle: reads `NEXT_PUBLIC_*` or Astro `PUBLIC_*` vars.\n * Use for `createJmpAnalytics` on the client. Returns `null` if incomplete.\n */\nexport function getJmpBrowserAnalyticsConfigFromEnv(\n overrides?: Partial<JmpAnalyticsConfig>\n): JmpAnalyticsConfig | null {\n const pub = readJmpPublicEnvFromProcess()\n const baseUrl = overrides?.baseUrl ?? pub.analyticsUrl\n const trackingKey = overrides?.trackingKey ?? pub.trackingKey\n if (!baseUrl || !trackingKey) return null\n return {\n baseUrl,\n trackingKey,\n pageViewDedupeMs: DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,\n ...overrides,\n requireConsent: overrides?.requireConsent ?? pub.requireConsent,\n autoTrackContactLinks:\n overrides?.autoTrackContactLinks ?? pub.autoContactLinks,\n }\n}\n\n/**\n * Server / Node (SSR, Route Handlers, `getStaticProps`, etc.):\n * Same **`NEXT_PUBLIC_*`** vars as the browser (`NEXT_PUBLIC_JMP_ANALYTICS_URL` +\n * `NEXT_PUBLIC_JMP_TRACKING_KEY`). Next.js injects these on the server for RSC/fetches.\n * Returns `null` if incomplete.\n */\nexport function getJmpServerContentConfigFromEnv(\n overrides?: Partial<JmpContentConfig>\n): JmpContentConfig | null {\n const pub = readJmpPublicEnvFromProcess()\n const baseUrl = overrides?.baseUrl ?? pub.analyticsUrl\n const trackingKey = overrides?.trackingKey ?? pub.trackingKey\n if (!baseUrl || !trackingKey) return null\n return { baseUrl, trackingKey, ...overrides }\n}\n\nexport function isJmpBrowserAnalyticsEnvConfigured(): boolean {\n const pub = readJmpPublicEnvFromProcess()\n return Boolean(pub.analyticsUrl && pub.trackingKey)\n}\n\nexport function isJmpServerContentEnvConfigured(): boolean {\n return isJmpBrowserAnalyticsEnvConfigured()\n}\n\nlet jmpAnalyticsBrowserSingleton: JmpAnalytics | null = null\n\n/**\n * One `JmpAnalytics` instance per browser tab (module singleton). Safe for page views and events.\n * Returns `null` on the server or when public env vars are missing.\n */\nexport function getOrCreateJmpAnalyticsFromBrowserEnv(\n overrides?: Partial<JmpAnalyticsConfig>\n): JmpAnalytics | null {\n if (typeof window === \"undefined\") return null\n if (jmpAnalyticsBrowserSingleton) return jmpAnalyticsBrowserSingleton\n const config = getJmpBrowserAnalyticsConfigFromEnv(overrides)\n if (!config) return null\n jmpAnalyticsBrowserSingleton = createJmpAnalytics(config)\n return jmpAnalyticsBrowserSingleton\n}\n\n/**\n * `createJmpContentClient` using server env resolution. Returns `null` if env is incomplete.\n */\nexport function createJmpContentClientFromServerEnv(\n overrides?: Partial<JmpContentConfig>\n): JmpContentClient | null {\n const config = getJmpServerContentConfigFromEnv(overrides)\n if (!config) return null\n return createJmpContentClient(config)\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Public env vars for JMP — supports Next.js (`NEXT_PUBLIC_*`) and Astro (`PUBLIC_*`).
3
+ * Server/build: `process.env`. Astro client bundles: pass overrides from `import.meta.env`.
4
+ */
5
+ type JmpPublicEnv = {
6
+ analyticsUrl?: string;
7
+ trackingKey?: string;
8
+ requireConsent?: boolean;
9
+ autoContactLinks?: boolean;
10
+ };
11
+ /** Read dashboard URL + tracking key from Node / build-time env. */
12
+ declare function readJmpPublicEnvFromProcess(overrides?: Partial<JmpPublicEnv>): JmpPublicEnv;
13
+
14
+ export { type JmpPublicEnv as J, readJmpPublicEnvFromProcess as r };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Public env vars for JMP — supports Next.js (`NEXT_PUBLIC_*`) and Astro (`PUBLIC_*`).
3
+ * Server/build: `process.env`. Astro client bundles: pass overrides from `import.meta.env`.
4
+ */
5
+ type JmpPublicEnv = {
6
+ analyticsUrl?: string;
7
+ trackingKey?: string;
8
+ requireConsent?: boolean;
9
+ autoContactLinks?: boolean;
10
+ };
11
+ /** Read dashboard URL + tracking key from Node / build-time env. */
12
+ declare function readJmpPublicEnvFromProcess(overrides?: Partial<JmpPublicEnv>): JmpPublicEnv;
13
+
14
+ export { type JmpPublicEnv as J, readJmpPublicEnvFromProcess as r };
package/dist/index.cjs CHANGED
@@ -44,6 +44,7 @@ __export(index_exports, {
44
44
  isJmpBrowserAnalyticsEnvConfigured: () => isJmpBrowserAnalyticsEnvConfigured,
45
45
  isJmpServerContentEnvConfigured: () => isJmpServerContentEnvConfigured,
46
46
  isJmpTrackingConsentGranted: () => isJmpTrackingConsentGranted,
47
+ readJmpPublicEnvFromProcess: () => readJmpPublicEnvFromProcess,
47
48
  registerJmpContentWrappers: () => registerJmpContentWrappers,
48
49
  setJmpTrackingConsent: () => setJmpTrackingConsent,
49
50
  subscribeJmpTrackingConsent: () => subscribeJmpTrackingConsent,
@@ -313,9 +314,9 @@ function classifyContactHref(href) {
313
314
  }
314
315
 
315
316
  // src/constants.ts
316
- var JMP_ANALYTICS_SDK_VERSION = "0.1.21";
317
+ var JMP_ANALYTICS_SDK_VERSION = "0.1.23";
317
318
  var JMP_TRACKING_CONSENT_STORAGE_KEY = "jmp_analytics_consent_v1";
318
- var DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS = 800;
319
+ var DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS = 2500;
319
320
 
320
321
  // src/consent.ts
321
322
  var moduleTrackingConsentGranted = true;
@@ -364,6 +365,37 @@ function setJmpTrackingConsent(granted) {
364
365
  }
365
366
  }
366
367
 
368
+ // src/tracker/page-view-dedupe.ts
369
+ var JMP_PAGE_VIEW_DEDUPE_STORAGE_KEY = "jmp_analytics_last_pv";
370
+ var moduleLastPath = "";
371
+ var moduleLastAt = 0;
372
+ function shouldDedupeBrowserPageView(pathname, dedupeMs, storage = null) {
373
+ if (dedupeMs <= 0) {
374
+ return false;
375
+ }
376
+ const now = Date.now();
377
+ if (pathname === moduleLastPath && now - moduleLastAt < dedupeMs) {
378
+ return true;
379
+ }
380
+ try {
381
+ const raw = storage?.getItem(JMP_PAGE_VIEW_DEDUPE_STORAGE_KEY);
382
+ if (raw) {
383
+ const prev = JSON.parse(raw);
384
+ if (prev.path === pathname && now - prev.at < dedupeMs) {
385
+ return true;
386
+ }
387
+ }
388
+ storage?.setItem(
389
+ JMP_PAGE_VIEW_DEDUPE_STORAGE_KEY,
390
+ JSON.stringify({ path: pathname, at: now })
391
+ );
392
+ } catch {
393
+ }
394
+ moduleLastPath = pathname;
395
+ moduleLastAt = now;
396
+ return false;
397
+ }
398
+
367
399
  // src/http.ts
368
400
  function responseLooksLikeHtml(body) {
369
401
  const s = body.trimStart();
@@ -467,8 +499,6 @@ function createJmpAnalytics(config) {
467
499
  function canSend() {
468
500
  return isJmpTrackingConsentGranted();
469
501
  }
470
- let lastDedupePath = "";
471
- let lastDedupeAt = 0;
472
502
  async function send(body) {
473
503
  if (!canSend()) {
474
504
  return;
@@ -533,19 +563,6 @@ function createJmpAnalytics(config) {
533
563
  throw new Error(message);
534
564
  }
535
565
  }
536
- function shouldDedupePageView(path) {
537
- const ms = config.pageViewDedupeMs ?? 0;
538
- if (ms <= 0 || typeof window === "undefined") {
539
- return false;
540
- }
541
- const now = Date.now();
542
- if (path === lastDedupePath && now - lastDedupeAt < ms) {
543
- return true;
544
- }
545
- lastDedupePath = path;
546
- lastDedupeAt = now;
547
- return false;
548
- }
549
566
  function trackPageView(options) {
550
567
  if (!canSend()) {
551
568
  return Promise.resolve();
@@ -565,7 +582,9 @@ function createJmpAnalytics(config) {
565
582
  search = window.location.search.replace(/^\?/, "");
566
583
  }
567
584
  const path = search ? `${pathname}?${search}` : pathname;
568
- if (shouldDedupePageView(pathname)) {
585
+ const dedupeMs = config.pageViewDedupeMs ?? DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS;
586
+ const storage = typeof window !== "undefined" ? window.sessionStorage : null;
587
+ if (shouldDedupeBrowserPageView(pathname, dedupeMs, storage)) {
569
588
  return Promise.resolve();
570
589
  }
571
590
  const metadata = options?.metadata && typeof options.metadata === "object" ? { ...options.metadata } : {};
@@ -727,7 +746,7 @@ function createJmpAnalytics(config) {
727
746
  if (!(anchor instanceof HTMLAnchorElement)) {
728
747
  return;
729
748
  }
730
- if (anchor.closest("[data-jmp-no-track]")) {
749
+ if (anchor.closest("[data-jmp-no-track]") || anchor.closest("[data-jmp-tracked]")) {
731
750
  return;
732
751
  }
733
752
  const href = anchor.getAttribute("href") ?? "";
@@ -994,37 +1013,32 @@ function createJmpContentClient(config) {
994
1013
  }
995
1014
 
996
1015
  // src/env-public.ts
997
- function readProcessEnv(name) {
998
- if (typeof process === "undefined" || !process.env) return void 0;
999
- const v = process.env[name];
1000
- return typeof v === "string" && v.length > 0 ? v : void 0;
1001
- }
1002
1016
  function firstDefined(...values) {
1003
1017
  for (const v of values) {
1004
1018
  if (v) return v;
1005
1019
  }
1006
1020
  return void 0;
1007
1021
  }
1008
- function readJmpPublicEnvFromProcess(overrides) {
1022
+ function readJmpPublicEnvFromProcessEnv() {
1023
+ if (typeof process === "undefined" || !process.env) {
1024
+ return {};
1025
+ }
1026
+ const { env } = process;
1009
1027
  const analyticsUrl = firstDefined(
1010
- overrides?.analyticsUrl,
1011
- readProcessEnv("NEXT_PUBLIC_JMP_ANALYTICS_URL"),
1012
- readProcessEnv("PUBLIC_JMP_ANALYTICS_URL")
1028
+ env.NEXT_PUBLIC_JMP_ANALYTICS_URL,
1029
+ env.PUBLIC_JMP_ANALYTICS_URL
1013
1030
  );
1014
1031
  const trackingKey = firstDefined(
1015
- overrides?.trackingKey,
1016
- readProcessEnv("NEXT_PUBLIC_JMP_TRACKING_KEY"),
1017
- readProcessEnv("PUBLIC_JMP_TRACKING_KEY")
1032
+ env.NEXT_PUBLIC_JMP_TRACKING_KEY,
1033
+ env.PUBLIC_JMP_TRACKING_KEY
1018
1034
  );
1019
1035
  const requireConsentRaw = firstDefined(
1020
- overrides?.requireConsent !== void 0 ? String(overrides.requireConsent) : void 0,
1021
- readProcessEnv("NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT"),
1022
- readProcessEnv("PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT")
1036
+ env.NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT,
1037
+ env.PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT
1023
1038
  );
1024
1039
  const autoContactRaw = firstDefined(
1025
- overrides?.autoContactLinks !== void 0 ? String(overrides.autoContactLinks) : void 0,
1026
- readProcessEnv("NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS"),
1027
- readProcessEnv("PUBLIC_JMP_AUTO_CONTACT_LINKS")
1040
+ env.NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS,
1041
+ env.PUBLIC_JMP_AUTO_CONTACT_LINKS
1028
1042
  );
1029
1043
  return {
1030
1044
  analyticsUrl,
@@ -1033,14 +1047,25 @@ function readJmpPublicEnvFromProcess(overrides) {
1033
1047
  autoContactLinks: autoContactRaw === "true"
1034
1048
  };
1035
1049
  }
1050
+ function readJmpPublicEnvFromProcess(overrides) {
1051
+ const fromEnv = readJmpPublicEnvFromProcessEnv();
1052
+ return {
1053
+ analyticsUrl: firstDefined(overrides?.analyticsUrl, fromEnv.analyticsUrl),
1054
+ trackingKey: firstDefined(overrides?.trackingKey, fromEnv.trackingKey),
1055
+ requireConsent: overrides?.requireConsent ?? fromEnv.requireConsent,
1056
+ autoContactLinks: overrides?.autoContactLinks ?? fromEnv.autoContactLinks
1057
+ };
1058
+ }
1036
1059
 
1037
1060
  // src/env/browser.ts
1038
1061
  function getJmpBrowserAnalyticsConfigFromEnv(overrides) {
1039
1062
  const pub = readJmpPublicEnvFromProcess();
1040
- if (!pub.analyticsUrl || !pub.trackingKey) return null;
1063
+ const baseUrl = overrides?.baseUrl ?? pub.analyticsUrl;
1064
+ const trackingKey = overrides?.trackingKey ?? pub.trackingKey;
1065
+ if (!baseUrl || !trackingKey) return null;
1041
1066
  return {
1042
- baseUrl: pub.analyticsUrl,
1043
- trackingKey: pub.trackingKey,
1067
+ baseUrl,
1068
+ trackingKey,
1044
1069
  pageViewDedupeMs: DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,
1045
1070
  ...overrides,
1046
1071
  requireConsent: overrides?.requireConsent ?? pub.requireConsent,
@@ -1049,8 +1074,10 @@ function getJmpBrowserAnalyticsConfigFromEnv(overrides) {
1049
1074
  }
1050
1075
  function getJmpServerContentConfigFromEnv(overrides) {
1051
1076
  const pub = readJmpPublicEnvFromProcess();
1052
- if (!pub.analyticsUrl || !pub.trackingKey) return null;
1053
- return { baseUrl: pub.analyticsUrl, trackingKey: pub.trackingKey, ...overrides };
1077
+ const baseUrl = overrides?.baseUrl ?? pub.analyticsUrl;
1078
+ const trackingKey = overrides?.trackingKey ?? pub.trackingKey;
1079
+ if (!baseUrl || !trackingKey) return null;
1080
+ return { baseUrl, trackingKey, ...overrides };
1054
1081
  }
1055
1082
  function isJmpBrowserAnalyticsEnvConfigured() {
1056
1083
  const pub = readJmpPublicEnvFromProcess();
@@ -1121,6 +1148,7 @@ function getJmpContentConfig() {
1121
1148
  isJmpBrowserAnalyticsEnvConfigured,
1122
1149
  isJmpServerContentEnvConfigured,
1123
1150
  isJmpTrackingConsentGranted,
1151
+ readJmpPublicEnvFromProcess,
1124
1152
  registerJmpContentWrappers,
1125
1153
  setJmpTrackingConsent,
1126
1154
  subscribeJmpTrackingConsent,