@jmp-technologies/analytics 0.1.21 → 0.1.22

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 (57) hide show
  1. package/README.md +7 -7
  2. package/dist/astro/client.cjs.map +1 -1
  3. package/dist/astro/client.d.cts +1 -11
  4. package/dist/astro/client.d.ts +1 -11
  5. package/dist/astro/client.js +2 -2
  6. package/dist/astro/content.cjs +22 -18
  7. package/dist/astro/content.cjs.map +1 -1
  8. package/dist/astro/content.js +3 -3
  9. package/dist/astro/integration.cjs +22 -18
  10. package/dist/astro/integration.cjs.map +1 -1
  11. package/dist/astro/integration.js +3 -3
  12. package/dist/astro/middleware.cjs +22 -18
  13. package/dist/astro/middleware.cjs.map +1 -1
  14. package/dist/astro/middleware.js +2 -2
  15. package/dist/astro/register.cjs +22 -18
  16. package/dist/astro/register.cjs.map +1 -1
  17. package/dist/astro/register.js +3 -3
  18. package/dist/{chunk-XA55GBSU.js → chunk-3U2BSIYX.js} +3 -3
  19. package/dist/{chunk-GLEOJIKL.js → chunk-4OOWA55U.js} +23 -19
  20. package/dist/chunk-4OOWA55U.js.map +1 -0
  21. package/dist/{chunk-PDOQ2XBW.js → chunk-AHRVNEFV.js} +11 -7
  22. package/dist/{chunk-PDOQ2XBW.js.map → chunk-AHRVNEFV.js.map} +1 -1
  23. package/dist/{chunk-DXHJGKIL.js → chunk-DQ6A6HTY.js} +12 -8
  24. package/dist/chunk-DQ6A6HTY.js.map +1 -0
  25. package/dist/{chunk-6TDPOZMJ.js → chunk-IYFVJ2EY.js} +3 -3
  26. package/dist/{chunk-TK4VRDMO.js → chunk-TEFKM3GW.js} +2 -2
  27. package/dist/env-public-B6Y0DG68.d.cts +14 -0
  28. package/dist/env-public-B6Y0DG68.d.ts +14 -0
  29. package/dist/index.cjs +33 -23
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +1 -0
  32. package/dist/index.d.ts +1 -0
  33. package/dist/index.js +4 -2
  34. package/dist/next/content.cjs +22 -18
  35. package/dist/next/content.cjs.map +1 -1
  36. package/dist/next/content.js +3 -3
  37. package/dist/next/instrumentation.cjs +22 -18
  38. package/dist/next/instrumentation.cjs.map +1 -1
  39. package/dist/next/instrumentation.js +2 -2
  40. package/dist/next/middleware.cjs +22 -18
  41. package/dist/next/middleware.cjs.map +1 -1
  42. package/dist/next/middleware.js +2 -2
  43. package/dist/next.cjs +97 -55
  44. package/dist/next.cjs.map +1 -1
  45. package/dist/next.d.cts +13 -3
  46. package/dist/next.d.ts +13 -3
  47. package/dist/next.js +45 -8
  48. package/dist/next.js.map +1 -1
  49. package/dist/react.cjs +37 -27
  50. package/dist/react.cjs.map +1 -1
  51. package/dist/react.js +3 -3
  52. package/package.json +1 -1
  53. package/dist/chunk-DXHJGKIL.js.map +0 -1
  54. package/dist/chunk-GLEOJIKL.js.map +0 -1
  55. /package/dist/{chunk-XA55GBSU.js.map → chunk-3U2BSIYX.js.map} +0 -0
  56. /package/dist/{chunk-6TDPOZMJ.js.map → chunk-IYFVJ2EY.js.map} +0 -0
  57. /package/dist/{chunk-TK4VRDMO.js.map → chunk-TEFKM3GW.js.map} +0 -0
