@jmp-technologies/analytics 0.1.25 → 0.1.26
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.
- package/dist/astro/client.cjs +103 -0
- package/dist/astro/client.cjs.map +1 -1
- package/dist/astro/client.d.cts +2 -2
- package/dist/astro/client.d.ts +2 -2
- package/dist/astro/client.js +5 -3
- package/dist/astro/client.js.map +1 -1
- package/dist/astro/content.cjs +185 -1
- package/dist/astro/content.cjs.map +1 -1
- package/dist/astro/content.d.cts +2 -2
- package/dist/astro/content.d.ts +2 -2
- package/dist/astro/content.js +4 -3
- package/dist/astro/integration.cjs +55 -9
- package/dist/astro/integration.cjs.map +1 -1
- package/dist/astro/integration.js +4 -3
- package/dist/astro/integration.js.map +1 -1
- package/dist/astro/middleware.cjs.map +1 -1
- package/dist/astro/middleware.d.cts +2 -2
- package/dist/astro/middleware.d.ts +2 -2
- package/dist/astro/middleware.js +2 -2
- package/dist/astro/register.cjs +55 -9
- package/dist/astro/register.cjs.map +1 -1
- package/dist/astro/register.js +4 -3
- package/dist/{chunk-MOX7RJUU.js → chunk-A4RRTY5D.js} +1 -1
- package/dist/{chunk-MOX7RJUU.js.map → chunk-A4RRTY5D.js.map} +1 -1
- package/dist/{chunk-7MLPSL6T.js → chunk-HODG4NJQ.js} +2 -2
- package/dist/{chunk-L67PVZYL.js → chunk-PKGUBYZR.js} +3 -24
- package/dist/chunk-PKGUBYZR.js.map +1 -0
- package/dist/{chunk-VRDPVKAN.js → chunk-RCOHQAGL.js} +159 -156
- package/dist/chunk-RCOHQAGL.js.map +1 -0
- package/dist/chunk-TCZ6TC2L.js +149 -0
- package/dist/chunk-TCZ6TC2L.js.map +1 -0
- package/dist/{chunk-AJ33NQVZ.js → chunk-TFHOTBOL.js} +3 -3
- package/dist/{chunk-5GORAM7J.js → chunk-WFTTIRDJ.js} +2 -2
- package/dist/{chunk-OLWCYWVB.js → chunk-XQZOGWF2.js} +3 -3
- package/dist/chunk-YCVHRMM6.js +28 -0
- package/dist/chunk-YCVHRMM6.js.map +1 -0
- package/dist/{create-jmp-analytics-COxo3-yg.d.ts → create-jmp-analytics-JKAsJAWb.d.ts} +1 -1
- package/dist/{create-jmp-analytics-ClLOMb9p.d.cts → create-jmp-analytics-Mau-oxji.d.cts} +1 -1
- package/dist/index.cjs +156 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -6
- package/dist/index.d.ts +12 -6
- package/dist/index.js +13 -3
- package/dist/next/content.cjs +185 -1
- package/dist/next/content.cjs.map +1 -1
- package/dist/next/content.d.cts +2 -2
- package/dist/next/content.d.ts +2 -2
- package/dist/next/content.js +4 -3
- package/dist/next/content.js.map +1 -1
- package/dist/next/instrumentation.cjs +55 -9
- package/dist/next/instrumentation.cjs.map +1 -1
- package/dist/next/instrumentation.js +3 -2
- package/dist/next/instrumentation.js.map +1 -1
- package/dist/next/landing-capture-script-server.cjs +61 -0
- package/dist/next/landing-capture-script-server.cjs.map +1 -0
- package/dist/next/landing-capture-script-server.d.cts +14 -0
- package/dist/next/landing-capture-script-server.d.ts +14 -0
- package/dist/next/landing-capture-script-server.js +8 -0
- package/dist/next/landing-capture-script-server.js.map +1 -0
- package/dist/next/middleware.cjs.map +1 -1
- package/dist/next/middleware.d.cts +2 -2
- package/dist/next/middleware.d.ts +2 -2
- package/dist/next/middleware.js +2 -2
- package/dist/next/site-root-server.cjs +159 -23
- package/dist/next/site-root-server.cjs.map +1 -1
- package/dist/next/site-root-server.js +9 -4
- package/dist/next/site-root-server.js.map +1 -1
- package/dist/next.cjs +152 -70
- package/dist/next.cjs.map +1 -1
- package/dist/next.d.cts +7 -15
- package/dist/next.d.ts +7 -15
- package/dist/next.js +7 -7
- package/dist/react.cjs +103 -0
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +4 -3
- package/dist/{site-config-BSMVyUjk.d.ts → site-config-CXUdGLqw.d.cts} +6 -3
- package/dist/{site-config-8TTHIW6w.d.cts → site-config-w_Db9YSk.d.ts} +6 -3
- package/dist/{track-page-view-BweavmXz.d.cts → track-page-view-CkuSYuco.d.cts} +1 -1
- package/dist/{track-page-view-DtxbdM5q.d.ts → track-page-view-QRSk8vap.d.ts} +1 -1
- package/dist/{types-Bk0ST1aH.d.ts → types-C1eq8E5u.d.cts} +12 -4
- package/dist/{types-Bk0ST1aH.d.cts → types-C1eq8E5u.d.ts} +12 -4
- package/docs/astro-integration.md +1 -1
- package/package.json +7 -2
- package/dist/chunk-L67PVZYL.js.map +0 -1
- package/dist/chunk-VRDPVKAN.js.map +0 -1
- /package/dist/{chunk-7MLPSL6T.js.map → chunk-HODG4NJQ.js.map} +0 -0
- /package/dist/{chunk-AJ33NQVZ.js.map → chunk-TFHOTBOL.js.map} +0 -0
- /package/dist/{chunk-5GORAM7J.js.map → chunk-WFTTIRDJ.js.map} +0 -0
- /package/dist/{chunk-OLWCYWVB.js.map → chunk-XQZOGWF2.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/http.ts","../src/server/page-view.ts","../src/env-public.ts","../src/site-config.ts"],"sourcesContent":["import type { JmpServerRequestLike } from \"./types\"\n\n/** CDN / Next.js often return HTML error pages; API errors are JSON */\nexport function responseLooksLikeHtml(body: string): boolean {\n const s = body.trimStart()\n return (\n s.startsWith(\"<!\") ||\n s.toLowerCase().startsWith(\"<html\") ||\n (s.startsWith(\"<\") && /<html[\\s>]/i.test(s.slice(0, 500)))\n )\n}\n\nexport function normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/$/, \"\")\n}\n\nexport function getHeader(\n headers: JmpServerRequestLike[\"headers\"] | undefined,\n key: string\n): string | null {\n if (!headers) {\n return null\n }\n if (typeof (headers as Pick<Headers, \"get\">).get === \"function\") {\n return (headers as Pick<Headers, \"get\">).get(key)\n }\n const record = headers as Record<string, string | string[] | undefined | null>\n const direct = record[key] ?? record[key.toLowerCase()]\n if (Array.isArray(direct)) {\n return direct[0] ?? null\n }\n return direct ?? null\n}\n\nexport function pathFromRequestUrl(url: string | undefined): string {\n if (!url) {\n return \"/\"\n }\n try {\n const u = new URL(url)\n return `${u.pathname}${u.search}`\n } catch {\n return url.startsWith(\"/\") ? url : \"/\"\n }\n}\n\nexport function resolveFetch(\n fetchImpl: typeof fetch | undefined,\n context: string\n): typeof fetch {\n const resolved =\n fetchImpl ??\n (typeof globalThis !== \"undefined\" && globalThis.fetch\n ? globalThis.fetch.bind(globalThis)\n : undefined)\n if (!resolved) {\n throw new Error(`${context}: fetch is not available; pass fetchImpl in config`)\n }\n return resolved\n}\n\ntype ApiErrorJson = {\n error?: string\n code?: string\n hint?: string\n expectedDomain?: string\n}\n\nexport function formatApiErrorMessage(\n status: number,\n text: string,\n prefix: string\n): string {\n if (responseLooksLikeHtml(text)) {\n return `${prefix} HTTP ${status} — response was HTML (not the JSON API). Check baseUrl points at the JMP Analytics dashboard.`\n }\n let parsed: ApiErrorJson = {}\n try {\n parsed = JSON.parse(text) as ApiErrorJson\n } catch {\n parsed = { error: text }\n }\n const detail = parsed.error ?? text\n return [\n `${prefix} HTTP ${status}`,\n parsed.code ? `[${parsed.code}]` : null,\n detail,\n parsed.hint ? `Hint: ${parsed.hint}` : null,\n parsed.expectedDomain\n ? `Domain in JMP admin: ${parsed.expectedDomain}`\n : null,\n ]\n .filter(Boolean)\n .join(\" — \")\n}\n","import {\n getHeader,\n normalizeBaseUrl,\n pathFromRequestUrl,\n resolveFetch,\n} from \"../http\"\nimport type { JmpContentConfig, TrackServerPageViewOptions } from \"../types\"\n\nexport async function trackJmpServerPageView(\n config: JmpContentConfig,\n options: TrackServerPageViewOptions = {}\n): Promise<void> {\n const baseUrl = normalizeBaseUrl(config.baseUrl)\n const resolvedFetch =\n config.fetchImpl ??\n (typeof globalThis !== \"undefined\" && globalThis.fetch\n ? globalThis.fetch.bind(globalThis)\n : undefined)\n\n if (!resolvedFetch) {\n throw new Error(\"JMP server analytics: fetch is not available; pass fetchImpl in config\")\n }\n\n const visitorUserAgent =\n options.userAgent ?? getHeader(options.request?.headers, \"user-agent\")\n const visitorReferer =\n options.referer ?? getHeader(options.request?.headers, \"referer\")\n const path = options.path ?? pathFromRequestUrl(options.request?.url)\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-JMP-Tracking-Key\": config.trackingKey,\n }\n if (visitorUserAgent) {\n headers[\"X-JMP-Visitor-User-Agent\"] = visitorUserAgent\n }\n if (visitorReferer) {\n headers[\"X-JMP-Visitor-Referer\"] = visitorReferer\n }\n\n const payload: Record<string, unknown> = {\n type: \"page_view\",\n path: path || \"/\",\n metadata:\n options.metadata && typeof options.metadata === \"object\"\n ? options.metadata\n : {},\n }\n if (options.sessionId) {\n payload.sessionId = options.sessionId\n }\n\n const url = `${baseUrl}/api/events`\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"POST\", url, payload)\n }\n\n const res = await resolvedFetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n })\n\n if (!res.ok) {\n const text = await res.text()\n throw new Error(`JMP server analytics HTTP ${res.status} — ${text}`)\n }\n}\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 { JmpContentConfig, JmpContentWrapper } from \"./types\"\nimport { readJmpPublicEnvFromProcess } from \"./env-public\"\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 hours?: string | null\n serviceArea?: string | null\n dashboardDemoUrl?: string\n profilePhotoSrc?: string\n privacyUrl?: string\n termsUrl?: string\n}\n\nexport type JmpSiteDefinition = {\n /** Content tabs this site uses — reported via instrumentation capabilities. */\n contentWrappers: JmpContentWrapper[]\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 }\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 }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/http.ts","../src/server/page-view.ts","../src/env-public.ts","../src/site-config.ts"],"sourcesContent":["import type { JmpServerRequestLike } from \"./types\"\n\n/** CDN / Next.js often return HTML error pages; API errors are JSON */\nexport function responseLooksLikeHtml(body: string): boolean {\n const s = body.trimStart()\n return (\n s.startsWith(\"<!\") ||\n s.toLowerCase().startsWith(\"<html\") ||\n (s.startsWith(\"<\") && /<html[\\s>]/i.test(s.slice(0, 500)))\n )\n}\n\nexport function normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/$/, \"\")\n}\n\nexport function getHeader(\n headers: JmpServerRequestLike[\"headers\"] | undefined,\n key: string\n): string | null {\n if (!headers) {\n return null\n }\n if (typeof (headers as Pick<Headers, \"get\">).get === \"function\") {\n return (headers as Pick<Headers, \"get\">).get(key)\n }\n const record = headers as Record<string, string | string[] | undefined | null>\n const direct = record[key] ?? record[key.toLowerCase()]\n if (Array.isArray(direct)) {\n return direct[0] ?? null\n }\n return direct ?? null\n}\n\nexport function pathFromRequestUrl(url: string | undefined): string {\n if (!url) {\n return \"/\"\n }\n try {\n const u = new URL(url)\n return `${u.pathname}${u.search}`\n } catch {\n return url.startsWith(\"/\") ? url : \"/\"\n }\n}\n\nexport function resolveFetch(\n fetchImpl: typeof fetch | undefined,\n context: string\n): typeof fetch {\n const resolved =\n fetchImpl ??\n (typeof globalThis !== \"undefined\" && globalThis.fetch\n ? globalThis.fetch.bind(globalThis)\n : undefined)\n if (!resolved) {\n throw new Error(`${context}: fetch is not available; pass fetchImpl in config`)\n }\n return resolved\n}\n\ntype ApiErrorJson = {\n error?: string\n code?: string\n hint?: string\n expectedDomain?: string\n}\n\nexport function formatApiErrorMessage(\n status: number,\n text: string,\n prefix: string\n): string {\n if (responseLooksLikeHtml(text)) {\n return `${prefix} HTTP ${status} — response was HTML (not the JSON API). Check baseUrl points at the JMP Analytics dashboard.`\n }\n let parsed: ApiErrorJson = {}\n try {\n parsed = JSON.parse(text) as ApiErrorJson\n } catch {\n parsed = { error: text }\n }\n const detail = parsed.error ?? text\n return [\n `${prefix} HTTP ${status}`,\n parsed.code ? `[${parsed.code}]` : null,\n detail,\n parsed.hint ? `Hint: ${parsed.hint}` : null,\n parsed.expectedDomain\n ? `Domain in JMP admin: ${parsed.expectedDomain}`\n : null,\n ]\n .filter(Boolean)\n .join(\" — \")\n}\n","import {\n getHeader,\n normalizeBaseUrl,\n pathFromRequestUrl,\n resolveFetch,\n} from \"../http\"\nimport type { JmpContentConfig, TrackServerPageViewOptions } from \"../types\"\n\nexport async function trackJmpServerPageView(\n config: JmpContentConfig,\n options: TrackServerPageViewOptions = {}\n): Promise<void> {\n const baseUrl = normalizeBaseUrl(config.baseUrl)\n const resolvedFetch =\n config.fetchImpl ??\n (typeof globalThis !== \"undefined\" && globalThis.fetch\n ? globalThis.fetch.bind(globalThis)\n : undefined)\n\n if (!resolvedFetch) {\n throw new Error(\"JMP server analytics: fetch is not available; pass fetchImpl in config\")\n }\n\n const visitorUserAgent =\n options.userAgent ?? getHeader(options.request?.headers, \"user-agent\")\n const visitorReferer =\n options.referer ?? getHeader(options.request?.headers, \"referer\")\n const path = options.path ?? pathFromRequestUrl(options.request?.url)\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-JMP-Tracking-Key\": config.trackingKey,\n }\n if (visitorUserAgent) {\n headers[\"X-JMP-Visitor-User-Agent\"] = visitorUserAgent\n }\n if (visitorReferer) {\n headers[\"X-JMP-Visitor-Referer\"] = visitorReferer\n }\n\n const payload: Record<string, unknown> = {\n type: \"page_view\",\n path: path || \"/\",\n metadata:\n options.metadata && typeof options.metadata === \"object\"\n ? options.metadata\n : {},\n }\n if (options.sessionId) {\n payload.sessionId = options.sessionId\n }\n\n const url = `${baseUrl}/api/events`\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"POST\", url, payload)\n }\n\n const res = await resolvedFetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n })\n\n if (!res.ok) {\n const text = await res.text()\n throw new Error(`JMP server analytics HTTP ${res.status} — ${text}`)\n }\n}\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 { JmpContentConfig, JmpContentWrapper } from \"./types\"\nimport { readJmpPublicEnvFromProcess } from \"./env-public\"\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 hours?: string | null\n serviceArea?: 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 * 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 }\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 }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
getHeader,
|
|
3
3
|
getJmpContentConfig,
|
|
4
4
|
trackJmpServerPageView
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-A4RRTY5D.js";
|
|
6
6
|
|
|
7
7
|
// src/server/should-track-server-page-view.ts
|
|
8
8
|
function shouldSkipServerPageViewRequest(request) {
|
|
@@ -56,4 +56,4 @@ async function jmpTrackServerPageView(request) {
|
|
|
56
56
|
export {
|
|
57
57
|
jmpTrackServerPageView
|
|
58
58
|
};
|
|
59
|
-
//# sourceMappingURL=chunk-
|
|
59
|
+
//# sourceMappingURL=chunk-HODG4NJQ.js.map
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
buildLandingCaptureInlineScript,
|
|
3
2
|
getOrCreateJmpAnalyticsFromBrowserEnv,
|
|
4
3
|
isJmpTrackingConsentGranted,
|
|
5
4
|
subscribeJmpTrackingConsent
|
|
6
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-RCOHQAGL.js";
|
|
7
6
|
import {
|
|
8
7
|
readJmpPublicEnvFromProcess
|
|
9
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-A4RRTY5D.js";
|
|
10
9
|
|
|
11
10
|
// src/next.tsx
|
|
12
|
-
import Script from "next/script";
|
|
13
11
|
import {
|
|
14
12
|
Suspense,
|
|
15
13
|
useLayoutEffect,
|
|
@@ -44,23 +42,6 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
44
42
|
function useJmpAnalytics() {
|
|
45
43
|
return useMemo(() => getOrCreateJmpAnalyticsForNextApp(), []);
|
|
46
44
|
}
|
|
47
|
-
function JmpLandingCaptureScript({
|
|
48
|
-
trackingKey = readJmpPublicEnvFromProcess().trackingKey
|
|
49
|
-
} = {}) {
|
|
50
|
-
if (!trackingKey?.trim()) {
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
return /* @__PURE__ */ jsx(
|
|
54
|
-
Script,
|
|
55
|
-
{
|
|
56
|
-
id: "jmp-landing-capture",
|
|
57
|
-
strategy: "beforeInteractive",
|
|
58
|
-
dangerouslySetInnerHTML: {
|
|
59
|
-
__html: buildLandingCaptureInlineScript()
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
45
|
function JmpNextPageViewTrackerInner({
|
|
65
46
|
configOverrides
|
|
66
47
|
}) {
|
|
@@ -120,7 +101,6 @@ function JmpNextAnalytics({
|
|
|
120
101
|
}
|
|
121
102
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
122
103
|
/* @__PURE__ */ jsx(JmpNextAnalyticsBootstrap, { configOverrides }),
|
|
123
|
-
/* @__PURE__ */ jsx(JmpLandingCaptureScript, { trackingKey }),
|
|
124
104
|
/* @__PURE__ */ jsx(JmpNextPageViewTracker, { configOverrides })
|
|
125
105
|
] });
|
|
126
106
|
}
|
|
@@ -146,9 +126,8 @@ function JmpSiteRoot({
|
|
|
146
126
|
export {
|
|
147
127
|
getOrCreateJmpAnalyticsForNextApp,
|
|
148
128
|
useJmpAnalytics,
|
|
149
|
-
JmpLandingCaptureScript,
|
|
150
129
|
JmpNextPageViewTracker,
|
|
151
130
|
JmpNextAnalytics,
|
|
152
131
|
JmpSiteRoot
|
|
153
132
|
};
|
|
154
|
-
//# sourceMappingURL=chunk-
|
|
133
|
+
//# sourceMappingURL=chunk-PKGUBYZR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/next.tsx","../src/env/browser-next.ts"],"sourcesContent":["\"use client\"\n\nimport {\n Suspense,\n useLayoutEffect,\n useMemo,\n useSyncExternalStore,\n type ReactNode,\n} from \"react\"\nimport { usePathname, useSearchParams } from \"next/navigation\"\nimport { readJmpPublicEnvFromProcess } from \"./env-public\"\nimport type { JmpAnalyticsConfig } from \"./types\"\nimport { getOrCreateJmpAnalyticsForNextApp } from \"./env/browser-next\"\nimport {\n isJmpTrackingConsentGranted,\n subscribeJmpTrackingConsent,\n} from \"./index\"\n\nexport {\n setJmpTrackingConsent,\n isJmpTrackingConsentGranted,\n subscribeJmpTrackingConsent,\n} from \"./index\"\nimport { JmpTrackedMailtoLink, JmpTrackedTelLink } from \"./react\"\nimport type { JmpAnalytics } from \"./index\"\n\nexport { JmpTrackedMailtoLink, JmpTrackedTelLink }\nexport { JmpGoogleTag } from \"./next/google-tag\"\nexport { getOrCreateJmpAnalyticsForNextApp } from \"./env/browser-next\"\nexport { readJmpPublicEnvFromProcess } from \"./env-public\"\n\nexport function useJmpAnalytics(): JmpAnalytics | null {\n return useMemo(() => getOrCreateJmpAnalyticsForNextApp(), [])\n}\nexport { buildLandingCaptureInlineScript } from \"./landing-capture\"\n\nfunction JmpNextPageViewTrackerInner({\n configOverrides,\n}: {\n configOverrides?: Partial<JmpAnalyticsConfig>\n}) {\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const query = searchParams?.toString() ?? \"\"\n const path = query ? `${pathname}?${query}` : pathname\n\n useLayoutEffect(() => {\n const client = getOrCreateJmpAnalyticsForNextApp(configOverrides)\n void client?.trackPageView({ path })\n }, [path, configOverrides])\n\n return null\n}\n\n/**\n * App Router: `page_view` on first paint and client navigations.\n */\nexport function JmpNextPageViewTracker({\n configOverrides,\n}: {\n configOverrides?: Partial<JmpAnalyticsConfig>\n}) {\n return (\n <Suspense fallback={null}>\n <JmpNextPageViewTrackerInner configOverrides={configOverrides} />\n </Suspense>\n )\n}\n\nfunction useJmpTrackingActive(deferUntilConsent: boolean): boolean {\n const consentGranted = useSyncExternalStore(\n subscribeJmpTrackingConsent,\n () => isJmpTrackingConsentGranted(),\n () => false\n )\n return !deferUntilConsent || consentGranted\n}\n\ntype JmpNextAnalyticsProps = {\n /**\n * When true (or `NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT=true`), scripts stay\n * off until {@link setJmpTrackingConsent}(true) from your cookie banner.\n */\n deferUntilConsent?: boolean\n /** From {@link JmpSiteRootServer} — ensures the client bundle can reach the portal. */\n analyticsUrl?: string\n trackingKey?: string\n}\n\nfunction jmpConfigOverridesFromProps(\n props: Pick<JmpNextAnalyticsProps, \"analyticsUrl\" | \"trackingKey\">,\n): Partial<JmpAnalyticsConfig> | undefined {\n const { analyticsUrl, trackingKey } = props\n if (!analyticsUrl?.trim() && !trackingKey?.trim()) {\n return undefined\n }\n return {\n ...(analyticsUrl?.trim() ? { baseUrl: analyticsUrl.trim() } : {}),\n ...(trackingKey?.trim() ? { trackingKey: trackingKey.trim() } : {}),\n }\n}\n\n/** Initializes the browser client (contact auto-track defaults) before page views. */\nfunction JmpNextAnalyticsBootstrap({\n configOverrides,\n}: {\n configOverrides?: Partial<JmpAnalyticsConfig>\n}) {\n useLayoutEffect(() => {\n getOrCreateJmpAnalyticsForNextApp(configOverrides)\n }, [configOverrides])\n return null\n}\n\n/** Client route tracking + analytics bootstrap — landing capture is SSR-only via {@link JmpLandingCaptureScriptServer}. */\nexport function JmpNextAnalytics({\n deferUntilConsent = readJmpPublicEnvFromProcess().requireConsent === true,\n analyticsUrl,\n trackingKey,\n}: JmpNextAnalyticsProps = {}) {\n const active = useJmpTrackingActive(deferUntilConsent)\n const configOverrides = useMemo(\n () => jmpConfigOverridesFromProps({ analyticsUrl, trackingKey }),\n [analyticsUrl, trackingKey],\n )\n\n if (!active) {\n return null\n }\n\n return (\n <>\n <JmpNextAnalyticsBootstrap configOverrides={configOverrides} />\n <JmpNextPageViewTracker configOverrides={configOverrides} />\n </>\n )\n}\n\ntype JmpSiteRootProps = {\n children: ReactNode\n deferUntilConsent?: boolean\n /** Prefer {@link JmpSiteRootServer} in layout — pass-through from server env. */\n analyticsUrl?: string\n trackingKey?: string\n}\n\n/**\n * Root layout wrapper: App Router page views + children.\n * Put {@link JmpLandingCaptureScriptServer} in layout `<head>` (or use {@link JmpSiteRootServer}).\n */\nexport function JmpSiteRoot({\n children,\n deferUntilConsent,\n analyticsUrl,\n trackingKey,\n}: JmpSiteRootProps) {\n return (\n <>\n <JmpNextAnalytics\n deferUntilConsent={deferUntilConsent}\n analyticsUrl={analyticsUrl}\n trackingKey={trackingKey}\n />\n {children}\n </>\n )\n}\n\n","import type { JmpAnalytics } from \"../tracker/create-jmp-analytics\"\nimport type { JmpAnalyticsConfig } from \"../types\"\nimport { getOrCreateJmpAnalyticsFromBrowserEnv } from \"./browser\"\n\n/**\n * Next.js App Router defaults: auto-track `tel:` / `mailto:` (and other contact\n * links) unless `NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS=false`.\n */\nfunction readNextAutoContactLinksEnv(): string | undefined {\n if (typeof process === \"undefined\" || !process.env) {\n return undefined\n }\n const { env } = process\n const raw = env.NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS ?? env.PUBLIC_JMP_AUTO_CONTACT_LINKS\n return typeof raw === \"string\" && raw.length > 0 ? raw : undefined\n}\n\nexport function getJmpNextAnalyticsConfigOverrides(): Partial<JmpAnalyticsConfig> {\n const autoEnv = readNextAutoContactLinksEnv()\n const autoContactLinks =\n autoEnv === \"false\" ? false : autoEnv === \"true\" ? true : true\n return { autoTrackContactLinks: autoContactLinks }\n}\n\n/**\n * Browser singleton for Next.js sites — ensures contact auto-track defaults apply\n * on the first `getOrCreate` call (page views, tracked links, delegated clicks).\n */\nexport function getOrCreateJmpAnalyticsForNextApp(\n overrides?: Partial<JmpAnalyticsConfig>,\n): JmpAnalytics | null {\n return getOrCreateJmpAnalyticsFromBrowserEnv({\n ...getJmpNextAnalyticsConfigOverrides(),\n ...overrides,\n })\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
JMP_UTM_PARAM_KEYS,
|
|
3
|
+
captureLandingAttribution,
|
|
4
|
+
getOrCreateSessionIdFromStorage
|
|
5
|
+
} from "./chunk-TCZ6TC2L.js";
|
|
1
6
|
import {
|
|
2
7
|
formatApiErrorMessage,
|
|
3
8
|
getJmpSiteDefinition,
|
|
@@ -5,149 +10,7 @@ import {
|
|
|
5
10
|
readJmpPublicEnvFromProcess,
|
|
6
11
|
resolveFetch,
|
|
7
12
|
responseLooksLikeHtml
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
|
|
10
|
-
// src/landing-capture.ts
|
|
11
|
-
var JMP_SESSION_ID_KEY = "jmp_analytics_sid";
|
|
12
|
-
var JMP_LANDING_REFERER_KEY = "jmp_analytics_landing_referer";
|
|
13
|
-
var JMP_LANDING_REFERER_COOKIE = "jmp_lr";
|
|
14
|
-
var JMP_UTM_SESSION_KEY = "jmp_analytics_utm";
|
|
15
|
-
var LANDING_REFERER_COOKIE_MAX_AGE_SEC = 30 * 60;
|
|
16
|
-
var JMP_UTM_PARAM_KEYS = [
|
|
17
|
-
"utm_source",
|
|
18
|
-
"utm_medium",
|
|
19
|
-
"utm_campaign",
|
|
20
|
-
"utm_content",
|
|
21
|
-
"utm_term"
|
|
22
|
-
];
|
|
23
|
-
function refererHostFromUrl(referer) {
|
|
24
|
-
const trimmed = referer.trim();
|
|
25
|
-
try {
|
|
26
|
-
return new URL(trimmed).hostname.replace(/^www\./i, "").toLowerCase();
|
|
27
|
-
} catch {
|
|
28
|
-
try {
|
|
29
|
-
return new URL(`https://${trimmed}`).hostname.replace(/^www\./i, "").toLowerCase();
|
|
30
|
-
} catch {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function isExternalReferrerUrl(referer, siteHostname) {
|
|
36
|
-
const refHost = refererHostFromUrl(referer);
|
|
37
|
-
if (!refHost) {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
const site = siteHostname.replace(/^www\./i, "").toLowerCase();
|
|
41
|
-
return refHost !== site && !refHost.endsWith(`.${site}`);
|
|
42
|
-
}
|
|
43
|
-
function registrableSiteDomain(siteHostname) {
|
|
44
|
-
const site = siteHostname.replace(/^www\./i, "").toLowerCase();
|
|
45
|
-
if (!site || site === "localhost" || site.endsWith(".local")) {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
if (!site.includes(".")) {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
return site;
|
|
52
|
-
}
|
|
53
|
-
function readLandingRefererCookie(siteHostname) {
|
|
54
|
-
if (typeof document === "undefined") {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
const apex = registrableSiteDomain(siteHostname);
|
|
58
|
-
if (!apex) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
const prefix = `${JMP_LANDING_REFERER_COOKIE}=`;
|
|
62
|
-
for (const part of document.cookie.split(";")) {
|
|
63
|
-
const trimmed = part.trim();
|
|
64
|
-
if (!trimmed.startsWith(prefix)) {
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
try {
|
|
68
|
-
const value = decodeURIComponent(trimmed.slice(prefix.length)).trim();
|
|
69
|
-
return value.length > 0 ? value : null;
|
|
70
|
-
} catch {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
function writeLandingRefererCookie(referer, siteHostname) {
|
|
77
|
-
if (typeof document === "undefined") {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
const apex = registrableSiteDomain(siteHostname);
|
|
81
|
-
if (!apex) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const value = encodeURIComponent(referer.trim().slice(0, 2048));
|
|
85
|
-
const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
|
|
86
|
-
document.cookie = `${JMP_LANDING_REFERER_COOKIE}=${value}; Domain=.${apex}; Path=/; Max-Age=${LANDING_REFERER_COOKIE_MAX_AGE_SEC}; SameSite=Lax${secure}`;
|
|
87
|
-
}
|
|
88
|
-
function captureLandingAttribution(siteHostname) {
|
|
89
|
-
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
90
|
-
return { landingReferer: null, utm: {} };
|
|
91
|
-
}
|
|
92
|
-
let landingReferer = null;
|
|
93
|
-
let utm = {};
|
|
94
|
-
try {
|
|
95
|
-
const storedReferer = sessionStorage.getItem(JMP_LANDING_REFERER_KEY)?.trim() || null;
|
|
96
|
-
const cookieReferer = readLandingRefererCookie(siteHostname);
|
|
97
|
-
const preservedReferer = storedReferer || cookieReferer;
|
|
98
|
-
const currentReferer = document.referrer?.trim() || null;
|
|
99
|
-
if (currentReferer && isExternalReferrerUrl(currentReferer, siteHostname)) {
|
|
100
|
-
sessionStorage.setItem(JMP_LANDING_REFERER_KEY, currentReferer);
|
|
101
|
-
writeLandingRefererCookie(currentReferer, siteHostname);
|
|
102
|
-
landingReferer = currentReferer;
|
|
103
|
-
} else if (preservedReferer) {
|
|
104
|
-
landingReferer = preservedReferer;
|
|
105
|
-
if (!storedReferer) {
|
|
106
|
-
sessionStorage.setItem(JMP_LANDING_REFERER_KEY, preservedReferer);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
const params = new URLSearchParams(window.location.search);
|
|
110
|
-
const rawUtm = sessionStorage.getItem(JMP_UTM_SESSION_KEY);
|
|
111
|
-
if (rawUtm) {
|
|
112
|
-
const parsed = JSON.parse(rawUtm);
|
|
113
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
114
|
-
utm = parsed;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
let updated = false;
|
|
118
|
-
for (const key of JMP_UTM_PARAM_KEYS) {
|
|
119
|
-
const value = params.get(key)?.trim();
|
|
120
|
-
if (value) {
|
|
121
|
-
utm[key] = value;
|
|
122
|
-
updated = true;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
if (updated) {
|
|
126
|
-
sessionStorage.setItem(JMP_UTM_SESSION_KEY, JSON.stringify(utm));
|
|
127
|
-
}
|
|
128
|
-
} catch {
|
|
129
|
-
}
|
|
130
|
-
return { landingReferer, utm };
|
|
131
|
-
}
|
|
132
|
-
function getOrCreateSessionIdFromStorage() {
|
|
133
|
-
if (typeof window === "undefined" || !globalThis.crypto?.randomUUID) {
|
|
134
|
-
return void 0;
|
|
135
|
-
}
|
|
136
|
-
try {
|
|
137
|
-
let id = sessionStorage.getItem(JMP_SESSION_ID_KEY);
|
|
138
|
-
if (!id) {
|
|
139
|
-
id = crypto.randomUUID();
|
|
140
|
-
sessionStorage.setItem(JMP_SESSION_ID_KEY, id);
|
|
141
|
-
}
|
|
142
|
-
return id;
|
|
143
|
-
} catch {
|
|
144
|
-
return void 0;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
function buildLandingCaptureInlineScript() {
|
|
148
|
-
const cookieMaxAge = String(LANDING_REFERER_COOKIE_MAX_AGE_SEC);
|
|
149
|
-
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){}})();`;
|
|
150
|
-
}
|
|
13
|
+
} from "./chunk-A4RRTY5D.js";
|
|
151
14
|
|
|
152
15
|
// src/contact-link-classifier.ts
|
|
153
16
|
var BOOKING_LINK_HOSTS = [
|
|
@@ -754,12 +617,146 @@ async function getContentText(config, path) {
|
|
|
754
617
|
return text;
|
|
755
618
|
}
|
|
756
619
|
|
|
620
|
+
// src/content/wrapper-discovery.ts
|
|
621
|
+
var WRAPPER_SLOT_ORDER = [
|
|
622
|
+
"blog",
|
|
623
|
+
"faq",
|
|
624
|
+
"contact",
|
|
625
|
+
"trust",
|
|
626
|
+
"trust.summary",
|
|
627
|
+
"trust.testimonials",
|
|
628
|
+
"trust.reviews",
|
|
629
|
+
"trust.stats",
|
|
630
|
+
"trust.logos",
|
|
631
|
+
"trust.credentials"
|
|
632
|
+
];
|
|
633
|
+
var discovered = /* @__PURE__ */ new Set();
|
|
634
|
+
var syncTimer = null;
|
|
635
|
+
var pendingSyncConfig = null;
|
|
636
|
+
var reportedDedupeKeys = /* @__PURE__ */ new Set();
|
|
637
|
+
var syncHandler = null;
|
|
638
|
+
function markContentWrapper(slot) {
|
|
639
|
+
const normalized = slot.trim().toLowerCase();
|
|
640
|
+
if (!normalized) {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
discovered.add(normalized);
|
|
644
|
+
}
|
|
645
|
+
function markTrustBundleWrappers(bundle) {
|
|
646
|
+
for (const slot of inferTrustWrapperSlots(bundle)) {
|
|
647
|
+
discovered.add(slot);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
function inferTrustWrapperSlots(bundle) {
|
|
651
|
+
const slots = /* @__PURE__ */ new Set(["trust"]);
|
|
652
|
+
const { summary, testimonials, stats, logos, credentials } = bundle;
|
|
653
|
+
const hasSummary = summary.google_rating != null || summary.google_review_count != null || Boolean(summary.google_reviews_url?.trim()) || Boolean(summary.yelp_url?.trim()) || Boolean(summary.trustpilot_url?.trim()) || Boolean(summary.disclaimer?.trim());
|
|
654
|
+
if (hasSummary) {
|
|
655
|
+
slots.add("trust.summary");
|
|
656
|
+
}
|
|
657
|
+
let hasTestimonials = false;
|
|
658
|
+
let hasReviews = false;
|
|
659
|
+
for (const item of testimonials) {
|
|
660
|
+
const bucket = item.placement?.trim().toLowerCase();
|
|
661
|
+
if (bucket === "reviews") {
|
|
662
|
+
hasReviews = true;
|
|
663
|
+
} else {
|
|
664
|
+
hasTestimonials = true;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
if (hasTestimonials) {
|
|
668
|
+
slots.add("trust.testimonials");
|
|
669
|
+
}
|
|
670
|
+
if (hasReviews) {
|
|
671
|
+
slots.add("trust.reviews");
|
|
672
|
+
}
|
|
673
|
+
if (stats.length > 0) {
|
|
674
|
+
slots.add("trust.stats");
|
|
675
|
+
}
|
|
676
|
+
if (logos.length > 0) {
|
|
677
|
+
slots.add("trust.logos");
|
|
678
|
+
}
|
|
679
|
+
if (credentials.length > 0) {
|
|
680
|
+
slots.add("trust.credentials");
|
|
681
|
+
}
|
|
682
|
+
return sortWrapperSlots([...slots]);
|
|
683
|
+
}
|
|
684
|
+
function getDiscoveredContentWrappers() {
|
|
685
|
+
return sortWrapperSlots([...discovered]);
|
|
686
|
+
}
|
|
687
|
+
function resolveContentWrappersForReport(config) {
|
|
688
|
+
const explicit = config.contentWrappers ?? [];
|
|
689
|
+
const auto = getDiscoveredContentWrappers();
|
|
690
|
+
if (config.contentWrapperMode === "manual") {
|
|
691
|
+
return sortWrapperSlots([...explicit]);
|
|
692
|
+
}
|
|
693
|
+
if (config.contentWrapperMode === "explicit-only") {
|
|
694
|
+
return sortWrapperSlots([...explicit]);
|
|
695
|
+
}
|
|
696
|
+
const merged = /* @__PURE__ */ new Set([...explicit, ...auto]);
|
|
697
|
+
return sortWrapperSlots([...merged]);
|
|
698
|
+
}
|
|
699
|
+
function hasReportedContentWrappers(dedupeKey) {
|
|
700
|
+
return reportedDedupeKeys.has(dedupeKey);
|
|
701
|
+
}
|
|
702
|
+
function rememberReportedContentWrappers(dedupeKey) {
|
|
703
|
+
reportedDedupeKeys.add(dedupeKey);
|
|
704
|
+
}
|
|
705
|
+
function setContentWrapperSyncHandler(handler) {
|
|
706
|
+
syncHandler = handler;
|
|
707
|
+
}
|
|
708
|
+
function scheduleContentWrapperSync(config) {
|
|
709
|
+
if (!syncHandler) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
pendingSyncConfig = config;
|
|
713
|
+
if (syncTimer) {
|
|
714
|
+
clearTimeout(syncTimer);
|
|
715
|
+
}
|
|
716
|
+
syncTimer = setTimeout(() => {
|
|
717
|
+
syncTimer = null;
|
|
718
|
+
const cfg = pendingSyncConfig;
|
|
719
|
+
pendingSyncConfig = null;
|
|
720
|
+
if (!cfg || !syncHandler) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
void syncHandler(cfg).catch(() => {
|
|
724
|
+
});
|
|
725
|
+
}, 750);
|
|
726
|
+
}
|
|
727
|
+
function resetContentWrapperDiscoveryForTests() {
|
|
728
|
+
discovered.clear();
|
|
729
|
+
reportedDedupeKeys.clear();
|
|
730
|
+
syncHandler = null;
|
|
731
|
+
if (syncTimer) {
|
|
732
|
+
clearTimeout(syncTimer);
|
|
733
|
+
syncTimer = null;
|
|
734
|
+
}
|
|
735
|
+
pendingSyncConfig = null;
|
|
736
|
+
}
|
|
737
|
+
function sortWrapperSlots(slots) {
|
|
738
|
+
const order = new Map(WRAPPER_SLOT_ORDER.map((slot, index) => [slot, index]));
|
|
739
|
+
return [...new Set(slots)].sort((a, b) => {
|
|
740
|
+
const ai = order.get(a) ?? 999;
|
|
741
|
+
const bi = order.get(b) ?? 999;
|
|
742
|
+
if (ai !== bi) {
|
|
743
|
+
return ai - bi;
|
|
744
|
+
}
|
|
745
|
+
return a.localeCompare(b);
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
|
|
757
749
|
// src/content/api.ts
|
|
750
|
+
function afterContentWrapperUse(config) {
|
|
751
|
+
scheduleContentWrapperSync(config);
|
|
752
|
+
}
|
|
758
753
|
async function getJmpBlogPosts(config) {
|
|
759
754
|
const data = await getContent(
|
|
760
755
|
config,
|
|
761
756
|
"/api/content/blog"
|
|
762
757
|
);
|
|
758
|
+
markContentWrapper("blog");
|
|
759
|
+
afterContentWrapperUse(config);
|
|
763
760
|
return data.posts;
|
|
764
761
|
}
|
|
765
762
|
async function getJmpBlogPost(config, slug) {
|
|
@@ -768,6 +765,8 @@ async function getJmpBlogPost(config, slug) {
|
|
|
768
765
|
config,
|
|
769
766
|
`/api/content/blog/${encodeURIComponent(slug)}`
|
|
770
767
|
);
|
|
768
|
+
markContentWrapper("blog");
|
|
769
|
+
afterContentWrapperUse(config);
|
|
771
770
|
return data.post;
|
|
772
771
|
} catch (error) {
|
|
773
772
|
if (error instanceof Error && error.message.includes("HTTP 404")) {
|
|
@@ -781,35 +780,35 @@ async function getJmpFaqs(config) {
|
|
|
781
780
|
config,
|
|
782
781
|
"/api/content/faqs"
|
|
783
782
|
);
|
|
783
|
+
markContentWrapper("faq");
|
|
784
|
+
afterContentWrapperUse(config);
|
|
784
785
|
return data.faqs;
|
|
785
786
|
}
|
|
786
787
|
async function getJmpTrustBundle(config) {
|
|
787
|
-
|
|
788
|
+
const bundle = await getContent(config, "/api/content/trust");
|
|
789
|
+
markTrustBundleWrappers(bundle);
|
|
790
|
+
afterContentWrapperUse(config);
|
|
791
|
+
return bundle;
|
|
788
792
|
}
|
|
789
793
|
async function getJmpSiteProfile(config) {
|
|
790
794
|
const data = await getContent(
|
|
791
795
|
config,
|
|
792
796
|
"/api/content/site"
|
|
793
797
|
);
|
|
798
|
+
markContentWrapper("contact");
|
|
799
|
+
afterContentWrapperUse(config);
|
|
794
800
|
return data.site;
|
|
795
801
|
}
|
|
796
802
|
async function getJmpRobotsTxt(config) {
|
|
797
803
|
return getContentText(config, "/api/content/robots");
|
|
798
804
|
}
|
|
799
|
-
var registeredContentWrappers = /* @__PURE__ */ new Set();
|
|
800
|
-
function resolveContentWrappersFromConfig(config) {
|
|
801
|
-
if (config.contentWrappers?.length) {
|
|
802
|
-
return [...config.contentWrappers];
|
|
803
|
-
}
|
|
804
|
-
return [];
|
|
805
|
-
}
|
|
806
805
|
async function registerJmpContentWrappers(config) {
|
|
807
|
-
const wrappers =
|
|
806
|
+
const wrappers = resolveContentWrappersForReport(config);
|
|
808
807
|
if (!wrappers.length) {
|
|
809
808
|
return { ok: false, skipped: true };
|
|
810
809
|
}
|
|
811
810
|
const dedupeKey = `${config.trackingKey}:${[...wrappers].sort().join(",")}`;
|
|
812
|
-
if (
|
|
811
|
+
if (hasReportedContentWrappers(dedupeKey)) {
|
|
813
812
|
return { ok: true, wrappers };
|
|
814
813
|
}
|
|
815
814
|
const baseUrl = normalizeBaseUrl(config.baseUrl);
|
|
@@ -846,10 +845,11 @@ async function registerJmpContentWrappers(config) {
|
|
|
846
845
|
[`JMP content wrappers HTTP ${res.status}`, parsed.error, parsed.hint].filter(Boolean).join(" \u2014 ")
|
|
847
846
|
);
|
|
848
847
|
}
|
|
849
|
-
|
|
848
|
+
rememberReportedContentWrappers(dedupeKey);
|
|
850
849
|
const data = JSON.parse(text);
|
|
851
850
|
return { ok: true, wrappers: data.wrappers ?? wrappers };
|
|
852
851
|
}
|
|
852
|
+
setContentWrapperSyncHandler(registerJmpContentWrappers);
|
|
853
853
|
|
|
854
854
|
// src/content/client.ts
|
|
855
855
|
function createJmpContentClient(config) {
|
|
@@ -910,7 +910,6 @@ function createJmpContentClientFromServerEnv(overrides) {
|
|
|
910
910
|
}
|
|
911
911
|
|
|
912
912
|
export {
|
|
913
|
-
buildLandingCaptureInlineScript,
|
|
914
913
|
BOOKING_LINK_HOSTS,
|
|
915
914
|
classifyContactHref,
|
|
916
915
|
JMP_ANALYTICS_SDK_VERSION,
|
|
@@ -920,6 +919,10 @@ export {
|
|
|
920
919
|
subscribeJmpTrackingConsent,
|
|
921
920
|
setJmpTrackingConsent,
|
|
922
921
|
createJmpAnalytics,
|
|
922
|
+
markContentWrapper,
|
|
923
|
+
inferTrustWrapperSlots,
|
|
924
|
+
getDiscoveredContentWrappers,
|
|
925
|
+
resetContentWrapperDiscoveryForTests,
|
|
923
926
|
getJmpBlogPosts,
|
|
924
927
|
getJmpBlogPost,
|
|
925
928
|
getJmpFaqs,
|
|
@@ -935,4 +938,4 @@ export {
|
|
|
935
938
|
getOrCreateJmpAnalyticsFromBrowserEnv,
|
|
936
939
|
createJmpContentClientFromServerEnv
|
|
937
940
|
};
|
|
938
|
-
//# sourceMappingURL=chunk-
|
|
941
|
+
//# sourceMappingURL=chunk-RCOHQAGL.js.map
|