@jmp-technologies/analytics 0.1.32 → 0.1.34

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 (79) hide show
  1. package/dist/astro/client.cjs +4 -1
  2. package/dist/astro/client.cjs.map +1 -1
  3. package/dist/astro/client.d.cts +2 -2
  4. package/dist/astro/client.d.ts +2 -2
  5. package/dist/astro/client.js +1 -1
  6. package/dist/astro/content.cjs +33 -15
  7. package/dist/astro/content.cjs.map +1 -1
  8. package/dist/astro/content.d.cts +7 -7
  9. package/dist/astro/content.d.ts +7 -7
  10. package/dist/astro/content.js +2 -2
  11. package/dist/astro/integration.cjs +4 -1
  12. package/dist/astro/integration.cjs.map +1 -1
  13. package/dist/astro/integration.js +2 -2
  14. package/dist/astro/middleware.d.cts +2 -2
  15. package/dist/astro/middleware.d.ts +2 -2
  16. package/dist/astro/register.cjs +4 -1
  17. package/dist/astro/register.cjs.map +1 -1
  18. package/dist/astro/register.js +2 -2
  19. package/dist/{chunk-HKMDWTA2.js → chunk-4NUFHUWN.js} +10 -10
  20. package/dist/chunk-4NUFHUWN.js.map +1 -0
  21. package/dist/{chunk-NI7R6QZQ.js → chunk-NJAMXGIW.js} +73 -11
  22. package/dist/chunk-NJAMXGIW.js.map +1 -0
  23. package/dist/{chunk-7623G3XL.js → chunk-SIJ4TDRV.js} +2 -2
  24. package/dist/{chunk-NDSPSAWQ.js → chunk-UL7JTTTB.js} +2 -2
  25. package/dist/{chunk-KPTMZQCY.js → chunk-VUWHDHXS.js} +2 -2
  26. package/dist/{create-jmp-analytics-htx2oOEf.d.ts → create-jmp-analytics-CwtcXBd7.d.ts} +1 -1
  27. package/dist/{create-jmp-analytics-D1dUsqCQ.d.cts → create-jmp-analytics-lkGcysG_.d.cts} +1 -1
  28. package/dist/index.cjs +74 -10
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +23 -13
  31. package/dist/index.d.ts +23 -13
  32. package/dist/index.js +5 -1
  33. package/dist/next/content.cjs +33 -15
  34. package/dist/next/content.cjs.map +1 -1
  35. package/dist/next/content.d.cts +2 -2
  36. package/dist/next/content.d.ts +2 -2
  37. package/dist/next/content.js +2 -2
  38. package/dist/next/instrumentation.cjs +4 -1
  39. package/dist/next/instrumentation.cjs.map +1 -1
  40. package/dist/next/instrumentation.js +1 -1
  41. package/dist/next/landing-pages.cjs +4 -1
  42. package/dist/next/landing-pages.cjs.map +1 -1
  43. package/dist/next/landing-pages.d.cts +2 -2
  44. package/dist/next/landing-pages.d.ts +2 -2
  45. package/dist/next/landing-pages.js +2 -2
  46. package/dist/next/locations.cjs +4 -1
  47. package/dist/next/locations.cjs.map +1 -1
  48. package/dist/next/locations.d.cts +2 -2
  49. package/dist/next/locations.d.ts +2 -2
  50. package/dist/next/locations.js +2 -2
  51. package/dist/next/middleware.d.cts +2 -2
  52. package/dist/next/middleware.d.ts +2 -2
  53. package/dist/next/site-root-server.cjs +4 -1
  54. package/dist/next/site-root-server.cjs.map +1 -1
  55. package/dist/next/site-root-server.js +3 -3
  56. package/dist/next.cjs +4 -1
  57. package/dist/next.cjs.map +1 -1
  58. package/dist/next.d.cts +3 -3
  59. package/dist/next.d.ts +3 -3
  60. package/dist/next.js +3 -3
  61. package/dist/react.cjs +4 -1
  62. package/dist/react.cjs.map +1 -1
  63. package/dist/react.d.cts +2 -2
  64. package/dist/react.d.ts +2 -2
  65. package/dist/react.js +2 -2
  66. package/dist/{site-config-B7USGGzu.d.cts → site-config-BMlEQ_8g.d.cts} +1 -1
  67. package/dist/{site-config-C4bcUoLI.d.ts → site-config-BbE6kGDd.d.ts} +1 -1
  68. package/dist/{track-page-view-RiB-xgIx.d.ts → track-page-view-BPWGGoMr.d.ts} +1 -1
  69. package/dist/{track-page-view-C4TbbULE.d.cts → track-page-view-DaNOaIxF.d.cts} +1 -1
  70. package/dist/{types-DRljY1Yy.d.cts → types-BdC3SRH-.d.cts} +24 -1
  71. package/dist/{types-DRljY1Yy.d.ts → types-BdC3SRH-.d.ts} +24 -1
  72. package/dist/{utils-CHT3SQFv.d.ts → utils-B4E-pcQ0.d.ts} +1 -1
  73. package/dist/{utils-BajG58pj.d.cts → utils-DIHFgovG.d.cts} +1 -1
  74. package/package.json +1 -1
  75. package/dist/chunk-HKMDWTA2.js.map +0 -1
  76. package/dist/chunk-NI7R6QZQ.js.map +0 -1
  77. /package/dist/{chunk-7623G3XL.js.map → chunk-SIJ4TDRV.js.map} +0 -0
  78. /package/dist/{chunk-NDSPSAWQ.js.map → chunk-UL7JTTTB.js.map} +0 -0
  79. /package/dist/{chunk-KPTMZQCY.js.map → chunk-VUWHDHXS.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/astro/register.ts","../../src/landing-capture.ts","../../src/constants.ts","../../src/http.ts","../../src/content/manifest.ts","../../src/content/wrapper-discovery.ts","../../src/content/api.ts","../../src/env-public.ts","../../src/site-config.ts"],"sourcesContent":["import { registerJmpContentWrappers } from \"../index\"\nimport { getJmpContentConfig } from \"../site-config\"\n\n/**\n * Register content wrappers with the dashboard (call once per build).\n * Import `./jmp.config` in `astro.config.mjs` before calling this, or use {@link jmpAnalytics} integration.\n */\nexport async function registerJmpAstroContent(): Promise<void> {\n const config = getJmpContentConfig()\n if (!config) {\n return\n }\n await registerJmpContentWrappers(config).catch(() => {})\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","/** Published package version — sent with capabilities for admin readiness. */\nexport const JMP_ANALYTICS_SDK_VERSION = \"0.1.26\"\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 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","/** Mirrors jmp-dashboard `lib/content/wrappers/client-manifest.ts` (keep in sync). */\n\nexport const JMP_CONTENT_MANIFEST_VERSION = 1\n\nexport type JmpTrustSection =\n | \"summary\"\n | \"testimonials\"\n | \"reviews\"\n | \"stats\"\n | \"logos\"\n | \"credentials\"\n\nexport type JmpContentRouteManifest = {\n wrapper: string\n pattern: string\n index?: string\n label?: string\n}\n\nexport type JmpTrustBlockManifest = {\n section: JmpTrustSection\n label?: string\n}\n\nexport type JmpContactExtraManifest = {\n key: string\n label: string\n}\n\nexport type JmpExternalSanitySourceManifest = {\n studioUrl?: string\n}\n\nexport type JmpExternalShopifySourceManifest = {\n adminUrl?: string\n storeUrl?: string\n}\n\nexport type JmpExternalAdminLinkManifest = {\n id: string\n label: string\n url: string\n}\n\nexport type JmpExternalSourcesManifest = {\n sanity?: JmpExternalSanitySourceManifest\n shopify?: JmpExternalShopifySourceManifest\n links?: JmpExternalAdminLinkManifest[]\n}\n\nexport type JmpContentManifest = {\n version?: number\n routes?: JmpContentRouteManifest[]\n externalSources?: JmpExternalSourcesManifest\n trust?: {\n blocks?: JmpTrustBlockManifest[]\n labels?: Partial<Record<JmpTrustSection, string>>\n mergeTestimonialsReviews?: boolean\n }\n contact?: {\n extras?: JmpContactExtraManifest[]\n }\n}\n\nconst TRUST_SECTIONS = new Set<string>([\n \"summary\",\n \"testimonials\",\n \"reviews\",\n \"stats\",\n \"logos\",\n \"credentials\",\n])\n\nfunction normalizeKey(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n}\n\nfunction isValidExtraKey(key: string): boolean {\n return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(key)\n}\n\nconst MAX_EXTERNAL_ADMIN_LINKS = 10\nconst MAX_EXTERNAL_ADMIN_LINK_LABEL_LENGTH = 80\nconst EXTERNAL_ADMIN_LINK_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/\n\nfunction tryNormalizeHttpUrl(input: unknown): string | null {\n if (typeof input !== \"string\" || !input.trim()) {\n return null\n }\n try {\n const url = new URL(input.trim())\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") {\n return null\n }\n return url.toString().replace(/\\/$/, \"\")\n } catch {\n return null\n }\n}\n\nfunction normalizeShopifyExternalSource(\n shopify: JmpExternalShopifySourceManifest | undefined\n): JmpExternalShopifySourceManifest | null {\n if (!shopify || typeof shopify !== \"object\") {\n return null\n }\n const adminUrl = tryNormalizeHttpUrl(shopify.adminUrl)\n const storeUrl = tryNormalizeHttpUrl(shopify.storeUrl)\n if (!adminUrl && !storeUrl) {\n return null\n }\n return {\n ...(adminUrl ? { adminUrl } : {}),\n ...(storeUrl ? { storeUrl } : {}),\n }\n}\n\nfunction normalizeExternalAdminLinkEntries(\n links: JmpExternalAdminLinkManifest[] | undefined\n): JmpExternalAdminLinkManifest[] {\n if (!Array.isArray(links)) {\n return []\n }\n const seenIds = new Set<string>()\n const out: JmpExternalAdminLinkManifest[] = []\n for (const raw of links) {\n if (!raw || typeof raw !== \"object\" || out.length >= MAX_EXTERNAL_ADMIN_LINKS) {\n continue\n }\n const id =\n typeof raw.id === \"string\"\n ? raw.id.trim().toLowerCase().replace(/[^a-z0-9-]+/g, \"-\").replace(/^-+|-+$/g, \"\")\n : \"\"\n const label =\n typeof raw.label === \"string\"\n ? raw.label.trim().slice(0, MAX_EXTERNAL_ADMIN_LINK_LABEL_LENGTH)\n : \"\"\n const url = tryNormalizeHttpUrl(raw.url)\n if (\n !id ||\n !label ||\n !url ||\n !EXTERNAL_ADMIN_LINK_ID_PATTERN.test(id) ||\n seenIds.has(id)\n ) {\n continue\n }\n seenIds.add(id)\n out.push({ id, label, url })\n }\n return out\n}\n\nfunction normalizeExternalSources(\n externalSources: JmpExternalSourcesManifest | undefined\n): JmpExternalSourcesManifest | null {\n if (!externalSources || typeof externalSources !== \"object\") {\n return null\n }\n const studioUrl = tryNormalizeHttpUrl(externalSources.sanity?.studioUrl)\n const shopify = normalizeShopifyExternalSource(externalSources.shopify)\n const links = normalizeExternalAdminLinkEntries(externalSources.links)\n if (!studioUrl && !shopify && links.length === 0) {\n return null\n }\n return {\n ...(studioUrl ? { sanity: { studioUrl } } : {}),\n ...(shopify ? { shopify } : {}),\n ...(links.length > 0 ? { links } : {}),\n }\n}\n\nexport function normalizeJmpContentManifest(\n input: JmpContentManifest | null | undefined\n): JmpContentManifest | null {\n if (!input || typeof input !== \"object\") {\n return null\n }\n\n const routes = normalizeRoutes(input.routes)\n const externalSources = normalizeExternalSources(input.externalSources)\n const trust = normalizeTrust(input.trust)\n const contact = normalizeContact(input.contact)\n\n if (routes.length === 0 && !externalSources && !trust && !contact) {\n return null\n }\n\n return {\n version: JMP_CONTENT_MANIFEST_VERSION,\n ...(routes.length > 0 ? { routes } : {}),\n ...(externalSources ? { externalSources } : {}),\n ...(trust ? { trust } : {}),\n ...(contact ? { contact } : {}),\n }\n}\n\nfunction normalizeRoutes(\n routes: JmpContentRouteManifest[] | undefined\n): JmpContentRouteManifest[] {\n if (!Array.isArray(routes)) {\n return []\n }\n const seen = new Set<string>()\n const out: JmpContentRouteManifest[] = []\n for (const raw of routes) {\n if (!raw || typeof raw !== \"object\") continue\n const wrapper =\n typeof raw.wrapper === \"string\" ? raw.wrapper.trim().toLowerCase() : \"\"\n const pattern =\n typeof raw.pattern === \"string\" ? raw.pattern.trim() : \"\"\n if (!wrapper || !pattern) continue\n const dedupe = `${wrapper}:${pattern}`\n if (seen.has(dedupe)) continue\n seen.add(dedupe)\n out.push({\n wrapper,\n pattern,\n ...(typeof raw.index === \"string\" && raw.index.trim()\n ? { index: raw.index.trim() }\n : {}),\n ...(typeof raw.label === \"string\" && raw.label.trim()\n ? { label: raw.label.trim() }\n : {}),\n })\n }\n return out\n}\n\nfunction normalizeTrust(\n trust: JmpContentManifest[\"trust\"] | undefined\n): JmpContentManifest[\"trust\"] | null {\n if (!trust || typeof trust !== \"object\") return null\n const blocks = normalizeTrustBlocks(trust.blocks)\n const labels = normalizeTrustLabels(trust.labels)\n const mergeTestimonialsReviews = trust.mergeTestimonialsReviews === true\n if (blocks.length === 0 && !labels && !mergeTestimonialsReviews) return null\n return {\n ...(blocks.length > 0 ? { blocks } : {}),\n ...(labels ? { labels } : {}),\n ...(mergeTestimonialsReviews ? { mergeTestimonialsReviews: true } : {}),\n }\n}\n\nfunction normalizeTrustBlocks(\n blocks: JmpTrustBlockManifest[] | undefined\n): JmpTrustBlockManifest[] {\n if (!Array.isArray(blocks)) return []\n const seen = new Set<string>()\n const out: JmpTrustBlockManifest[] = []\n for (const raw of blocks) {\n if (!raw || typeof raw !== \"object\") continue\n const section =\n typeof raw.section === \"string\" ? raw.section.trim().toLowerCase() : \"\"\n if (!TRUST_SECTIONS.has(section) || seen.has(section)) continue\n seen.add(section)\n out.push({\n section: section as JmpTrustSection,\n ...(typeof raw.label === \"string\" && raw.label.trim()\n ? { label: raw.label.trim() }\n : {}),\n })\n }\n return out\n}\n\nfunction normalizeTrustLabels(\n labels: Partial<Record<JmpTrustSection, string>> | undefined\n): Partial<Record<JmpTrustSection, string>> | null {\n if (!labels || typeof labels !== \"object\") return null\n const out: Partial<Record<JmpTrustSection, string>> = {}\n for (const [key, value] of Object.entries(labels)) {\n if (TRUST_SECTIONS.has(key) && typeof value === \"string\" && value.trim()) {\n out[key as JmpTrustSection] = value.trim()\n }\n }\n return Object.keys(out).length > 0 ? out : null\n}\n\nfunction normalizeContact(\n contact: JmpContentManifest[\"contact\"] | undefined\n): JmpContentManifest[\"contact\"] | null {\n if (!contact || typeof contact !== \"object\") return null\n const extras = normalizeContactExtras(contact.extras)\n return extras.length > 0 ? { extras } : null\n}\n\nfunction normalizeContactExtras(\n extras: JmpContactExtraManifest[] | undefined\n): JmpContactExtraManifest[] {\n if (!Array.isArray(extras)) return []\n const seen = new Set<string>()\n const out: JmpContactExtraManifest[] = []\n for (const raw of extras) {\n if (!raw || typeof raw !== \"object\") continue\n const label = typeof raw.label === \"string\" ? raw.label.trim() : \"\"\n const key = normalizeKey(\n typeof raw.key === \"string\" && raw.key.trim() ? raw.key : label\n )\n if (!key || !label || !isValidExtraKey(key) || seen.has(key)) continue\n seen.add(key)\n out.push({ key, label })\n }\n return out\n}\n\nexport function stableJmpManifestFingerprint(\n manifest: JmpContentManifest | null\n): string {\n return manifest ? JSON.stringify(manifest) : \"\"\n}\n\nexport function inferWrapperSlotsFromManifest(\n manifest: JmpContentManifest | null\n): string[] {\n const slots = new Set<string>()\n for (const route of manifest?.routes ?? []) {\n slots.add(route.wrapper)\n }\n for (const block of manifest?.trust?.blocks ?? []) {\n slots.add(`trust.${block.section}`)\n slots.add(\"trust\")\n }\n if (manifest?.contact?.extras?.length) {\n slots.add(\"contact\")\n }\n return [...slots]\n}\n","import {\n inferWrapperSlotsFromManifest,\n normalizeJmpContentManifest,\n type JmpContentManifest,\n} from \"./manifest\"\nimport type { JmpContentConfig, JmpContentWrapper, JmpTrustBundle } from \"../types\"\n\n/** Stable report order (matches dashboard `CLIENT_REPORTABLE_WRAPPER_SLOTS`). */\nconst WRAPPER_SLOT_ORDER: readonly string[] = [\n \"blog\",\n \"faq\",\n \"contact\",\n \"trust\",\n \"trust.summary\",\n \"trust.testimonials\",\n \"trust.reviews\",\n \"trust.stats\",\n \"trust.logos\",\n \"trust.credentials\",\n]\n\nconst discovered = new Set<string>()\nlet syncTimer: ReturnType<typeof setTimeout> | null = null\nlet pendingSyncConfig: JmpContentConfig | null = null\n\nconst reportedDedupeKeys = new Set<string>()\nlet syncHandler: ((config: JmpContentConfig) => Promise<unknown>) | null = null\n\nexport function markContentWrapper(slot: JmpContentWrapper | string): void {\n const normalized = slot.trim().toLowerCase()\n if (!normalized) {\n return\n }\n discovered.add(normalized)\n}\n\nexport function markTrustBundleWrappers(bundle: JmpTrustBundle): void {\n for (const slot of inferTrustWrapperSlots(bundle)) {\n discovered.add(slot)\n }\n}\n\nexport function inferTrustWrapperSlots(bundle: JmpTrustBundle): string[] {\n const slots = new Set<string>([\"trust\"])\n const { summary, testimonials, stats, logos, credentials } = bundle\n\n const hasSummary =\n summary.google_rating != null ||\n summary.google_review_count != null ||\n Boolean(summary.google_reviews_url?.trim()) ||\n Boolean(summary.yelp_url?.trim()) ||\n Boolean(summary.trustpilot_url?.trim()) ||\n Boolean(summary.disclaimer?.trim())\n\n if (hasSummary) {\n slots.add(\"trust.summary\")\n }\n\n let hasTestimonials = false\n let hasReviews = false\n for (const item of testimonials) {\n const bucket = item.placement?.trim().toLowerCase()\n if (bucket === \"reviews\") {\n hasReviews = true\n } else {\n hasTestimonials = true\n }\n }\n if (hasTestimonials) {\n slots.add(\"trust.testimonials\")\n }\n if (hasReviews) {\n slots.add(\"trust.reviews\")\n }\n if (stats.length > 0) {\n slots.add(\"trust.stats\")\n }\n if (logos.length > 0) {\n slots.add(\"trust.logos\")\n }\n if (credentials.length > 0) {\n slots.add(\"trust.credentials\")\n }\n\n return sortWrapperSlots([...slots])\n}\n\nexport function getDiscoveredContentWrappers(): string[] {\n return sortWrapperSlots([...discovered])\n}\n\nexport function resolveContentManifestForReport(\n config: JmpContentConfig\n): JmpContentManifest | null {\n return normalizeJmpContentManifest(config.contentManifest)\n}\n\nexport function resolveContentWrappersForReport(\n config: JmpContentConfig\n): string[] {\n const explicit = config.contentWrappers ?? []\n const auto = getDiscoveredContentWrappers()\n const fromManifest = inferWrapperSlotsFromManifest(\n resolveContentManifestForReport(config)\n )\n\n if (config.contentWrapperMode === \"manual\") {\n return sortWrapperSlots([...explicit, ...fromManifest])\n }\n\n if (config.contentWrapperMode === \"explicit-only\") {\n return sortWrapperSlots([...explicit, ...fromManifest])\n }\n\n const merged = new Set<string>([...explicit, ...auto, ...fromManifest])\n return sortWrapperSlots([...merged])\n}\n\nexport function hasReportedContentWrappers(dedupeKey: string): boolean {\n return reportedDedupeKeys.has(dedupeKey)\n}\n\nexport function rememberReportedContentWrappers(dedupeKey: string): void {\n reportedDedupeKeys.add(dedupeKey)\n}\n\nexport function setContentWrapperSyncHandler(\n handler: (config: JmpContentConfig) => Promise<unknown>\n): void {\n syncHandler = handler\n}\n\n/**\n * Debounced POST after content APIs run (build SSG, first page hit, etc.).\n */\nexport function scheduleContentWrapperSync(config: JmpContentConfig): void {\n if (!syncHandler) {\n return\n }\n pendingSyncConfig = config\n if (syncTimer) {\n clearTimeout(syncTimer)\n }\n syncTimer = setTimeout(() => {\n syncTimer = null\n const cfg = pendingSyncConfig\n pendingSyncConfig = null\n if (!cfg || !syncHandler) {\n return\n }\n void syncHandler(cfg).catch(() => {})\n }, 750)\n}\n\nexport function resetContentWrapperDiscoveryForTests(): void {\n discovered.clear()\n reportedDedupeKeys.clear()\n syncHandler = null\n if (syncTimer) {\n clearTimeout(syncTimer)\n syncTimer = null\n }\n pendingSyncConfig = null\n}\n\nfunction sortWrapperSlots(slots: string[]): string[] {\n const order = new Map(WRAPPER_SLOT_ORDER.map((slot, index) => [slot, index]))\n return [...new Set(slots)].sort((a, b) => {\n const ai = order.get(a) ?? 999\n const bi = order.get(b) ?? 999\n if (ai !== bi) {\n return ai - bi\n }\n return a.localeCompare(b)\n })\n}\n","import { JMP_ANALYTICS_SDK_VERSION } from \"../constants\"\nimport { normalizeBaseUrl } from \"../http\"\nimport type {\n JmpBlogPost,\n JmpContentConfig,\n JmpFaqItem,\n JmpLandingPage,\n JmpLandingPageDetail,\n JmpLandingPageKind,\n JmpLandingPageSitemapEntry,\n JmpSiteProfile,\n JmpTrustBundle,\n JmpWebsiteLocation,\n JmpWebsiteLocationDetail,\n} from \"../types\"\nimport { getContent, getContentText } from \"./fetch\"\nimport {\n stableJmpManifestFingerprint,\n type JmpContentManifest,\n} from \"./manifest\"\nimport {\n hasReportedContentWrappers,\n markContentWrapper,\n markTrustBundleWrappers,\n rememberReportedContentWrappers,\n resolveContentManifestForReport,\n resolveContentWrappersForReport,\n scheduleContentWrapperSync,\n setContentWrapperSyncHandler,\n} from \"./wrapper-discovery\"\n\nexport {\n getDiscoveredContentWrappers,\n inferTrustWrapperSlots,\n markContentWrapper,\n markTrustBundleWrappers,\n resetContentWrapperDiscoveryForTests,\n} from \"./wrapper-discovery\"\n\nfunction afterContentWrapperUse(config: JmpContentConfig): void {\n scheduleContentWrapperSync(config)\n}\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 markContentWrapper(\"blog\")\n afterContentWrapperUse(config)\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 markContentWrapper(\"blog\")\n afterContentWrapperUse(config)\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 markContentWrapper(\"faq\")\n afterContentWrapperUse(config)\n return data.faqs\n}\n\nexport async function getJmpLandingPages(\n config: JmpContentConfig,\n filters?: { kind?: JmpLandingPageKind; locale?: string }\n): Promise<JmpLandingPage[]> {\n const params = new URLSearchParams()\n if (filters?.kind) {\n params.set(\"kind\", filters.kind)\n }\n if (filters?.locale) {\n params.set(\"locale\", filters.locale)\n }\n const query = params.toString()\n const data = await getContent<{ pages: JmpLandingPage[] }>(\n config,\n `/api/content/landing-pages${query ? `?${query}` : \"\"}`\n )\n markContentWrapper(\"landing\")\n afterContentWrapperUse(config)\n return data.pages\n}\n\nexport async function getJmpLandingPageDetail(\n config: JmpContentConfig,\n kind: JmpLandingPageKind,\n slug: string,\n locale?: string\n): Promise<JmpLandingPageDetail | null> {\n const params = new URLSearchParams()\n if (locale) {\n params.set(\"locale\", locale)\n }\n const query = params.toString()\n try {\n const detail = await getContent<JmpLandingPageDetail>(\n config,\n `/api/content/landing-pages/${encodeURIComponent(kind)}/${encodeURIComponent(slug)}${query ? `?${query}` : \"\"}`\n )\n markContentWrapper(\"landing\")\n afterContentWrapperUse(config)\n return detail\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 getJmpLandingPageSitemap(\n config: JmpContentConfig\n): Promise<JmpLandingPageSitemapEntry[]> {\n const data = await getContent<{ entries: JmpLandingPageSitemapEntry[] }>(\n config,\n \"/api/content/landing-pages/sitemap\"\n )\n markContentWrapper(\"landing\")\n afterContentWrapperUse(config)\n return data.entries\n}\n\nexport async function getJmpLocations(\n config: JmpContentConfig\n): Promise<JmpWebsiteLocation[]> {\n const data = await getContent<{ locations: JmpWebsiteLocation[] }>(\n config,\n \"/api/content/locations\"\n )\n markContentWrapper(\"locations\")\n afterContentWrapperUse(config)\n return data.locations\n}\n\nexport async function getJmpLocationDetail(\n config: JmpContentConfig,\n slug: string\n): Promise<JmpWebsiteLocationDetail | null> {\n const normalized = slug.trim()\n if (!normalized) {\n return null\n }\n try {\n const detail = await getContent<JmpWebsiteLocationDetail>(\n config,\n `/api/content/locations/${encodeURIComponent(normalized)}`\n )\n markContentWrapper(\"locations\")\n afterContentWrapperUse(config)\n return detail\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 getJmpTrustBundle(\n config: JmpContentConfig\n): Promise<JmpTrustBundle> {\n const bundle = await getContent<JmpTrustBundle>(config, \"/api/content/trust\")\n markTrustBundleWrappers(bundle)\n afterContentWrapperUse(config)\n return bundle\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 markContentWrapper(\"contact\")\n afterContentWrapperUse(config)\n return data.site\n}\n\nexport async function getJmpRobotsTxt(config: JmpContentConfig): Promise<string> {\n return getContentText(config, \"/api/content/robots\")\n}\n\n/**\n * Tell the JMP dashboard which content wrappers this site wires (blog, faq, trust.*, contact).\n * Uses auto-discovered slots from content API usage unless `contentWrapperMode` is `manual`.\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 = resolveContentWrappersForReport(config)\n if (!wrappers.length) {\n return { ok: false, skipped: true }\n }\n\n const manifest = resolveContentManifestForReport(config)\n const dedupeKey = `${config.trackingKey}:${[...wrappers].sort().join(\",\")}:${stableJmpManifestFingerprint(manifest)}`\n if (hasReportedContentWrappers(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, {\n wrappers,\n manifest,\n })\n }\n\n const body: {\n wrappers: string[]\n sdkVersion: string\n manifest?: JmpContentManifest\n } = {\n wrappers,\n sdkVersion: JMP_ANALYTICS_SDK_VERSION,\n }\n if (manifest) {\n body.manifest = manifest\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(body),\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 rememberReportedContentWrappers(dedupeKey)\n const data = JSON.parse(text) as { wrappers?: string[] }\n return { ok: true, wrappers: data.wrappers ?? wrappers }\n}\n\nsetContentWrapperSyncHandler(registerJmpContentWrappers)\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","import type { JmpContentManifest } from \"./content/manifest\"\nimport type { JmpContentConfig, JmpContentWrapper } from \"./types\"\nimport { readJmpPublicEnvFromProcess } from \"./env-public\"\n\nexport type JmpSiteSocialLinks = {\n linkedin: string | null\n instagram: string | null\n facebook: string | null\n x: string | null\n youtube: string | null\n}\n\n/** Per-site values used when the dashboard profile is missing or fields are empty. */\nexport type JmpSiteContactFallback = {\n displayName?: string\n founderName?: string\n email?: string\n phone?: string | null\n linkedin?: string | null\n instagram?: string | null\n facebook?: string | null\n x?: string | null\n youtube?: string | null\n hours?: string | null\n serviceArea?: string | null\n address?: string | null\n googleMapsUrl?: string | null\n googleMapsEmbedUrl?: string | null\n dashboardDemoUrl?: string\n profilePhotoSrc?: string\n privacyUrl?: string\n termsUrl?: string\n}\n\nexport type JmpSiteDefinition = {\n /**\n * Optional override. Wrappers are auto-discovered when content loaders run\n * (`loadBlogPosts`, `loadFaqs`, etc.) and reported to the dashboard.\n */\n contentWrappers?: JmpContentWrapper[]\n /**\n * Routes, trust block order, and contact extras for dashboard layout sync.\n * Reported with capabilities when the site loads content from the portal.\n */\n contentManifest?: JmpContentManifest\n /**\n * Public site origin (no trailing slash), e.g. `https://example.com`.\n * Used to resolve relative profile image paths in structured data.\n */\n siteUrl?: string\n /**\n * JMP dashboard origin (no trailing slash), e.g. `https://portal.example.com`.\n * Fallback when `NEXT_PUBLIC_JMP_ANALYTICS_URL` is missing from the client bundle.\n */\n analyticsPortalUrl?: string\n contactFallback?: JmpSiteContactFallback\n /**\n * `extras` keys to read from the dashboard site profile (hyphenated keys from admin).\n */\n contactExtraKeys?: {\n founderName?: readonly string[]\n dashboardDemoUrl?: readonly string[]\n profilePhotoSrc?: readonly string[]\n hours?: readonly string[]\n serviceArea?: readonly string[]\n googleMapsEmbedUrl?: readonly string[]\n }\n}\n\nlet registeredSite: JmpSiteDefinition | null = null\n\n/**\n * Register site-specific JMP integration options once (typically from `jmp.config.ts`).\n * Call at module load time before server content or instrumentation runs.\n */\nexport function defineJmpSite(definition: JmpSiteDefinition): JmpSiteDefinition {\n registeredSite = definition\n return definition\n}\n\nexport function getJmpSiteDefinition(): JmpSiteDefinition | null {\n return registeredSite\n}\n\n/** Server content + lead config: env vars + wrappers from {@link defineJmpSite}. */\nexport function getJmpContentConfig(): JmpContentConfig | null {\n const { analyticsUrl, trackingKey } = readJmpPublicEnvFromProcess()\n if (!analyticsUrl || !trackingKey) {\n return null\n }\n const site = getJmpSiteDefinition()\n return {\n baseUrl: analyticsUrl,\n trackingKey,\n contentWrappers: site?.contentWrappers ?? undefined,\n contentManifest: site?.contentManifest ?? undefined,\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/astro/register.ts","../../src/landing-capture.ts","../../package.json","../../src/constants.ts","../../src/http.ts","../../src/content/manifest.ts","../../src/content/wrapper-discovery.ts","../../src/content/api.ts","../../src/env-public.ts","../../src/site-config.ts"],"sourcesContent":["import { registerJmpContentWrappers } from \"../index\"\nimport { getJmpContentConfig } from \"../site-config\"\n\n/**\n * Register content wrappers with the dashboard (call once per build).\n * Import `./jmp.config` in `astro.config.mjs` before calling this, or use {@link jmpAnalytics} integration.\n */\nexport async function registerJmpAstroContent(): Promise<void> {\n const config = getJmpContentConfig()\n if (!config) {\n return\n }\n await registerJmpContentWrappers(config).catch(() => {})\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 \"name\": \"@jmp-technologies/analytics\",\n \"version\": \"0.1.34\",\n \"description\": \"JMP Analytics tracking plus blog, FAQ, trust content, and site contact profile for client websites\",\n \"license\": \"UNLICENSED\",\n \"keywords\": [\n \"jmp\",\n \"analytics\",\n \"tracking\",\n \"jmp-analytics\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/jmp-technologies/jmp-dashboard.git\",\n \"directory\": \"packages/jmp-technologies-analytics\"\n },\n \"homepage\": \"https://github.com/jmp-technologies/jmp-dashboard/tree/main/packages/jmp-technologies-analytics#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/jmp-technologies/jmp-dashboard/issues\"\n },\n \"type\": \"module\",\n \"bin\": {\n \"jmp-init\": \"./bin/jmp-init.mjs\"\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"./react\": {\n \"types\": \"./dist/react.d.ts\",\n \"import\": \"./dist/react.js\",\n \"require\": \"./dist/react.cjs\"\n },\n \"./next\": {\n \"types\": \"./dist/next.d.ts\",\n \"import\": \"./dist/next.js\",\n \"require\": \"./dist/next.cjs\"\n },\n \"./next/site-root-server\": {\n \"types\": \"./dist/next/site-root-server.d.ts\",\n \"import\": \"./dist/next/site-root-server.js\",\n \"require\": \"./dist/next/site-root-server.cjs\"\n },\n \"./next/landing-capture-script-server\": {\n \"types\": \"./dist/next/landing-capture-script-server.d.ts\",\n \"import\": \"./dist/next/landing-capture-script-server.js\",\n \"require\": \"./dist/next/landing-capture-script-server.cjs\"\n },\n \"./next/middleware\": {\n \"types\": \"./dist/next/middleware.d.ts\",\n \"import\": \"./dist/next/middleware.js\",\n \"require\": \"./dist/next/middleware.cjs\"\n },\n \"./next/instrumentation\": {\n \"types\": \"./dist/next/instrumentation.d.ts\",\n \"import\": \"./dist/next/instrumentation.js\",\n \"require\": \"./dist/next/instrumentation.cjs\"\n },\n \"./next/content\": {\n \"types\": \"./dist/next/content.d.ts\",\n \"import\": \"./dist/next/content.js\",\n \"require\": \"./dist/next/content.cjs\"\n },\n \"./next/landing-pages\": {\n \"types\": \"./dist/next/landing-pages.d.ts\",\n \"import\": \"./dist/next/landing-pages.js\",\n \"require\": \"./dist/next/landing-pages.cjs\"\n },\n \"./next/locations\": {\n \"types\": \"./dist/next/locations.d.ts\",\n \"import\": \"./dist/next/locations.js\",\n \"require\": \"./dist/next/locations.cjs\"\n },\n \"./astro/content\": {\n \"types\": \"./dist/astro/content.d.ts\",\n \"import\": \"./dist/astro/content.js\",\n \"require\": \"./dist/astro/content.cjs\"\n },\n \"./astro/client\": {\n \"types\": \"./dist/astro/client.d.ts\",\n \"import\": \"./dist/astro/client.js\",\n \"require\": \"./dist/astro/client.cjs\"\n },\n \"./astro/middleware\": {\n \"types\": \"./dist/astro/middleware.d.ts\",\n \"import\": \"./dist/astro/middleware.js\",\n \"require\": \"./dist/astro/middleware.cjs\"\n },\n \"./astro/register\": {\n \"types\": \"./dist/astro/register.d.ts\",\n \"import\": \"./dist/astro/register.js\",\n \"require\": \"./dist/astro/register.cjs\"\n },\n \"./astro/integration\": {\n \"types\": \"./dist/astro/integration.d.ts\",\n \"import\": \"./dist/astro/integration.js\",\n \"require\": \"./dist/astro/integration.cjs\"\n }\n },\n \"files\": [\n \"dist\",\n \"bin\",\n \"docs\",\n \"README.md\"\n ],\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"scripts\": {\n \"build\": \"tsup src/index.ts src/react.tsx src/next.tsx src/next/google-tag.tsx src/next/landing-capture-script-server.tsx src/next/site-root-server.tsx src/next/middleware.ts src/next/instrumentation.ts src/next/content.ts src/next/landing-pages.tsx src/next/locations.tsx src/astro/content.ts src/astro/client.ts src/astro/middleware.ts src/astro/register.ts src/astro/integration.ts --format esm,cjs --dts --clean --sourcemap --external react --external react-dom --external next\"\n },\n \"peerDependencies\": {\n \"next\": \">=14\",\n \"react\": \">=18\",\n \"react-dom\": \">=18\"\n },\n \"peerDependenciesMeta\": {\n \"react\": {\n \"optional\": true\n },\n \"react-dom\": {\n \"optional\": true\n },\n \"next\": {\n \"optional\": true\n },\n \"astro\": {\n \"optional\": true\n }\n },\n \"devDependencies\": {\n \"@types/react\": \"^19\",\n \"@types/react-dom\": \"^19\",\n \"next\": \"^16.2.3\",\n \"react\": \"^19.2.0\",\n \"react-dom\": \"^19.2.0\",\n \"tsup\": \"^8.5.1\",\n \"typescript\": \"^5.9.3\"\n },\n \"dependencies\": {}\n}\n","import { version as packageVersion } from \"../package.json\"\n\n/** Published package version — sent with capabilities for admin readiness. */\nexport const JMP_ANALYTICS_SDK_VERSION = packageVersion\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 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","/** Mirrors jmp-dashboard `lib/content/wrappers/client-manifest.ts` (keep in sync). */\n\nexport const JMP_CONTENT_MANIFEST_VERSION = 1\n\nexport type JmpTrustSection =\n | \"summary\"\n | \"testimonials\"\n | \"reviews\"\n | \"stats\"\n | \"logos\"\n | \"credentials\"\n\nexport type JmpContentRouteManifest = {\n wrapper: string\n pattern: string\n index?: string\n label?: string\n}\n\nexport type JmpTrustBlockManifest = {\n section: JmpTrustSection\n label?: string\n}\n\nexport type JmpContactExtraManifest = {\n key: string\n label: string\n}\n\nexport type JmpExternalSanitySourceManifest = {\n studioUrl?: string\n}\n\nexport type JmpExternalShopifySourceManifest = {\n adminUrl?: string\n storeUrl?: string\n}\n\nexport type JmpExternalAdminLinkManifest = {\n id: string\n label: string\n url: string\n}\n\nexport type JmpExternalSourcesManifest = {\n sanity?: JmpExternalSanitySourceManifest\n shopify?: JmpExternalShopifySourceManifest\n links?: JmpExternalAdminLinkManifest[]\n}\n\nexport type JmpContentManifest = {\n version?: number\n routes?: JmpContentRouteManifest[]\n externalSources?: JmpExternalSourcesManifest\n trust?: {\n blocks?: JmpTrustBlockManifest[]\n labels?: Partial<Record<JmpTrustSection, string>>\n mergeTestimonialsReviews?: boolean\n }\n contact?: {\n extras?: JmpContactExtraManifest[]\n }\n}\n\nconst TRUST_SECTIONS = new Set<string>([\n \"summary\",\n \"testimonials\",\n \"reviews\",\n \"stats\",\n \"logos\",\n \"credentials\",\n])\n\nfunction normalizeKey(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n}\n\nfunction isValidExtraKey(key: string): boolean {\n return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(key)\n}\n\nconst MAX_EXTERNAL_ADMIN_LINKS = 10\nconst MAX_EXTERNAL_ADMIN_LINK_LABEL_LENGTH = 80\nconst EXTERNAL_ADMIN_LINK_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/\n\nfunction tryNormalizeHttpUrl(input: unknown): string | null {\n if (typeof input !== \"string\" || !input.trim()) {\n return null\n }\n try {\n const url = new URL(input.trim())\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") {\n return null\n }\n return url.toString().replace(/\\/$/, \"\")\n } catch {\n return null\n }\n}\n\nfunction normalizeShopifyExternalSource(\n shopify: JmpExternalShopifySourceManifest | undefined\n): JmpExternalShopifySourceManifest | null {\n if (!shopify || typeof shopify !== \"object\") {\n return null\n }\n const adminUrl = tryNormalizeHttpUrl(shopify.adminUrl)\n const storeUrl = tryNormalizeHttpUrl(shopify.storeUrl)\n if (!adminUrl && !storeUrl) {\n return null\n }\n return {\n ...(adminUrl ? { adminUrl } : {}),\n ...(storeUrl ? { storeUrl } : {}),\n }\n}\n\nfunction normalizeExternalAdminLinkEntries(\n links: JmpExternalAdminLinkManifest[] | undefined\n): JmpExternalAdminLinkManifest[] {\n if (!Array.isArray(links)) {\n return []\n }\n const seenIds = new Set<string>()\n const out: JmpExternalAdminLinkManifest[] = []\n for (const raw of links) {\n if (!raw || typeof raw !== \"object\" || out.length >= MAX_EXTERNAL_ADMIN_LINKS) {\n continue\n }\n const id =\n typeof raw.id === \"string\"\n ? raw.id.trim().toLowerCase().replace(/[^a-z0-9-]+/g, \"-\").replace(/^-+|-+$/g, \"\")\n : \"\"\n const label =\n typeof raw.label === \"string\"\n ? raw.label.trim().slice(0, MAX_EXTERNAL_ADMIN_LINK_LABEL_LENGTH)\n : \"\"\n const url = tryNormalizeHttpUrl(raw.url)\n if (\n !id ||\n !label ||\n !url ||\n !EXTERNAL_ADMIN_LINK_ID_PATTERN.test(id) ||\n seenIds.has(id)\n ) {\n continue\n }\n seenIds.add(id)\n out.push({ id, label, url })\n }\n return out\n}\n\nfunction normalizeExternalSources(\n externalSources: JmpExternalSourcesManifest | undefined\n): JmpExternalSourcesManifest | null {\n if (!externalSources || typeof externalSources !== \"object\") {\n return null\n }\n const studioUrl = tryNormalizeHttpUrl(externalSources.sanity?.studioUrl)\n const shopify = normalizeShopifyExternalSource(externalSources.shopify)\n const links = normalizeExternalAdminLinkEntries(externalSources.links)\n if (!studioUrl && !shopify && links.length === 0) {\n return null\n }\n return {\n ...(studioUrl ? { sanity: { studioUrl } } : {}),\n ...(shopify ? { shopify } : {}),\n ...(links.length > 0 ? { links } : {}),\n }\n}\n\nexport function normalizeJmpContentManifest(\n input: JmpContentManifest | null | undefined\n): JmpContentManifest | null {\n if (!input || typeof input !== \"object\") {\n return null\n }\n\n const routes = normalizeRoutes(input.routes)\n const externalSources = normalizeExternalSources(input.externalSources)\n const trust = normalizeTrust(input.trust)\n const contact = normalizeContact(input.contact)\n\n if (routes.length === 0 && !externalSources && !trust && !contact) {\n return null\n }\n\n return {\n version: JMP_CONTENT_MANIFEST_VERSION,\n ...(routes.length > 0 ? { routes } : {}),\n ...(externalSources ? { externalSources } : {}),\n ...(trust ? { trust } : {}),\n ...(contact ? { contact } : {}),\n }\n}\n\nfunction normalizeRoutes(\n routes: JmpContentRouteManifest[] | undefined\n): JmpContentRouteManifest[] {\n if (!Array.isArray(routes)) {\n return []\n }\n const seen = new Set<string>()\n const out: JmpContentRouteManifest[] = []\n for (const raw of routes) {\n if (!raw || typeof raw !== \"object\") continue\n const wrapper =\n typeof raw.wrapper === \"string\" ? raw.wrapper.trim().toLowerCase() : \"\"\n const pattern =\n typeof raw.pattern === \"string\" ? raw.pattern.trim() : \"\"\n if (!wrapper || !pattern) continue\n const dedupe = `${wrapper}:${pattern}`\n if (seen.has(dedupe)) continue\n seen.add(dedupe)\n out.push({\n wrapper,\n pattern,\n ...(typeof raw.index === \"string\" && raw.index.trim()\n ? { index: raw.index.trim() }\n : {}),\n ...(typeof raw.label === \"string\" && raw.label.trim()\n ? { label: raw.label.trim() }\n : {}),\n })\n }\n return out\n}\n\nfunction normalizeTrust(\n trust: JmpContentManifest[\"trust\"] | undefined\n): JmpContentManifest[\"trust\"] | null {\n if (!trust || typeof trust !== \"object\") return null\n const blocks = normalizeTrustBlocks(trust.blocks)\n const labels = normalizeTrustLabels(trust.labels)\n const mergeTestimonialsReviews = trust.mergeTestimonialsReviews === true\n if (blocks.length === 0 && !labels && !mergeTestimonialsReviews) return null\n return {\n ...(blocks.length > 0 ? { blocks } : {}),\n ...(labels ? { labels } : {}),\n ...(mergeTestimonialsReviews ? { mergeTestimonialsReviews: true } : {}),\n }\n}\n\nfunction normalizeTrustBlocks(\n blocks: JmpTrustBlockManifest[] | undefined\n): JmpTrustBlockManifest[] {\n if (!Array.isArray(blocks)) return []\n const seen = new Set<string>()\n const out: JmpTrustBlockManifest[] = []\n for (const raw of blocks) {\n if (!raw || typeof raw !== \"object\") continue\n const section =\n typeof raw.section === \"string\" ? raw.section.trim().toLowerCase() : \"\"\n if (!TRUST_SECTIONS.has(section) || seen.has(section)) continue\n seen.add(section)\n out.push({\n section: section as JmpTrustSection,\n ...(typeof raw.label === \"string\" && raw.label.trim()\n ? { label: raw.label.trim() }\n : {}),\n })\n }\n return out\n}\n\nfunction normalizeTrustLabels(\n labels: Partial<Record<JmpTrustSection, string>> | undefined\n): Partial<Record<JmpTrustSection, string>> | null {\n if (!labels || typeof labels !== \"object\") return null\n const out: Partial<Record<JmpTrustSection, string>> = {}\n for (const [key, value] of Object.entries(labels)) {\n if (TRUST_SECTIONS.has(key) && typeof value === \"string\" && value.trim()) {\n out[key as JmpTrustSection] = value.trim()\n }\n }\n return Object.keys(out).length > 0 ? out : null\n}\n\nfunction normalizeContact(\n contact: JmpContentManifest[\"contact\"] | undefined\n): JmpContentManifest[\"contact\"] | null {\n if (!contact || typeof contact !== \"object\") return null\n const extras = normalizeContactExtras(contact.extras)\n return extras.length > 0 ? { extras } : null\n}\n\nfunction normalizeContactExtras(\n extras: JmpContactExtraManifest[] | undefined\n): JmpContactExtraManifest[] {\n if (!Array.isArray(extras)) return []\n const seen = new Set<string>()\n const out: JmpContactExtraManifest[] = []\n for (const raw of extras) {\n if (!raw || typeof raw !== \"object\") continue\n const label = typeof raw.label === \"string\" ? raw.label.trim() : \"\"\n const key = normalizeKey(\n typeof raw.key === \"string\" && raw.key.trim() ? raw.key : label\n )\n if (!key || !label || !isValidExtraKey(key) || seen.has(key)) continue\n seen.add(key)\n out.push({ key, label })\n }\n return out\n}\n\nexport function stableJmpManifestFingerprint(\n manifest: JmpContentManifest | null\n): string {\n return manifest ? JSON.stringify(manifest) : \"\"\n}\n\nexport function inferWrapperSlotsFromManifest(\n manifest: JmpContentManifest | null\n): string[] {\n const slots = new Set<string>()\n for (const route of manifest?.routes ?? []) {\n slots.add(route.wrapper)\n }\n for (const block of manifest?.trust?.blocks ?? []) {\n slots.add(`trust.${block.section}`)\n slots.add(\"trust\")\n }\n if (manifest?.contact?.extras?.length) {\n slots.add(\"contact\")\n }\n return [...slots]\n}\n","import {\n inferWrapperSlotsFromManifest,\n normalizeJmpContentManifest,\n type JmpContentManifest,\n} from \"./manifest\"\nimport type { JmpContentConfig, JmpContentWrapper, JmpTrustBundle } from \"../types\"\n\n/** Stable report order (matches dashboard `CLIENT_REPORTABLE_WRAPPER_SLOTS`). */\nconst WRAPPER_SLOT_ORDER: readonly string[] = [\n \"blog\",\n \"faq\",\n \"contact\",\n \"trust\",\n \"trust.summary\",\n \"trust.testimonials\",\n \"trust.reviews\",\n \"trust.stats\",\n \"trust.logos\",\n \"trust.credentials\",\n]\n\nconst discovered = new Set<string>()\nlet syncTimer: ReturnType<typeof setTimeout> | null = null\nlet pendingSyncConfig: JmpContentConfig | null = null\n\nconst reportedDedupeKeys = new Set<string>()\nlet syncHandler: ((config: JmpContentConfig) => Promise<unknown>) | null = null\n\nexport function markContentWrapper(slot: JmpContentWrapper | string): void {\n const normalized = slot.trim().toLowerCase()\n if (!normalized) {\n return\n }\n discovered.add(normalized)\n}\n\nexport function markTrustBundleWrappers(bundle: JmpTrustBundle): void {\n for (const slot of inferTrustWrapperSlots(bundle)) {\n discovered.add(slot)\n }\n}\n\nexport function inferTrustWrapperSlots(bundle: JmpTrustBundle): string[] {\n const slots = new Set<string>([\"trust\"])\n const { summary, testimonials, stats, logos, credentials } = bundle\n\n const hasSummary =\n summary.google_rating != null ||\n summary.google_review_count != null ||\n Boolean(summary.google_reviews_url?.trim()) ||\n Boolean(summary.yelp_url?.trim()) ||\n Boolean(summary.trustpilot_url?.trim()) ||\n Boolean(summary.disclaimer?.trim())\n\n if (hasSummary) {\n slots.add(\"trust.summary\")\n }\n\n let hasTestimonials = false\n let hasReviews = false\n for (const item of testimonials) {\n const bucket = item.placement?.trim().toLowerCase()\n if (bucket === \"reviews\") {\n hasReviews = true\n } else {\n hasTestimonials = true\n }\n }\n if (hasTestimonials) {\n slots.add(\"trust.testimonials\")\n }\n if (hasReviews) {\n slots.add(\"trust.reviews\")\n }\n if (stats.length > 0) {\n slots.add(\"trust.stats\")\n }\n if (logos.length > 0) {\n slots.add(\"trust.logos\")\n }\n if (credentials.length > 0) {\n slots.add(\"trust.credentials\")\n }\n\n return sortWrapperSlots([...slots])\n}\n\nexport function getDiscoveredContentWrappers(): string[] {\n return sortWrapperSlots([...discovered])\n}\n\nexport function resolveContentManifestForReport(\n config: JmpContentConfig\n): JmpContentManifest | null {\n return normalizeJmpContentManifest(config.contentManifest)\n}\n\nexport function resolveContentWrappersForReport(\n config: JmpContentConfig\n): string[] {\n const explicit = config.contentWrappers ?? []\n const auto = getDiscoveredContentWrappers()\n const fromManifest = inferWrapperSlotsFromManifest(\n resolveContentManifestForReport(config)\n )\n\n if (config.contentWrapperMode === \"manual\") {\n return sortWrapperSlots([...explicit, ...fromManifest])\n }\n\n if (config.contentWrapperMode === \"explicit-only\") {\n return sortWrapperSlots([...explicit, ...fromManifest])\n }\n\n const merged = new Set<string>([...explicit, ...auto, ...fromManifest])\n return sortWrapperSlots([...merged])\n}\n\nexport function hasReportedContentWrappers(dedupeKey: string): boolean {\n return reportedDedupeKeys.has(dedupeKey)\n}\n\nexport function rememberReportedContentWrappers(dedupeKey: string): void {\n reportedDedupeKeys.add(dedupeKey)\n}\n\nexport function setContentWrapperSyncHandler(\n handler: (config: JmpContentConfig) => Promise<unknown>\n): void {\n syncHandler = handler\n}\n\n/**\n * Debounced POST after content APIs run (build SSG, first page hit, etc.).\n */\nexport function scheduleContentWrapperSync(config: JmpContentConfig): void {\n if (!syncHandler) {\n return\n }\n pendingSyncConfig = config\n if (syncTimer) {\n clearTimeout(syncTimer)\n }\n syncTimer = setTimeout(() => {\n syncTimer = null\n const cfg = pendingSyncConfig\n pendingSyncConfig = null\n if (!cfg || !syncHandler) {\n return\n }\n void syncHandler(cfg).catch(() => {})\n }, 750)\n}\n\nexport function resetContentWrapperDiscoveryForTests(): void {\n discovered.clear()\n reportedDedupeKeys.clear()\n syncHandler = null\n if (syncTimer) {\n clearTimeout(syncTimer)\n syncTimer = null\n }\n pendingSyncConfig = null\n}\n\nfunction sortWrapperSlots(slots: string[]): string[] {\n const order = new Map(WRAPPER_SLOT_ORDER.map((slot, index) => [slot, index]))\n return [...new Set(slots)].sort((a, b) => {\n const ai = order.get(a) ?? 999\n const bi = order.get(b) ?? 999\n if (ai !== bi) {\n return ai - bi\n }\n return a.localeCompare(b)\n })\n}\n","import { JMP_ANALYTICS_SDK_VERSION } from \"../constants\"\nimport { normalizeBaseUrl } from \"../http\"\nimport type {\n JmpBlogPost,\n JmpContentConfig,\n JmpFaqItem,\n JmpLandingPage,\n JmpLandingPageDetail,\n JmpLandingPageKind,\n JmpLandingPageSitemapEntry,\n JmpSiteProfile,\n JmpTrustBundle,\n JmpWebsiteLocation,\n JmpWebsiteLocationDetail,\n} from \"../types\"\nimport { getContent, getContentText } from \"./fetch\"\nimport {\n stableJmpManifestFingerprint,\n type JmpContentManifest,\n} from \"./manifest\"\nimport {\n hasReportedContentWrappers,\n markContentWrapper,\n markTrustBundleWrappers,\n rememberReportedContentWrappers,\n resolveContentManifestForReport,\n resolveContentWrappersForReport,\n scheduleContentWrapperSync,\n setContentWrapperSyncHandler,\n} from \"./wrapper-discovery\"\n\nexport {\n getDiscoveredContentWrappers,\n inferTrustWrapperSlots,\n markContentWrapper,\n markTrustBundleWrappers,\n resetContentWrapperDiscoveryForTests,\n} from \"./wrapper-discovery\"\n\nfunction afterContentWrapperUse(config: JmpContentConfig): void {\n scheduleContentWrapperSync(config)\n}\n\nexport async function getJmpBlogPosts(\n config: JmpContentConfig,\n filters?: { locale?: string }\n): Promise<JmpBlogPost[]> {\n const params = new URLSearchParams()\n if (filters?.locale) {\n params.set(\"locale\", filters.locale)\n }\n const query = params.toString()\n const data = await getContent<{ posts: JmpBlogPost[] }>(\n config,\n `/api/content/blog${query ? `?${query}` : \"\"}`\n )\n markContentWrapper(\"blog\")\n afterContentWrapperUse(config)\n return data.posts\n}\n\nexport async function getJmpBlogPost(\n config: JmpContentConfig,\n slug: string,\n locale?: string\n): Promise<JmpBlogPost | null> {\n try {\n const params = new URLSearchParams()\n if (locale) {\n params.set(\"locale\", locale)\n }\n const query = params.toString()\n const data = await getContent<{ post: JmpBlogPost; alternates?: unknown }>(\n config,\n `/api/content/blog/${encodeURIComponent(slug)}${query ? `?${query}` : \"\"}`\n )\n markContentWrapper(\"blog\")\n afterContentWrapperUse(config)\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 getJmpBlogPostDetail(\n config: JmpContentConfig,\n slug: string,\n locale?: string\n): Promise<import(\"../types\").JmpBlogPostDetail | null> {\n try {\n const params = new URLSearchParams()\n if (locale) {\n params.set(\"locale\", locale)\n }\n const query = params.toString()\n const data = await getContent<import(\"../types\").JmpBlogPostDetail>(\n config,\n `/api/content/blog/${encodeURIComponent(slug)}${query ? `?${query}` : \"\"}`\n )\n markContentWrapper(\"blog\")\n afterContentWrapperUse(config)\n return data\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 filters?: { locale?: string }\n): Promise<JmpFaqItem[]> {\n const params = new URLSearchParams()\n if (filters?.locale) {\n params.set(\"locale\", filters.locale)\n }\n const query = params.toString()\n const data = await getContent<{ faqs: JmpFaqItem[] }>(\n config,\n `/api/content/faqs${query ? `?${query}` : \"\"}`\n )\n markContentWrapper(\"faq\")\n afterContentWrapperUse(config)\n return data.faqs\n}\n\nexport async function getJmpFaqDetail(\n config: JmpContentConfig,\n slug: string,\n locale?: string\n): Promise<import(\"../types\").JmpFaqItemDetail | null> {\n try {\n const params = new URLSearchParams()\n if (locale) {\n params.set(\"locale\", locale)\n }\n const query = params.toString()\n const data = await getContent<import(\"../types\").JmpFaqItemDetail>(\n config,\n `/api/content/faqs/${encodeURIComponent(slug)}${query ? `?${query}` : \"\"}`\n )\n markContentWrapper(\"faq\")\n afterContentWrapperUse(config)\n return data\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 getJmpLandingPages(\n config: JmpContentConfig,\n filters?: { kind?: JmpLandingPageKind; locale?: string }\n): Promise<JmpLandingPage[]> {\n const params = new URLSearchParams()\n if (filters?.kind) {\n params.set(\"kind\", filters.kind)\n }\n if (filters?.locale) {\n params.set(\"locale\", filters.locale)\n }\n const query = params.toString()\n const data = await getContent<{ pages: JmpLandingPage[] }>(\n config,\n `/api/content/landing-pages${query ? `?${query}` : \"\"}`\n )\n markContentWrapper(\"landing\")\n afterContentWrapperUse(config)\n return data.pages\n}\n\nexport async function getJmpLandingPageDetail(\n config: JmpContentConfig,\n kind: JmpLandingPageKind,\n slug: string,\n locale?: string\n): Promise<JmpLandingPageDetail | null> {\n const params = new URLSearchParams()\n if (locale) {\n params.set(\"locale\", locale)\n }\n const query = params.toString()\n try {\n const detail = await getContent<JmpLandingPageDetail>(\n config,\n `/api/content/landing-pages/${encodeURIComponent(kind)}/${encodeURIComponent(slug)}${query ? `?${query}` : \"\"}`\n )\n markContentWrapper(\"landing\")\n afterContentWrapperUse(config)\n return detail\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 getJmpLandingPageSitemap(\n config: JmpContentConfig\n): Promise<JmpLandingPageSitemapEntry[]> {\n const data = await getContent<{ entries: JmpLandingPageSitemapEntry[] }>(\n config,\n \"/api/content/landing-pages/sitemap\"\n )\n markContentWrapper(\"landing\")\n afterContentWrapperUse(config)\n return data.entries\n}\n\nexport async function getJmpLocations(\n config: JmpContentConfig\n): Promise<JmpWebsiteLocation[]> {\n const data = await getContent<{ locations: JmpWebsiteLocation[] }>(\n config,\n \"/api/content/locations\"\n )\n markContentWrapper(\"locations\")\n afterContentWrapperUse(config)\n return data.locations\n}\n\nexport async function getJmpLocationDetail(\n config: JmpContentConfig,\n slug: string\n): Promise<JmpWebsiteLocationDetail | null> {\n const normalized = slug.trim()\n if (!normalized) {\n return null\n }\n try {\n const detail = await getContent<JmpWebsiteLocationDetail>(\n config,\n `/api/content/locations/${encodeURIComponent(normalized)}`\n )\n markContentWrapper(\"locations\")\n afterContentWrapperUse(config)\n return detail\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 getJmpTrustBundle(\n config: JmpContentConfig\n): Promise<JmpTrustBundle> {\n const bundle = await getContent<JmpTrustBundle>(config, \"/api/content/trust\")\n markTrustBundleWrappers(bundle)\n afterContentWrapperUse(config)\n return bundle\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 markContentWrapper(\"contact\")\n afterContentWrapperUse(config)\n return data.site\n}\n\nexport async function getJmpRobotsTxt(config: JmpContentConfig): Promise<string> {\n return getContentText(config, \"/api/content/robots\")\n}\n\n/**\n * Tell the JMP dashboard which content wrappers this site wires (blog, faq, trust.*, contact).\n * Uses auto-discovered slots from content API usage unless `contentWrapperMode` is `manual`.\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 = resolveContentWrappersForReport(config)\n if (!wrappers.length) {\n return { ok: false, skipped: true }\n }\n\n const manifest = resolveContentManifestForReport(config)\n const dedupeKey = `${config.trackingKey}:${[...wrappers].sort().join(\",\")}:${stableJmpManifestFingerprint(manifest)}`\n if (hasReportedContentWrappers(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, {\n wrappers,\n manifest,\n })\n }\n\n const body: {\n wrappers: string[]\n sdkVersion: string\n manifest?: JmpContentManifest\n } = {\n wrappers,\n sdkVersion: JMP_ANALYTICS_SDK_VERSION,\n }\n if (manifest) {\n body.manifest = manifest\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(body),\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 rememberReportedContentWrappers(dedupeKey)\n const data = JSON.parse(text) as { wrappers?: string[] }\n return { ok: true, wrappers: data.wrappers ?? wrappers }\n}\n\nsetContentWrapperSyncHandler(registerJmpContentWrappers)\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","import type { JmpContentManifest } from \"./content/manifest\"\nimport type { JmpContentConfig, JmpContentWrapper } from \"./types\"\nimport { readJmpPublicEnvFromProcess } from \"./env-public\"\n\nexport type JmpSiteSocialLinks = {\n linkedin: string | null\n instagram: string | null\n facebook: string | null\n x: string | null\n youtube: string | null\n}\n\n/** Per-site values used when the dashboard profile is missing or fields are empty. */\nexport type JmpSiteContactFallback = {\n displayName?: string\n founderName?: string\n email?: string\n phone?: string | null\n linkedin?: string | null\n instagram?: string | null\n facebook?: string | null\n x?: string | null\n youtube?: string | null\n hours?: string | null\n serviceArea?: string | null\n address?: string | null\n googleMapsUrl?: string | null\n googleMapsEmbedUrl?: string | null\n dashboardDemoUrl?: string\n profilePhotoSrc?: string\n privacyUrl?: string\n termsUrl?: string\n}\n\nexport type JmpSiteDefinition = {\n /**\n * Optional override. Wrappers are auto-discovered when content loaders run\n * (`loadBlogPosts`, `loadFaqs`, etc.) and reported to the dashboard.\n */\n contentWrappers?: JmpContentWrapper[]\n /**\n * Routes, trust block order, and contact extras for dashboard layout sync.\n * Reported with capabilities when the site loads content from the portal.\n */\n contentManifest?: JmpContentManifest\n /**\n * Public site origin (no trailing slash), e.g. `https://example.com`.\n * Used to resolve relative profile image paths in structured data.\n */\n siteUrl?: string\n /**\n * JMP dashboard origin (no trailing slash), e.g. `https://portal.example.com`.\n * Fallback when `NEXT_PUBLIC_JMP_ANALYTICS_URL` is missing from the client bundle.\n */\n analyticsPortalUrl?: string\n contactFallback?: JmpSiteContactFallback\n /**\n * `extras` keys to read from the dashboard site profile (hyphenated keys from admin).\n */\n contactExtraKeys?: {\n founderName?: readonly string[]\n dashboardDemoUrl?: readonly string[]\n profilePhotoSrc?: readonly string[]\n hours?: readonly string[]\n serviceArea?: readonly string[]\n googleMapsEmbedUrl?: readonly string[]\n }\n}\n\nlet registeredSite: JmpSiteDefinition | null = null\n\n/**\n * Register site-specific JMP integration options once (typically from `jmp.config.ts`).\n * Call at module load time before server content or instrumentation runs.\n */\nexport function defineJmpSite(definition: JmpSiteDefinition): JmpSiteDefinition {\n registeredSite = definition\n return definition\n}\n\nexport function getJmpSiteDefinition(): JmpSiteDefinition | null {\n return registeredSite\n}\n\n/** Server content + lead config: env vars + wrappers from {@link defineJmpSite}. */\nexport function getJmpContentConfig(): JmpContentConfig | null {\n const { analyticsUrl, trackingKey } = readJmpPublicEnvFromProcess()\n if (!analyticsUrl || !trackingKey) {\n return null\n }\n const site = getJmpSiteDefinition()\n return {\n baseUrl: analyticsUrl,\n trackingKey,\n contentWrappers: site?.contentWrappers ?? undefined,\n contentManifest: site?.contentManifest ?? undefined,\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  registerJmpAstroContent
3
- } from "../chunk-7623G3XL.js";
4
- import "../chunk-NI7R6QZQ.js";
3
+ } from "../chunk-SIJ4TDRV.js";
4
+ import "../chunk-NJAMXGIW.js";
5
5
  import "../chunk-TCZ6TC2L.js";