package/README.md CHANGED
@@ -33,27 +33,27 @@ Set **`NEXT_PUBLIC_JMP_ANALYTICS_URL`** and **`NEXT_PUBLIC_JMP_TRACKING_KEY`** (
33
33
  ### Next.js App Router (least client code)
34
34
 
35
35
  1. Install peers: **`react`**, **`react-dom`**, **`next`** (already present in a Next app).
36
- 2. In **`app/layout.tsx`** (or the root layout that wraps all pages), add **one** client line:
36
+ 2. Set **`NEXT_PUBLIC_JMP_ANALYTICS_URL`** and **`NEXT_PUBLIC_JMP_TRACKING_KEY`** in Vercel / `.env.local` (both are inlined into the client bundle at build time).
37
+ 3. In **`app/layout.tsx`**, wrap the app once:
37
38
 
38
39
  ```tsx
39
40
  import type { ReactNode } from "react"
40
- import { JmpNextAnalytics } from "@jmp-technologies/analytics/next"
41
+ import { JmpSiteRoot } from "@jmp-technologies/analytics/next"
41
42
 
42
43
  export default function RootLayout({ children }: { children: ReactNode }) {
43
44
  return (
44
45
  <html lang="en">
45
46
  <body>
46
- <JmpNextAnalytics />
47
- {children}
47
+ <JmpSiteRoot>{children}</JmpSiteRoot>
48
48
  </body>
49
49
  </html>
50
50
  )
51
51
  }
52
52
  ```
53
53
 
54
- That enables **automatic `page_view`** on first load and on client-side navigations (includes query string). No `jmp-analytics-client.ts`, no `useEffect` + `usePathname` boilerplate.
54
+ **`JmpSiteRoot`** enables **automatic `page_view`**, **landing referrer capture**, and **delegated `tel:` / `mailto:` tracking** (on by default for Next; set **`NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS=false`** to disable). No custom `useEffect` or env shim files in the client repo.
55
55
 
56
- **Optional:** track phone and email clicks without custom handlersuse **`JmpTrackedTelLink`** and **`JmpTrackedMailtoLink`** (since **0.1.10**) from the same module:
56
+ **Optional:** explicit tracked links (same events as auto-track, useful for styling) — **`JmpTrackedTelLink`** and **`JmpTrackedMailtoLink`**:
57
57
 
58
58
  ```tsx
59
59
  import {
@@ -73,7 +73,7 @@ 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).
76
+ For **SMS / WhatsApp / maps / scheduling** link taps, call **`trackContactClick(path, { channel, linkHost })`** — or rely on the **Next.js default** delegated listener (same as **`NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS=true`**). Per-link opt-out: `data-jmp-no-track` on the `<a>` (or any ancestor).
77
77
 
78
78
  **Hooks:** `useJmpAnalytics()` from `@jmp-technologies/analytics/next` or `@jmp-technologies/analytics/react` for forms and custom events.
79
79
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/astro/client.ts","../../src/landing-capture.ts","../../src/contact-link-classifier.ts","../../src/constants.ts","../../src/consent.ts","../../src/http.ts","../../src/tracker/helpers.ts","../../src/tracker/create-jmp-analytics.ts","../../src/env-public.ts"],"sourcesContent":["import {\n createJmpAnalytics,\n DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,\n type JmpAnalytics,\n type JmpAnalyticsConfig,\n} from \"../index\"\nimport {\n readJmpPublicEnvFromImportMeta,\n type JmpPublicEnv,\n} from \"../env-public\"\nimport { buildLandingCaptureInlineScript } from \"../landing-capture\"\n\nlet jmpAnalyticsSingleton: JmpAnalytics | null = null\nlet astroListenersInstalled = false\n\nfunction currentPath(): string {\n if (typeof window === \"undefined\") return \"/\"\n const { pathname, search } = window.location\n return search ? `${pathname}${search}` : pathname\n}\n\nfunction createClientFromPublicEnv(\n overrides?: Partial<JmpPublicEnv>,\n): JmpAnalytics | null {\n const meta =\n typeof import.meta !== \"undefined\"\n ? (import.meta as ImportMeta & { env?: Record<string, unknown> }).env\n : undefined\n const pub = readJmpPublicEnvFromImportMeta(meta, overrides)\n if (!pub.analyticsUrl || !pub.trackingKey) {\n return null\n }\n const config: JmpAnalyticsConfig = {\n baseUrl: pub.analyticsUrl,\n trackingKey: pub.trackingKey,\n pageViewDedupeMs: DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,\n requireConsent: pub.requireConsent,\n autoTrackContactLinks: pub.autoContactLinks,\n }\n return createJmpAnalytics(config)\n}\n\n/**\n * Inline script for `<script is:inline>` — stores landing referrer/UTMs before hydration.\n * Only inject when a tracking key is configured.\n */\nexport function getJmpLandingCaptureScriptHtml(): string | null {\n const meta =\n typeof import.meta !== \"undefined\"\n ? (import.meta as ImportMeta & { env?: Record<string, unknown> }).env\n : undefined\n const pub = readJmpPublicEnvFromImportMeta(meta)\n if (!pub.trackingKey?.trim()) {\n return null\n }\n return buildLandingCaptureInlineScript()\n}\n\n/**\n * Initialize browser analytics for Astro: first `page_view`, then `astro:page-load` (View Transitions).\n * Add to root layout: `<script>import { initJmpAstroAnalytics } from '...'; initJmpAstroAnalytics()</script>`\n */\nexport function initJmpAstroAnalytics(\n overrides?: Partial<JmpPublicEnv>,\n): JmpAnalytics | null {\n if (typeof window === \"undefined\") {\n return null\n }\n if (!jmpAnalyticsSingleton) {\n jmpAnalyticsSingleton = createClientFromPublicEnv(overrides)\n }\n const client = jmpAnalyticsSingleton\n if (!client) {\n return null\n }\n\n void client.trackPageView({ path: currentPath() })\n\n if (!astroListenersInstalled) {\n astroListenersInstalled = true\n document.addEventListener(\"astro:page-load\", () => {\n void jmpAnalyticsSingleton?.trackPageView({ path: currentPath() })\n })\n }\n\n return client\n}\n\n/** Singleton after {@link initJmpAstroAnalytics}. */\nexport function getJmpAstroAnalytics(): JmpAnalytics | null {\n return jmpAnalyticsSingleton\n}\n","/** 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.21\"\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 = 800\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","import type { JmpServerRequestLike } from \"./types\"\n\n/** CDN / Next.js often return HTML error pages; API errors are JSON */\nexport function responseLooksLikeHtml(body: string): boolean {\n const s = body.trimStart()\n return (\n s.startsWith(\"<!\") ||\n s.toLowerCase().startsWith(\"<html\") ||\n (s.startsWith(\"<\") && /<html[\\s>]/i.test(s.slice(0, 500)))\n )\n}\n\nexport function normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/$/, \"\")\n}\n\nexport function getHeader(\n headers: JmpServerRequestLike[\"headers\"] | undefined,\n key: string\n): string | null {\n if (!headers) {\n return null\n }\n if (typeof (headers as Pick<Headers, \"get\">).get === \"function\") {\n return (headers as Pick<Headers, \"get\">).get(key)\n }\n const record = headers as Record<string, string | string[] | undefined | null>\n const direct = record[key] ?? record[key.toLowerCase()]\n if (Array.isArray(direct)) {\n return direct[0] ?? null\n }\n return direct ?? null\n}\n\nexport function pathFromRequestUrl(url: string | undefined): string {\n if (!url) {\n return \"/\"\n }\n try {\n const u = new URL(url)\n return `${u.pathname}${u.search}`\n } catch {\n return url.startsWith(\"/\") ? url : \"/\"\n }\n}\n\nexport function resolveFetch(\n fetchImpl: typeof fetch | undefined,\n context: string\n): typeof fetch {\n const resolved =\n fetchImpl ??\n (typeof globalThis !== \"undefined\" && globalThis.fetch\n ? globalThis.fetch.bind(globalThis)\n : undefined)\n if (!resolved) {\n throw new Error(`${context}: fetch is not available; pass fetchImpl in config`)\n }\n return resolved\n}\n\ntype ApiErrorJson = {\n error?: string\n code?: string\n hint?: string\n expectedDomain?: string\n}\n\nexport function formatApiErrorMessage(\n status: number,\n text: string,\n prefix: string\n): string {\n if (responseLooksLikeHtml(text)) {\n return `${prefix} HTTP ${status} — response was HTML (not the JSON API). Check baseUrl points at the JMP Analytics dashboard.`\n }\n let parsed: ApiErrorJson = {}\n try {\n parsed = JSON.parse(text) as ApiErrorJson\n } catch {\n parsed = { error: text }\n }\n const detail = parsed.error ?? text\n return [\n `${prefix} HTTP ${status}`,\n parsed.code ? `[${parsed.code}]` : null,\n detail,\n parsed.hint ? `Hint: ${parsed.hint}` : null,\n parsed.expectedDomain\n ? `Domain in JMP admin: ${parsed.expectedDomain}`\n : null,\n ]\n .filter(Boolean)\n .join(\" — \")\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 {\n captureLandingAttribution,\n getOrCreateSessionIdFromStorage,\n JMP_UTM_PARAM_KEYS,\n} from \"../landing-capture\"\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 let lastDedupePath = \"\"\n let lastDedupeAt = 0\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 shouldDedupePageView(path: string): boolean {\n const ms = config.pageViewDedupeMs ?? 0\n if (ms <= 0 || typeof window === \"undefined\") {\n return false\n }\n const now = Date.now()\n if (path === lastDedupePath && now - lastDedupeAt < ms) {\n return true\n }\n lastDedupePath = path\n lastDedupeAt = now\n return false\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 if (shouldDedupePageView(pathname)) {\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 (anchor.closest(\"[data-jmp-no-track]\")) {\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","/**\n * Public env vars for JMP — supports Next.js (`NEXT_PUBLIC_*`) and Astro (`PUBLIC_*`).\n * Server/build: `process.env`. Astro client bundles: pass overrides from `import.meta.env`.\n */\n\nexport type JmpPublicEnv = {\n analyticsUrl?: string\n trackingKey?: string\n requireConsent?: boolean\n autoContactLinks?: boolean\n}\n\nfunction readProcessEnv(name: string): string | undefined {\n if (typeof process === \"undefined\" || !process.env) return undefined\n const v = process.env[name]\n return typeof v === \"string\" && v.length > 0 ? v : undefined\n}\n\nfunction firstDefined(...values: (string | undefined)[]): string | undefined {\n for (const v of values) {\n if (v) return v\n }\n return undefined\n}\n\n/** Read dashboard URL + tracking key from Node / build-time env. */\nexport function readJmpPublicEnvFromProcess(\n overrides?: Partial<JmpPublicEnv>,\n): JmpPublicEnv {\n const analyticsUrl = firstDefined(\n overrides?.analyticsUrl,\n readProcessEnv(\"NEXT_PUBLIC_JMP_ANALYTICS_URL\"),\n readProcessEnv(\"PUBLIC_JMP_ANALYTICS_URL\"),\n )\n const trackingKey = firstDefined(\n overrides?.trackingKey,\n readProcessEnv(\"NEXT_PUBLIC_JMP_TRACKING_KEY\"),\n readProcessEnv(\"PUBLIC_JMP_TRACKING_KEY\"),\n )\n const requireConsentRaw = firstDefined(\n overrides?.requireConsent !== undefined\n ? String(overrides.requireConsent)\n : undefined,\n readProcessEnv(\"NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT\"),\n readProcessEnv(\"PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT\"),\n )\n const autoContactRaw = firstDefined(\n overrides?.autoContactLinks !== undefined\n ? String(overrides.autoContactLinks)\n : undefined,\n readProcessEnv(\"NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS\"),\n readProcessEnv(\"PUBLIC_JMP_AUTO_CONTACT_LINKS\"),\n )\n\n return {\n analyticsUrl,\n trackingKey,\n requireConsent: requireConsentRaw === \"true\",\n autoContactLinks: autoContactRaw === \"true\",\n }\n}\n\n/** Read from Astro/Vite `import.meta.env` in client scripts. */\nexport function readJmpPublicEnvFromImportMeta(\n meta: Record<string, unknown> | undefined,\n overrides?: Partial<JmpPublicEnv>,\n): JmpPublicEnv {\n const get = (key: string): string | undefined => {\n const v = meta?.[key]\n return typeof v === \"string\" && v.length > 0 ? v : undefined\n }\n\n const analyticsUrl = firstDefined(\n overrides?.analyticsUrl,\n get(\"PUBLIC_JMP_ANALYTICS_URL\"),\n get(\"NEXT_PUBLIC_JMP_ANALYTICS_URL\"),\n )\n const trackingKey = firstDefined(\n overrides?.trackingKey,\n get(\"PUBLIC_JMP_TRACKING_KEY\"),\n get(\"NEXT_PUBLIC_JMP_TRACKING_KEY\"),\n )\n const requireConsentRaw = firstDefined(\n overrides?.requireConsent !== undefined\n ? String(overrides.requireConsent)\n : undefined,\n get(\"PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT\"),\n get(\"NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT\"),\n )\n const autoContactRaw = firstDefined(\n overrides?.autoContactLinks !== undefined\n ? String(overrides.autoContactLinks)\n : undefined,\n get(\"PUBLIC_JMP_AUTO_CONTACT_LINKS\"),\n get(\"NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS\"),\n )\n\n return {\n analyticsUrl,\n trackingKey,\n requireConsent: requireConsentRaw === \"true\",\n autoContactLinks: autoContactRaw === \"true\",\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/astro/client.ts","../../src/landing-capture.ts","../../src/contact-link-classifier.ts","../../src/constants.ts","../../src/consent.ts","../../src/http.ts","../../src/tracker/helpers.ts","../../src/tracker/create-jmp-analytics.ts","../../src/env-public.ts"],"sourcesContent":["import {\n createJmpAnalytics,\n DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,\n type JmpAnalytics,\n type JmpAnalyticsConfig,\n} from \"../index\"\nimport {\n readJmpPublicEnvFromImportMeta,\n type JmpPublicEnv,\n} from \"../env-public\"\nimport { buildLandingCaptureInlineScript } from \"../landing-capture\"\n\nlet jmpAnalyticsSingleton: JmpAnalytics | null = null\nlet astroListenersInstalled = false\n\nfunction currentPath(): string {\n if (typeof window === \"undefined\") return \"/\"\n const { pathname, search } = window.location\n return search ? `${pathname}${search}` : pathname\n}\n\nfunction createClientFromPublicEnv(\n overrides?: Partial<JmpPublicEnv>,\n): JmpAnalytics | null {\n const meta =\n typeof import.meta !== \"undefined\"\n ? (import.meta as ImportMeta & { env?: Record<string, unknown> }).env\n : undefined\n const pub = readJmpPublicEnvFromImportMeta(meta, overrides)\n if (!pub.analyticsUrl || !pub.trackingKey) {\n return null\n }\n const config: JmpAnalyticsConfig = {\n baseUrl: pub.analyticsUrl,\n trackingKey: pub.trackingKey,\n pageViewDedupeMs: DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,\n requireConsent: pub.requireConsent,\n autoTrackContactLinks: pub.autoContactLinks,\n }\n return createJmpAnalytics(config)\n}\n\n/**\n * Inline script for `<script is:inline>` — stores landing referrer/UTMs before hydration.\n * Only inject when a tracking key is configured.\n */\nexport function getJmpLandingCaptureScriptHtml(): string | null {\n const meta =\n typeof import.meta !== \"undefined\"\n ? (import.meta as ImportMeta & { env?: Record<string, unknown> }).env\n : undefined\n const pub = readJmpPublicEnvFromImportMeta(meta)\n if (!pub.trackingKey?.trim()) {\n return null\n }\n return buildLandingCaptureInlineScript()\n}\n\n/**\n * Initialize browser analytics for Astro: first `page_view`, then `astro:page-load` (View Transitions).\n * Add to root layout: `<script>import { initJmpAstroAnalytics } from '...'; initJmpAstroAnalytics()</script>`\n */\nexport function initJmpAstroAnalytics(\n overrides?: Partial<JmpPublicEnv>,\n): JmpAnalytics | null {\n if (typeof window === \"undefined\") {\n return null\n }\n if (!jmpAnalyticsSingleton) {\n jmpAnalyticsSingleton = createClientFromPublicEnv(overrides)\n }\n const client = jmpAnalyticsSingleton\n if (!client) {\n return null\n }\n\n void client.trackPageView({ path: currentPath() })\n\n if (!astroListenersInstalled) {\n astroListenersInstalled = true\n document.addEventListener(\"astro:page-load\", () => {\n void jmpAnalyticsSingleton?.trackPageView({ path: currentPath() })\n })\n }\n\n return client\n}\n\n/** Singleton after {@link initJmpAstroAnalytics}. */\nexport function getJmpAstroAnalytics(): JmpAnalytics | null {\n return jmpAnalyticsSingleton\n}\n","/** 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.21\"\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 = 800\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","import type { JmpServerRequestLike } from \"./types\"\n\n/** CDN / Next.js often return HTML error pages; API errors are JSON */\nexport function responseLooksLikeHtml(body: string): boolean {\n const s = body.trimStart()\n return (\n s.startsWith(\"<!\") ||\n s.toLowerCase().startsWith(\"<html\") ||\n (s.startsWith(\"<\") && /<html[\\s>]/i.test(s.slice(0, 500)))\n )\n}\n\nexport function normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/$/, \"\")\n}\n\nexport function getHeader(\n headers: JmpServerRequestLike[\"headers\"] | undefined,\n key: string\n): string | null {\n if (!headers) {\n return null\n }\n if (typeof (headers as Pick<Headers, \"get\">).get === \"function\") {\n return (headers as Pick<Headers, \"get\">).get(key)\n }\n const record = headers as Record<string, string | string[] | undefined | null>\n const direct = record[key] ?? record[key.toLowerCase()]\n if (Array.isArray(direct)) {\n return direct[0] ?? null\n }\n return direct ?? null\n}\n\nexport function pathFromRequestUrl(url: string | undefined): string {\n if (!url) {\n return \"/\"\n }\n try {\n const u = new URL(url)\n return `${u.pathname}${u.search}`\n } catch {\n return url.startsWith(\"/\") ? url : \"/\"\n }\n}\n\nexport function resolveFetch(\n fetchImpl: typeof fetch | undefined,\n context: string\n): typeof fetch {\n const resolved =\n fetchImpl ??\n (typeof globalThis !== \"undefined\" && globalThis.fetch\n ? globalThis.fetch.bind(globalThis)\n : undefined)\n if (!resolved) {\n throw new Error(`${context}: fetch is not available; pass fetchImpl in config`)\n }\n return resolved\n}\n\ntype ApiErrorJson = {\n error?: string\n code?: string\n hint?: string\n expectedDomain?: string\n}\n\nexport function formatApiErrorMessage(\n status: number,\n text: string,\n prefix: string\n): string {\n if (responseLooksLikeHtml(text)) {\n return `${prefix} HTTP ${status} — response was HTML (not the JSON API). Check baseUrl points at the JMP Analytics dashboard.`\n }\n let parsed: ApiErrorJson = {}\n try {\n parsed = JSON.parse(text) as ApiErrorJson\n } catch {\n parsed = { error: text }\n }\n const detail = parsed.error ?? text\n return [\n `${prefix} HTTP ${status}`,\n parsed.code ? `[${parsed.code}]` : null,\n detail,\n parsed.hint ? `Hint: ${parsed.hint}` : null,\n parsed.expectedDomain\n ? `Domain in JMP admin: ${parsed.expectedDomain}`\n : null,\n ]\n .filter(Boolean)\n .join(\" — \")\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 {\n captureLandingAttribution,\n getOrCreateSessionIdFromStorage,\n JMP_UTM_PARAM_KEYS,\n} from \"../landing-capture\"\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 let lastDedupePath = \"\"\n let lastDedupeAt = 0\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 shouldDedupePageView(path: string): boolean {\n const ms = config.pageViewDedupeMs ?? 0\n if (ms <= 0 || typeof window === \"undefined\") {\n return false\n }\n const now = Date.now()\n if (path === lastDedupePath && now - lastDedupeAt < ms) {\n return true\n }\n lastDedupePath = path\n lastDedupeAt = now\n return false\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 if (shouldDedupePageView(pathname)) {\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 (anchor.closest(\"[data-jmp-no-track]\")) {\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","/**\n * Public env vars for JMP — supports Next.js (`NEXT_PUBLIC_*`) and Astro (`PUBLIC_*`).\n * Server/build: `process.env`. Astro client bundles: pass overrides from `import.meta.env`.\n */\n\nexport type JmpPublicEnv = {\n analyticsUrl?: string\n trackingKey?: string\n requireConsent?: boolean\n autoContactLinks?: boolean\n}\n\nfunction firstDefined(...values: (string | undefined)[]): string | undefined {\n for (const v of values) {\n if (v) return v\n }\n return undefined\n}\n\n/** Static reads so Next.js / Vite inline `NEXT_PUBLIC_*` into client bundles. */\nfunction readJmpPublicEnvFromProcessEnv(): Omit<JmpPublicEnv, never> {\n if (typeof process === \"undefined\" || !process.env) {\n return {}\n }\n const { env } = process\n const analyticsUrl = firstDefined(\n env.NEXT_PUBLIC_JMP_ANALYTICS_URL,\n env.PUBLIC_JMP_ANALYTICS_URL,\n )\n const trackingKey = firstDefined(\n env.NEXT_PUBLIC_JMP_TRACKING_KEY,\n env.PUBLIC_JMP_TRACKING_KEY,\n )\n const requireConsentRaw = firstDefined(\n env.NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT,\n env.PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT,\n )\n const autoContactRaw = firstDefined(\n env.NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS,\n env.PUBLIC_JMP_AUTO_CONTACT_LINKS,\n )\n\n return {\n analyticsUrl,\n trackingKey,\n requireConsent: requireConsentRaw === \"true\",\n autoContactLinks: autoContactRaw === \"true\",\n }\n}\n\n/** Read dashboard URL + tracking key from Node / build-time env. */\nexport function readJmpPublicEnvFromProcess(\n overrides?: Partial<JmpPublicEnv>,\n): JmpPublicEnv {\n const fromEnv = readJmpPublicEnvFromProcessEnv()\n\n return {\n analyticsUrl: firstDefined(overrides?.analyticsUrl, fromEnv.analyticsUrl),\n trackingKey: firstDefined(overrides?.trackingKey, fromEnv.trackingKey),\n requireConsent: overrides?.requireConsent ?? fromEnv.requireConsent,\n autoContactLinks:\n overrides?.autoContactLinks ?? fromEnv.autoContactLinks,\n }\n}\n\n/** Read from Astro/Vite `import.meta.env` in client scripts. */\nexport function readJmpPublicEnvFromImportMeta(\n meta: Record<string, unknown> | undefined,\n overrides?: Partial<JmpPublicEnv>,\n): JmpPublicEnv {\n const get = (key: string): string | undefined => {\n const v = meta?.[key]\n return typeof v === \"string\" && v.length > 0 ? v : undefined\n }\n\n const analyticsUrl = firstDefined(\n overrides?.analyticsUrl,\n get(\"PUBLIC_JMP_ANALYTICS_URL\"),\n get(\"NEXT_PUBLIC_JMP_ANALYTICS_URL\"),\n )\n const trackingKey = firstDefined(\n overrides?.trackingKey,\n get(\"PUBLIC_JMP_TRACKING_KEY\"),\n get(\"NEXT_PUBLIC_JMP_TRACKING_KEY\"),\n )\n const requireConsentRaw = firstDefined(\n overrides?.requireConsent !== undefined\n ? String(overrides.requireConsent)\n : undefined,\n get(\"PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT\"),\n get(\"NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT\"),\n )\n const autoContactRaw = firstDefined(\n overrides?.autoContactLinks !== undefined\n ? String(overrides.autoContactLinks)\n : undefined,\n get(\"PUBLIC_JMP_AUTO_CONTACT_LINKS\"),\n get(\"NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS\"),\n )\n\n return {\n analyticsUrl,\n trackingKey,\n requireConsent: requireConsentRaw === \"true\",\n autoContactLinks: autoContactRaw === \"true\",\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,17 +1,7 @@
1
1
  import { J as JmpAnalytics } from '../create-jmp-analytics-DxhHOmtR.cjs';
2
+ import { J as JmpPublicEnv } from '../env-public-B6Y0DG68.cjs';
2
3
  import '../types-BtiK_Acj.cjs';
3
4
 
4
- /**
5
- * Public env vars for JMP — supports Next.js (`NEXT_PUBLIC_*`) and Astro (`PUBLIC_*`).
6
- * Server/build: `process.env`. Astro client bundles: pass overrides from `import.meta.env`.
7
- */
8
- type JmpPublicEnv = {
9
- analyticsUrl?: string;
10
- trackingKey?: string;
11
- requireConsent?: boolean;
12
- autoContactLinks?: boolean;
13
- };
14
-
15
5
  /**
16
6
  * Inline script for `<script is:inline>` — stores landing referrer/UTMs before hydration.
17
7
  * Only inject when a tracking key is configured.
@@ -1,17 +1,7 @@
1
1
  import { J as JmpAnalytics } from '../create-jmp-analytics-BdGOgPxl.js';
2
+ import { J as JmpPublicEnv } from '../env-public-B6Y0DG68.js';
2
3
  import '../types-BtiK_Acj.js';
3
4
 
4
- /**
5
- * Public env vars for JMP — supports Next.js (`NEXT_PUBLIC_*`) and Astro (`PUBLIC_*`).
6
- * Server/build: `process.env`. Astro client bundles: pass overrides from `import.meta.env`.
7
- */
8
- type JmpPublicEnv = {
9
- analyticsUrl?: string;
10
- trackingKey?: string;
11
- requireConsent?: boolean;
12
- autoContactLinks?: boolean;
13
- };
14
-
15
5
  /**
16
6
  * Inline script for `<script is:inline>` — stores landing referrer/UTMs before hydration.
17
7
  * Only inject when a tracking key is configured.
@@ -2,10 +2,10 @@ import {
2
2
  DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,
3
3
  buildLandingCaptureInlineScript,
4
4
  createJmpAnalytics
5
- } from "../chunk-PDOQ2XBW.js";
5
+ } from "../chunk-AHRVNEFV.js";
6
6
  import {
7
7
  readJmpPublicEnvFromImportMeta
8
- } from "../chunk-GLEOJIKL.js";
8
+ } from "../chunk-4OOWA55U.js";
9
9
 
10
10
  // src/astro/client.ts
11
11
  var jmpAnalyticsSingleton = null;
@@ -134,37 +134,32 @@ async function getJmpSiteProfile(config) {
134
134
  }
135
135
 
136
136
  // src/env-public.ts
137
- function readProcessEnv(name) {
138
- if (typeof process === "undefined" || !process.env) return void 0;
139
- const v = process.env[name];
140
- return typeof v === "string" && v.length > 0 ? v : void 0;
141
- }
142
137
  function firstDefined(...values) {
143
138
  for (const v of values) {
144
139
  if (v) return v;
145
140
  }
146
141
  return void 0;
147
142
  }
148
- function readJmpPublicEnvFromProcess(overrides) {
143
+ function readJmpPublicEnvFromProcessEnv() {
144
+ if (typeof process === "undefined" || !process.env) {
145
+ return {};
146
+ }
147
+ const { env } = process;
149
148
  const analyticsUrl = firstDefined(
150
- overrides?.analyticsUrl,
151
- readProcessEnv("NEXT_PUBLIC_JMP_ANALYTICS_URL"),
152
- readProcessEnv("PUBLIC_JMP_ANALYTICS_URL")
149
+ env.NEXT_PUBLIC_JMP_ANALYTICS_URL,
150
+ env.PUBLIC_JMP_ANALYTICS_URL
153
151
  );
154
152
  const trackingKey = firstDefined(
155
- overrides?.trackingKey,
156
- readProcessEnv("NEXT_PUBLIC_JMP_TRACKING_KEY"),
157
- readProcessEnv("PUBLIC_JMP_TRACKING_KEY")
153
+ env.NEXT_PUBLIC_JMP_TRACKING_KEY,
154
+ env.PUBLIC_JMP_TRACKING_KEY
158
155
  );
159
156
  const requireConsentRaw = firstDefined(
160
- overrides?.requireConsent !== void 0 ? String(overrides.requireConsent) : void 0,
161
- readProcessEnv("NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT"),
162
- readProcessEnv("PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT")
157
+ env.NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT,
158
+ env.PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT
163
159
  );
164
160
  const autoContactRaw = firstDefined(
165
- overrides?.autoContactLinks !== void 0 ? String(overrides.autoContactLinks) : void 0,
166
- readProcessEnv("NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS"),
167
- readProcessEnv("PUBLIC_JMP_AUTO_CONTACT_LINKS")
161
+ env.NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS,
162
+ env.PUBLIC_JMP_AUTO_CONTACT_LINKS
168
163
  );
169
164
  return {
170
165
  analyticsUrl,
@@ -173,6 +168,15 @@ function readJmpPublicEnvFromProcess(overrides) {
173
168
  autoContactLinks: autoContactRaw === "true"
174
169
  };
175
170
  }
171
+ function readJmpPublicEnvFromProcess(overrides) {
172
+ const fromEnv = readJmpPublicEnvFromProcessEnv();
173
+ return {
174
+ analyticsUrl: firstDefined(overrides?.analyticsUrl, fromEnv.analyticsUrl),
175
+ trackingKey: firstDefined(overrides?.trackingKey, fromEnv.trackingKey),
176
+ requireConsent: overrides?.requireConsent ?? fromEnv.requireConsent,
177
+ autoContactLinks: overrides?.autoContactLinks ?? fromEnv.autoContactLinks
178
+ };
179
+ }
176
180
 
177
181
  // src/site-config.ts
178
182
  var registeredSite = null;