6
6
  import "../chunk-AIFJ7DVO.js";
7
7
  export {
@@ -9,7 +9,7 @@ import {
9
9
  getJmpLocations,
10
10
  getJmpSiteProfile,
11
11
  getJmpTrustBundle
12
- } from "./chunk-NI7R6QZQ.js";
12
+ } from "./chunk-NJAMXGIW.js";
13
13
  import {
14
14
  getJmpContentConfig,
15
15
  getJmpSiteDefinition
@@ -116,47 +116,47 @@ function listResolvedSocialUrls(social) {
116
116
  social.youtube
117
117
  ].filter((url) => Boolean(url));
118
118
  }
119
- async function loadBlogPosts() {
119
+ async function loadBlogPosts(locale) {
120
120
  const c = getJmpContentConfig();
121
121
  if (!c) {
122
122
  return [];
123
123
  }
124
124
  try {
125
- const posts = await getJmpBlogPosts(c);
125
+ const posts = await getJmpBlogPosts(c, locale ? { locale } : void 0);
126
126
  return sortBlogPosts(posts);
127
127
  } catch {
128
128
  return [];
129
129
  }
130
130
  }
131
- async function loadBlogPost(slug) {
131
+ async function loadBlogPost(slug, locale) {
132
132
  const c = getJmpContentConfig();
133
133
  if (!c) {
134
134
  return null;
135
135
  }
136
136
  try {
137
- return await getJmpBlogPost(c, slug);
137
+ return await getJmpBlogPost(c, slug, locale);
138
138
  } catch {
139
139
  return null;
140
140
  }
141
141
  }
142
- async function loadFaqs() {
142
+ async function loadFaqs(locale) {
143
143
  const c = getJmpContentConfig();
144
144
  if (!c) {
145
145
  return [];
146
146
  }
147
147
  try {
148
- const faqs = await getJmpFaqs(c);
148
+ const faqs = await getJmpFaqs(c, locale ? { locale } : void 0);
149
149
  return sortFaqs(faqs);
150
150
  } catch {
151
151
  return [];
152
152
  }
153
153
  }
154
- async function loadFaqBySlug(slug) {
154
+ async function loadFaqBySlug(slug, locale) {
155
155
  const normalized = slug.trim();
156
156
  if (!normalized) {
157
157
  return null;
158
158
  }
159
- const items = await loadFaqs();
159
+ const items = await loadFaqs(locale);
160
160
  return items.find((item) => item.slug === normalized || item.id === normalized) ?? null;
161
161
  }
162
162
  async function loadLandingPages(filters) {
@@ -256,4 +256,4 @@ export {
256
256
  loadTrustBundle,
257
257
  filterFeaturedTrustQuotes
258
258
  };
259
- //# sourceMappingURL=chunk-HKMDWTA2.js.map
259
+ //# sourceMappingURL=chunk-4NUFHUWN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/content/site-content.ts"],"sourcesContent":["import {\n getJmpBlogPost,\n getJmpBlogPosts,\n getJmpFaqs,\n getJmpLandingPageDetail,\n getJmpLandingPageSitemap,\n getJmpLandingPages,\n getJmpLocations,\n getJmpLocationDetail,\n getJmpSiteProfile,\n getJmpTrustBundle,\n type JmpBlogPost,\n type JmpFaqItem,\n type JmpLandingPage,\n type JmpLandingPageDetail,\n type JmpLandingPageKind,\n type JmpLandingPageSitemapEntry,\n type JmpWebsiteLocation,\n type JmpWebsiteLocationDetail,\n type JmpSiteProfile,\n type JmpTestimonial,\n type JmpTrustBundle,\n} from \"../index\"\nimport {\n getJmpContentConfig,\n getJmpSiteDefinition,\n type JmpSiteSocialLinks,\n} from \"../site-config\"\n\nexport type {\n JmpBlogPost,\n JmpFaqItem,\n JmpLandingPage,\n JmpLandingPageDetail,\n JmpLandingPageKind,\n JmpLandingPageSitemapEntry,\n JmpWebsiteLocation,\n JmpWebsiteLocationDetail,\n JmpSiteProfile,\n JmpTestimonial,\n JmpTrustBundle,\n}\n\nexport { getJmpContentConfig } from \"../site-config\"\n\nexport type { JmpSiteSocialLinks } from \"../site-config\"\n\nexport type ResolvedSiteContact = {\n displayName: string\n email: string\n founderName: string\n dashboardDemoUrl: string\n profilePhotoSrc: string\n linkedin: string | null\n social: JmpSiteSocialLinks\n phone: string | null\n address: string | null\n googleMapsUrl: string | null\n googleMapsEmbedUrl: string | null\n hours: string | null\n serviceArea: string | null\n privacyUrl: string\n termsUrl: string\n cta: { label: string | null; url: string | null }\n}\n\nconst DEFAULT_EXTRA_KEYS = {\n founderName: [\"founder-name\", \"founder_name\"],\n dashboardDemoUrl: [\"dashboard-demo-url\", \"sample-dashboard-url\"],\n profilePhotoSrc: [\"profile-photo-url\", \"profile_photo_url\"],\n hours: [\"hours\", \"business-hours\"],\n serviceArea: [\"service-area\", \"service_area\", \"service-area-text\"],\n googleMapsEmbedUrl: [\"google-maps-embed-url\", \"google_maps_embed_url\"],\n} as const\n\nfunction sortBlogPosts(posts: JmpBlogPost[]): JmpBlogPost[] {\n return [...posts].sort((a, b) => {\n const da = a.published_at ?? a.updated_at\n const db = b.published_at ?? b.updated_at\n return db.localeCompare(da)\n })\n}\n\nfunction sortFaqs(items: JmpFaqItem[]): JmpFaqItem[] {\n return [...items].sort((a, b) => a.sort_order - b.sort_order)\n}\n\nfunction readProfileExtra(\n profile: JmpSiteProfile | null,\n keys: readonly string[],\n): string | null {\n for (const key of keys) {\n const value = profile?.extras?.[key]?.trim()\n if (value) {\n return value\n }\n }\n return null\n}\n\n/** Resolve a path or URL against {@link defineJmpSite}'s `siteUrl`. */\nexport function absoluteSiteAssetUrl(src: string): string {\n const trimmed = src.trim()\n if (trimmed.startsWith(\"http://\") || trimmed.startsWith(\"https://\")) {\n return trimmed\n }\n const siteUrl = getJmpSiteDefinition()?.siteUrl?.replace(/\\/$/, \"\")\n if (!siteUrl) {\n return trimmed.startsWith(\"/\") ? trimmed : `/${trimmed}`\n }\n return `${siteUrl}${trimmed.startsWith(\"/\") ? trimmed : `/${trimmed}`}`\n}\n\nexport async function loadSiteProfile(): Promise<JmpSiteProfile | null> {\n const config = getJmpContentConfig()\n if (!config) {\n return null\n }\n try {\n return await getJmpSiteProfile(config)\n } catch {\n return null\n }\n}\n\nexport function resolveSiteContact(profile: JmpSiteProfile | null): ResolvedSiteContact {\n const site = getJmpSiteDefinition()\n const fallback = site?.contactFallback ?? {}\n const extraKeys = {\n ...DEFAULT_EXTRA_KEYS,\n ...site?.contactExtraKeys,\n }\n\n const email = profile?.email?.trim() || fallback.email || \"\"\n const social: JmpSiteSocialLinks = {\n linkedin:\n profile?.social?.linkedin?.trim() || fallback.linkedin || null,\n instagram:\n profile?.social?.instagram?.trim() || fallback.instagram || null,\n facebook:\n profile?.social?.facebook?.trim() || fallback.facebook || null,\n x: profile?.social?.x?.trim() || fallback.x || null,\n youtube:\n profile?.social?.youtube?.trim() || fallback.youtube || null,\n }\n const phone = profile?.phone?.trim() || fallback.phone || null\n\n return {\n displayName:\n profile?.display_name?.trim() ||\n profile?.name?.trim() ||\n fallback.displayName ||\n \"\",\n founderName:\n readProfileExtra(profile, extraKeys.founderName ?? []) ||\n fallback.founderName ||\n \"\",\n dashboardDemoUrl:\n readProfileExtra(profile, extraKeys.dashboardDemoUrl ?? []) ||\n fallback.dashboardDemoUrl ||\n \"\",\n profilePhotoSrc:\n readProfileExtra(profile, extraKeys.profilePhotoSrc ?? []) ||\n fallback.profilePhotoSrc ||\n \"\",\n email,\n linkedin: social.linkedin,\n social,\n phone,\n address: profile?.address?.trim() || fallback.address || null,\n googleMapsUrl:\n profile?.google_maps_url?.trim() || fallback.googleMapsUrl || null,\n googleMapsEmbedUrl:\n readProfileExtra(profile, extraKeys.googleMapsEmbedUrl ?? []) ||\n fallback.googleMapsEmbedUrl ||\n null,\n hours:\n readProfileExtra(profile, extraKeys.hours ?? []) || fallback.hours || null,\n serviceArea:\n readProfileExtra(profile, extraKeys.serviceArea ?? []) ||\n fallback.serviceArea ||\n null,\n privacyUrl: profile?.legal?.privacy_policy_url?.trim() || fallback.privacyUrl || \"/privacy\",\n termsUrl: profile?.legal?.terms_url?.trim() || fallback.termsUrl || \"/terms\",\n cta: {\n label: profile?.cta?.label?.trim() || null,\n url: profile?.cta?.url?.trim() || null,\n },\n }\n}\n\nexport async function getResolvedSiteContact(): Promise<ResolvedSiteContact> {\n const profile = await loadSiteProfile()\n return resolveSiteContact(profile)\n}\n\n/** Non-null social profile URLs in a stable order for JSON-LD `sameAs`. */\nexport function listResolvedSocialUrls(social: JmpSiteSocialLinks): string[] {\n return [\n social.linkedin,\n social.instagram,\n social.facebook,\n social.x,\n social.youtube,\n ].filter((url): url is string => Boolean(url))\n}\n\nexport async function loadBlogPosts(locale?: string): Promise<JmpBlogPost[]> {\n const c = getJmpContentConfig()\n if (!c) {\n return []\n }\n try {\n const posts = await getJmpBlogPosts(c, locale ? { locale } : undefined)\n return sortBlogPosts(posts)\n } catch {\n return []\n }\n}\n\nexport async function loadBlogPost(\n slug: string,\n locale?: string\n): Promise<JmpBlogPost | null> {\n const c = getJmpContentConfig()\n if (!c) {\n return null\n }\n try {\n return await getJmpBlogPost(c, slug, locale)\n } catch {\n return null\n }\n}\n\nexport async function loadFaqs(locale?: string): Promise<JmpFaqItem[]> {\n const c = getJmpContentConfig()\n if (!c) {\n return []\n }\n try {\n const faqs = await getJmpFaqs(c, locale ? { locale } : undefined)\n return sortFaqs(faqs)\n } catch {\n return []\n }\n}\n\nexport async function loadFaqBySlug(\n slug: string,\n locale?: string\n): Promise<JmpFaqItem | null> {\n const normalized = slug.trim()\n if (!normalized) {\n return null\n }\n const items = await loadFaqs(locale)\n return (\n items.find((item) => item.slug === normalized || item.id === normalized) ?? null\n )\n}\n\nexport async function loadLandingPages(filters?: {\n kind?: JmpLandingPageKind\n locale?: string\n}): Promise<JmpLandingPage[]> {\n const c = getJmpContentConfig()\n if (!c) {\n return []\n }\n try {\n return await getJmpLandingPages(c, filters)\n } catch {\n return []\n }\n}\n\nexport async function loadLandingPageDetail(\n kind: JmpLandingPageKind,\n slug: string,\n locale?: string\n): Promise<JmpLandingPageDetail | null> {\n const c = getJmpContentConfig()\n if (!c) {\n return null\n }\n try {\n return await getJmpLandingPageDetail(c, kind, slug, locale)\n } catch {\n return null\n }\n}\n\nexport async function loadLandingPageSitemap(): Promise<JmpLandingPageSitemapEntry[]> {\n const c = getJmpContentConfig()\n if (!c) {\n return []\n }\n try {\n return await getJmpLandingPageSitemap(c)\n } catch {\n return []\n }\n}\n\nexport async function loadLocations(): Promise<JmpWebsiteLocation[]> {\n const c = getJmpContentConfig()\n if (!c) {\n return []\n }\n try {\n return await getJmpLocations(c)\n } catch {\n return []\n }\n}\n\nexport async function loadLocationDetail(\n slug: string\n): Promise<JmpWebsiteLocationDetail | null> {\n const c = getJmpContentConfig()\n if (!c) {\n return null\n }\n try {\n return await getJmpLocationDetail(c, slug)\n } catch {\n return null\n }\n}\n\nexport async function loadTrustBundle(): Promise<JmpTrustBundle | null> {\n const c = getJmpContentConfig()\n if (!c) {\n return null\n }\n try {\n return await getJmpTrustBundle(c)\n } catch {\n return null\n }\n}\n\nexport function filterFeaturedTrustQuotes(\n testimonials: JmpTestimonial[],\n placement: \"testimonials\" | \"reviews\",\n limit = 2,\n): JmpTestimonial[] {\n return testimonials\n .filter((item) => {\n if (!item.featured) {\n return false\n }\n const bucket = item.placement?.trim().toLowerCase()\n if (placement === \"reviews\") {\n return bucket === \"reviews\"\n }\n return !bucket || bucket === \"testimonials\"\n })\n .sort((a, b) => a.sort_order - b.sort_order)\n .slice(0, limit)\n}\n"],"mappings":"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","names":[]}
@@ -151,8 +151,11 @@ function extractMailtoTarget(href) {
151
151
  return target || null;
152
152
  }
153
153
 
154
+ // package.json
155
+ var version = "0.1.34";
156
+
154
157
  // src/constants.ts
155
- var JMP_ANALYTICS_SDK_VERSION = "0.1.26";
158
+ var JMP_ANALYTICS_SDK_VERSION = version;
156
159
  var JMP_TRACKING_CONSENT_STORAGE_KEY = "jmp_analytics_consent_v1";
157
160
  var DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS = 2500;
158
161
 
@@ -987,20 +990,30 @@ function sortWrapperSlots(slots) {
987
990
  function afterContentWrapperUse(config) {
988
991
  scheduleContentWrapperSync(config);
989
992
  }
990
- async function getJmpBlogPosts(config) {
993
+ async function getJmpBlogPosts(config, filters) {
994
+ const params = new URLSearchParams();
995
+ if (filters?.locale) {
996
+ params.set("locale", filters.locale);
997
+ }
998
+ const query = params.toString();
991
999
  const data = await getContent(
992
1000
  config,
993
- "/api/content/blog"
1001
+ `/api/content/blog${query ? `?${query}` : ""}`
994
1002
  );
995
1003
  markContentWrapper("blog");
996
1004
  afterContentWrapperUse(config);
997
1005
  return data.posts;
998
1006
  }
999
- async function getJmpBlogPost(config, slug) {
1007
+ async function getJmpBlogPost(config, slug, locale) {
1000
1008
  try {
1009
+ const params = new URLSearchParams();
1010
+ if (locale) {
1011
+ params.set("locale", locale);
1012
+ }
1013
+ const query = params.toString();
1001
1014
  const data = await getContent(
1002
1015
  config,
1003
- `/api/content/blog/${encodeURIComponent(slug)}`
1016
+ `/api/content/blog/${encodeURIComponent(slug)}${query ? `?${query}` : ""}`
1004
1017
  );
1005
1018
  markContentWrapper("blog");
1006
1019
  afterContentWrapperUse(config);
@@ -1012,15 +1025,62 @@ async function getJmpBlogPost(config, slug) {
1012
1025
  throw error;
1013
1026
  }
1014
1027
  }
1015
- async function getJmpFaqs(config) {
1028
+ async function getJmpBlogPostDetail(config, slug, locale) {
1029
+ try {
1030
+ const params = new URLSearchParams();
1031
+ if (locale) {
1032
+ params.set("locale", locale);
1033
+ }
1034
+ const query = params.toString();
1035
+ const data = await getContent(
1036
+ config,
1037
+ `/api/content/blog/${encodeURIComponent(slug)}${query ? `?${query}` : ""}`
1038
+ );
1039
+ markContentWrapper("blog");
1040
+ afterContentWrapperUse(config);
1041
+ return data;
1042
+ } catch (error) {
1043
+ if (error instanceof Error && error.message.includes("HTTP 404")) {
1044
+ return null;
1045
+ }
1046
+ throw error;
1047
+ }
1048
+ }
1049
+ async function getJmpFaqs(config, filters) {
1050
+ const params = new URLSearchParams();
1051
+ if (filters?.locale) {
1052
+ params.set("locale", filters.locale);
1053
+ }
1054
+ const query = params.toString();
1016
1055
  const data = await getContent(
1017
1056
  config,
1018
- "/api/content/faqs"
1057
+ `/api/content/faqs${query ? `?${query}` : ""}`
1019
1058
  );
1020
1059
  markContentWrapper("faq");
1021
1060
  afterContentWrapperUse(config);
1022
1061
  return data.faqs;
1023
1062
  }
1063
+ async function getJmpFaqDetail(config, slug, locale) {
1064
+ try {
1065
+ const params = new URLSearchParams();
1066
+ if (locale) {
1067
+ params.set("locale", locale);
1068
+ }
1069
+ const query = params.toString();
1070
+ const data = await getContent(
1071
+ config,
1072
+ `/api/content/faqs/${encodeURIComponent(slug)}${query ? `?${query}` : ""}`
1073
+ );
1074
+ markContentWrapper("faq");
1075
+ afterContentWrapperUse(config);
1076
+ return data;
1077
+ } catch (error) {
1078
+ if (error instanceof Error && error.message.includes("HTTP 404")) {
1079
+ return null;
1080
+ }
1081
+ throw error;
1082
+ }
1083
+ }
1024
1084
  async function getJmpLandingPages(config, filters) {
1025
1085
  const params = new URLSearchParams();
1026
1086
  if (filters?.kind) {
@@ -1175,9 +1235,9 @@ setContentWrapperSyncHandler(registerJmpContentWrappers);
1175
1235
  // src/content/client.ts
1176
1236
  function createJmpContentClient(config) {
1177
1237
  return {
1178
- getBlogPosts: () => getJmpBlogPosts(config),
1179
- getBlogPost: (slug) => getJmpBlogPost(config, slug),
1180
- getFaqs: () => getJmpFaqs(config),
1238
+ getBlogPosts: (filters) => getJmpBlogPosts(config, filters),
1239
+ getBlogPost: (slug, locale) => getJmpBlogPost(config, slug, locale),
1240
+ getFaqs: (filters) => getJmpFaqs(config, filters),
1181
1241
  getLandingPages: (filters) => getJmpLandingPages(config, filters),
1182
1242
  getLandingPageDetail: (kind, slug, locale) => getJmpLandingPageDetail(config, kind, slug, locale),
1183
1243
  getLandingPageSitemap: () => getJmpLandingPageSitemap(config),
@@ -1253,7 +1313,9 @@ export {
1253
1313
  resetContentWrapperDiscoveryForTests,
1254
1314
  getJmpBlogPosts,
1255
1315
  getJmpBlogPost,
1316
+ getJmpBlogPostDetail,
1256
1317
  getJmpFaqs,
1318
+ getJmpFaqDetail,
1257
1319
  getJmpLandingPages,
1258
1320
  getJmpLandingPageDetail,
1259
1321
  getJmpLandingPageSitemap,
@@ -1271,4 +1333,4 @@ export {
1271
1333
  getOrCreateJmpAnalyticsFromBrowserEnv,
1272
1334
  createJmpContentClientFromServerEnv
1273
1335
  };
1274
- //# sourceMappingURL=chunk-NI7R6QZQ.js.map
1336
+ //# sourceMappingURL=chunk-NJAMXGIW.js.map