@jmp-technologies/analytics 0.1.27 → 0.1.30
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 +226 -9
- 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 +2 -2
- package/dist/astro/content.cjs +253 -13
- package/dist/astro/content.cjs.map +1 -1
- package/dist/astro/content.d.cts +10 -3
- package/dist/astro/content.d.ts +10 -3
- package/dist/astro/content.js +5 -3
- package/dist/astro/integration.cjs +228 -10
- package/dist/astro/integration.cjs.map +1 -1
- package/dist/astro/integration.js +3 -3
- package/dist/astro/middleware.cjs +2 -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 +228 -10
- package/dist/astro/register.cjs.map +1 -1
- package/dist/astro/register.js +3 -3
- package/dist/{chunk-A4RRTY5D.js → chunk-AIFJ7DVO.js} +3 -2
- package/dist/{chunk-A4RRTY5D.js.map → chunk-AIFJ7DVO.js.map} +1 -1
- package/dist/{chunk-PG3IOI5O.js → chunk-FS4BZ7DR.js} +27 -6
- package/dist/chunk-FS4BZ7DR.js.map +1 -0
- package/dist/{chunk-UVYF33XE.js → chunk-GTPKRBFR.js} +230 -11
- package/dist/chunk-GTPKRBFR.js.map +1 -0
- package/dist/{chunk-HODG4NJQ.js → chunk-HFLU7HQE.js} +2 -2
- package/dist/{chunk-FNWE2DT5.js → chunk-LLTB7ZFC.js} +2 -2
- package/dist/{chunk-DNYJF7BG.js → chunk-LTPLLPOR.js} +3 -3
- package/dist/{chunk-KZ4WVDGL.js → chunk-ZEC22LFP.js} +3 -3
- package/dist/{create-jmp-analytics-DpG5Hdl5.d.ts → create-jmp-analytics-BeuuU6as.d.ts} +1 -1
- package/dist/{create-jmp-analytics-D4Qw_d6o.d.cts → create-jmp-analytics-Ce2CrhhR.d.cts} +1 -1
- package/dist/index.cjs +232 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +6 -2
- package/dist/next/content.cjs +253 -13
- package/dist/next/content.cjs.map +1 -1
- package/dist/next/content.d.cts +3 -3
- package/dist/next/content.d.ts +3 -3
- package/dist/next/content.js +5 -3
- package/dist/next/content.js.map +1 -1
- package/dist/next/instrumentation.cjs +228 -10
- package/dist/next/instrumentation.cjs.map +1 -1
- package/dist/next/instrumentation.js +2 -2
- package/dist/next/landing-pages.cjs +228 -12
- package/dist/next/landing-pages.cjs.map +1 -1
- package/dist/next/landing-pages.d.cts +2 -3
- package/dist/next/landing-pages.d.ts +2 -3
- package/dist/next/landing-pages.js +3 -5
- package/dist/next/landing-pages.js.map +1 -1
- package/dist/next/middleware.cjs +2 -1
- 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 +228 -10
- package/dist/next/site-root-server.cjs.map +1 -1
- package/dist/next/site-root-server.js +4 -4
- package/dist/next.cjs +226 -9
- package/dist/next.cjs.map +1 -1
- package/dist/next.d.cts +3 -3
- package/dist/next.d.ts +3 -3
- package/dist/next.js +4 -4
- package/dist/react.cjs +226 -9
- 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 +3 -3
- package/dist/{site-config-CJqYnZm7.d.cts → site-config-B4uEj4xX.d.ts} +22 -2
- package/dist/{site-config-UTzDG-UG.d.ts → site-config-DIFmnwqv.d.cts} +22 -2
- package/dist/{track-page-view-tX0Magkd.d.cts → track-page-view-Bv0BAvxG.d.cts} +1 -1
- package/dist/{track-page-view-CMAqyhD_.d.ts → track-page-view-CkgIp9-j.d.ts} +1 -1
- package/dist/{types-B-111Wht.d.cts → types-B2iP79lb.d.cts} +59 -1
- package/dist/{types-B-111Wht.d.ts → types-B2iP79lb.d.ts} +59 -1
- package/package.json +1 -1
- package/dist/chunk-PG3IOI5O.js.map +0 -1
- package/dist/chunk-UVYF33XE.js.map +0 -1
- /package/dist/{chunk-HODG4NJQ.js.map → chunk-HFLU7HQE.js.map} +0 -0
- /package/dist/{chunk-FNWE2DT5.js.map → chunk-LLTB7ZFC.js.map} +0 -0
- /package/dist/{chunk-DNYJF7BG.js.map → chunk-LTPLLPOR.js.map} +0 -0
- /package/dist/{chunk-KZ4WVDGL.js.map → chunk-ZEC22LFP.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/next/middleware.ts","../../src/http.ts","../../src/server/page-view.ts","../../src/server/should-track-server-page-view.ts","../../src/env-public.ts","../../src/site-config.ts","../../src/server/track-page-view.ts"],"sourcesContent":["import type { JmpServerRequestLike } from \"../index\"\nimport { jmpTrackServerPageView } from \"../server/track-page-view\"\n\n/**\n * Default middleware matcher string for copy-paste into the client `middleware.ts`.\n * Do **not** spread this into `export const config` — Next.js requires a literal array there.\n */\nexport const JMP_MIDDLEWARE_MATCHER = [\n \"/((?!_next/static|_next/image|favicon.ico|.*\\\\.(?:ico|png|jpg|jpeg|gif|webp|svg|webm|mp4|css|js|woff2?|txt|xml)$).*)\",\n] as const\n\nexport { jmpTrackServerPageView } from \"../server/track-page-view\"\n\nexport type { JmpServerRequestLike }\n","import type { JmpServerRequestLike } from \"./types\"\n\n/** CDN / Next.js often return HTML error pages; API errors are JSON */\nexport function responseLooksLikeHtml(body: string): boolean {\n const s = body.trimStart()\n return (\n s.startsWith(\"<!\") ||\n s.toLowerCase().startsWith(\"<html\") ||\n (s.startsWith(\"<\") && /<html[\\s>]/i.test(s.slice(0, 500)))\n )\n}\n\nexport function normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/$/, \"\")\n}\n\nexport function getHeader(\n headers: JmpServerRequestLike[\"headers\"] | undefined,\n key: string\n): string | null {\n if (!headers) {\n return null\n }\n if (typeof (headers as Pick<Headers, \"get\">).get === \"function\") {\n return (headers as Pick<Headers, \"get\">).get(key)\n }\n const record = headers as Record<string, string | string[] | undefined | null>\n const direct = record[key] ?? record[key.toLowerCase()]\n if (Array.isArray(direct)) {\n return direct[0] ?? null\n }\n return direct ?? null\n}\n\nexport function pathFromRequestUrl(url: string | undefined): string {\n if (!url) {\n return \"/\"\n }\n try {\n const u = new URL(url)\n return `${u.pathname}${u.search}`\n } catch {\n return url.startsWith(\"/\") ? url : \"/\"\n }\n}\n\nexport function resolveFetch(\n fetchImpl: typeof fetch | undefined,\n context: string\n): typeof fetch {\n const resolved =\n fetchImpl ??\n (typeof globalThis !== \"undefined\" && globalThis.fetch\n ? globalThis.fetch.bind(globalThis)\n : undefined)\n if (!resolved) {\n throw new Error(`${context}: fetch is not available; pass fetchImpl in config`)\n }\n return resolved\n}\n\ntype ApiErrorJson = {\n error?: string\n code?: string\n hint?: string\n expectedDomain?: string\n}\n\nexport function formatApiErrorMessage(\n status: number,\n text: string,\n prefix: string\n): string {\n if (responseLooksLikeHtml(text)) {\n return `${prefix} HTTP ${status} — response was HTML (not the JSON API). Check baseUrl points at the JMP Analytics dashboard.`\n }\n let parsed: ApiErrorJson = {}\n try {\n parsed = JSON.parse(text) as ApiErrorJson\n } catch {\n parsed = { error: text }\n }\n const detail = parsed.error ?? text\n return [\n `${prefix} HTTP ${status}`,\n parsed.code ? `[${parsed.code}]` : null,\n detail,\n parsed.hint ? `Hint: ${parsed.hint}` : null,\n parsed.expectedDomain\n ? `Domain in JMP admin: ${parsed.expectedDomain}`\n : null,\n ]\n .filter(Boolean)\n .join(\" — \")\n}\n","import {\n 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","import { getHeader } from \"../http\"\nimport type { JmpServerRequestLike } from \"../types\"\n\n/**\n * Returns true when middleware should not record a server page view.\n * Skips Next.js RSC flights, router prefetch, and non-document fetches so\n * {@link jmpTrackServerPageView} does not inflate counts alongside `JmpSiteRoot`.\n */\nexport function shouldSkipServerPageViewRequest(\n request: JmpServerRequestLike | undefined,\n): boolean {\n if (!request) {\n return false\n }\n\n const url = request.url\n if (url) {\n try {\n const parsed = new URL(url, \"https://jmp.local\")\n if (parsed.searchParams.has(\"_rsc\")) {\n return true\n }\n } catch {\n if (url.includes(\"_rsc=\")) {\n return true\n }\n }\n }\n\n const headers = request.headers\n\n if (\n getHeader(headers, \"next-router-prefetch\") === \"1\" ||\n getHeader(headers, \"purpose\") === \"prefetch\" ||\n getHeader(headers, \"x-middleware-prefetch\") === \"1\"\n ) {\n return true\n }\n\n const rsc = getHeader(headers, \"rsc\")\n if (rsc === \"1\") {\n return true\n }\n\n if (getHeader(headers, \"next-router-state-tree\")) {\n return true\n }\n\n const secDest = getHeader(headers, \"sec-fetch-dest\")\n if (secDest && secDest !== \"document\") {\n return true\n }\n\n return false\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","import { trackJmpServerPageView } from \"./page-view\"\nimport { shouldSkipServerPageViewRequest } from \"./should-track-server-page-view\"\nimport type { JmpServerRequestLike } from \"../types\"\nimport { getJmpContentConfig } from \"../site-config\"\n\n/**\n * Records a server-side page view when JMP env is configured.\n * Framework-agnostic — pass any `Request`-like object (Next middleware, Astro `context.request`).\n *\n * Skips Next.js RSC/prefetch requests. When using {@link JmpSiteRoot} for browser\n * page views, do not call this from middleware — it duplicates counts and omits `sessionId`.\n */\nexport async function jmpTrackServerPageView(\n request: JmpServerRequestLike,\n): Promise<void> {\n if (shouldSkipServerPageViewRequest(request)) {\n return\n }\n const config = getJmpContentConfig()\n if (!config) {\n return\n }\n await trackJmpServerPageView(config, { request }).catch(() => {})\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/next/middleware.ts","../../src/http.ts","../../src/server/page-view.ts","../../src/server/should-track-server-page-view.ts","../../src/env-public.ts","../../src/site-config.ts","../../src/server/track-page-view.ts"],"sourcesContent":["import type { JmpServerRequestLike } from \"../index\"\nimport { jmpTrackServerPageView } from \"../server/track-page-view\"\n\n/**\n * Default middleware matcher string for copy-paste into the client `middleware.ts`.\n * Do **not** spread this into `export const config` — Next.js requires a literal array there.\n */\nexport const JMP_MIDDLEWARE_MATCHER = [\n \"/((?!_next/static|_next/image|favicon.ico|.*\\\\.(?:ico|png|jpg|jpeg|gif|webp|svg|webm|mp4|css|js|woff2?|txt|xml)$).*)\",\n] as const\n\nexport { jmpTrackServerPageView } from \"../server/track-page-view\"\n\nexport type { JmpServerRequestLike }\n","import type { JmpServerRequestLike } from \"./types\"\n\n/** CDN / Next.js often return HTML error pages; API errors are JSON */\nexport function responseLooksLikeHtml(body: string): boolean {\n const s = body.trimStart()\n return (\n s.startsWith(\"<!\") ||\n s.toLowerCase().startsWith(\"<html\") ||\n (s.startsWith(\"<\") && /<html[\\s>]/i.test(s.slice(0, 500)))\n )\n}\n\nexport function normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/$/, \"\")\n}\n\nexport function getHeader(\n headers: JmpServerRequestLike[\"headers\"] | undefined,\n key: string\n): string | null {\n if (!headers) {\n return null\n }\n if (typeof (headers as Pick<Headers, \"get\">).get === \"function\") {\n return (headers as Pick<Headers, \"get\">).get(key)\n }\n const record = headers as Record<string, string | string[] | undefined | null>\n const direct = record[key] ?? record[key.toLowerCase()]\n if (Array.isArray(direct)) {\n return direct[0] ?? null\n }\n return direct ?? null\n}\n\nexport function pathFromRequestUrl(url: string | undefined): string {\n if (!url) {\n return \"/\"\n }\n try {\n const u = new URL(url)\n return `${u.pathname}${u.search}`\n } catch {\n return url.startsWith(\"/\") ? url : \"/\"\n }\n}\n\nexport function resolveFetch(\n fetchImpl: typeof fetch | undefined,\n context: string\n): typeof fetch {\n const resolved =\n fetchImpl ??\n (typeof globalThis !== \"undefined\" && globalThis.fetch\n ? globalThis.fetch.bind(globalThis)\n : undefined)\n if (!resolved) {\n throw new Error(`${context}: fetch is not available; pass fetchImpl in config`)\n }\n return resolved\n}\n\ntype ApiErrorJson = {\n error?: string\n code?: string\n hint?: string\n expectedDomain?: string\n}\n\nexport function formatApiErrorMessage(\n status: number,\n text: string,\n prefix: string\n): string {\n if (responseLooksLikeHtml(text)) {\n return `${prefix} HTTP ${status} — response was HTML (not the JSON API). Check baseUrl points at the JMP Analytics dashboard.`\n }\n let parsed: ApiErrorJson = {}\n try {\n parsed = JSON.parse(text) as ApiErrorJson\n } catch {\n parsed = { error: text }\n }\n const detail = parsed.error ?? text\n return [\n `${prefix} HTTP ${status}`,\n parsed.code ? `[${parsed.code}]` : null,\n detail,\n parsed.hint ? `Hint: ${parsed.hint}` : null,\n parsed.expectedDomain\n ? `Domain in JMP admin: ${parsed.expectedDomain}`\n : null,\n ]\n .filter(Boolean)\n .join(\" — \")\n}\n","import {\n 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","import { getHeader } from \"../http\"\nimport type { JmpServerRequestLike } from \"../types\"\n\n/**\n * Returns true when middleware should not record a server page view.\n * Skips Next.js RSC flights, router prefetch, and non-document fetches so\n * {@link jmpTrackServerPageView} does not inflate counts alongside `JmpSiteRoot`.\n */\nexport function shouldSkipServerPageViewRequest(\n request: JmpServerRequestLike | undefined,\n): boolean {\n if (!request) {\n return false\n }\n\n const url = request.url\n if (url) {\n try {\n const parsed = new URL(url, \"https://jmp.local\")\n if (parsed.searchParams.has(\"_rsc\")) {\n return true\n }\n } catch {\n if (url.includes(\"_rsc=\")) {\n return true\n }\n }\n }\n\n const headers = request.headers\n\n if (\n getHeader(headers, \"next-router-prefetch\") === \"1\" ||\n getHeader(headers, \"purpose\") === \"prefetch\" ||\n getHeader(headers, \"x-middleware-prefetch\") === \"1\"\n ) {\n return true\n }\n\n const rsc = getHeader(headers, \"rsc\")\n if (rsc === \"1\") {\n return true\n }\n\n if (getHeader(headers, \"next-router-state-tree\")) {\n return true\n }\n\n const secDest = getHeader(headers, \"sec-fetch-dest\")\n if (secDest && secDest !== \"document\") {\n return true\n }\n\n return false\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 { 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","import { trackJmpServerPageView } from \"./page-view\"\nimport { shouldSkipServerPageViewRequest } from \"./should-track-server-page-view\"\nimport type { JmpServerRequestLike } from \"../types\"\nimport { getJmpContentConfig } from \"../site-config\"\n\n/**\n * Records a server-side page view when JMP env is configured.\n * Framework-agnostic — pass any `Request`-like object (Next middleware, Astro `context.request`).\n *\n * Skips Next.js RSC/prefetch requests. When using {@link JmpSiteRoot} for browser\n * page views, do not call this from middleware — it duplicates counts and omits `sessionId`.\n */\nexport async function jmpTrackServerPageView(\n request: JmpServerRequestLike,\n): Promise<void> {\n if (shouldSkipServerPageViewRequest(request)) {\n return\n }\n const config = getJmpContentConfig()\n if (!config) {\n return\n }\n await trackJmpServerPageView(config, { request }).catch(() => {})\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { j as jmpTrackServerPageView } from '../track-page-view-
|
|
1
|
+
export { l as JmpServerRequestLike } from '../types-B2iP79lb.cjs';
|
|
2
|
+
export { j as jmpTrackServerPageView } from '../track-page-view-Bv0BAvxG.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Default middleware matcher string for copy-paste into the client `middleware.ts`.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { j as jmpTrackServerPageView } from '../track-page-view-
|
|
1
|
+
export { l as JmpServerRequestLike } from '../types-B2iP79lb.js';
|
|
2
|
+
export { j as jmpTrackServerPageView } from '../track-page-view-CkgIp9-j.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Default middleware matcher string for copy-paste into the client `middleware.ts`.
|
package/dist/next/middleware.js
CHANGED
|
@@ -121,6 +121,209 @@ async function getContentText(config, path) {
|
|
|
121
121
|
return text;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
// src/content/manifest.ts
|
|
125
|
+
var JMP_CONTENT_MANIFEST_VERSION = 1;
|
|
126
|
+
var TRUST_SECTIONS = /* @__PURE__ */ new Set([
|
|
127
|
+
"summary",
|
|
128
|
+
"testimonials",
|
|
129
|
+
"reviews",
|
|
130
|
+
"stats",
|
|
131
|
+
"logos",
|
|
132
|
+
"credentials"
|
|
133
|
+
]);
|
|
134
|
+
function normalizeKey(value) {
|
|
135
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
136
|
+
}
|
|
137
|
+
function isValidExtraKey(key) {
|
|
138
|
+
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(key);
|
|
139
|
+
}
|
|
140
|
+
var MAX_EXTERNAL_ADMIN_LINKS = 10;
|
|
141
|
+
var MAX_EXTERNAL_ADMIN_LINK_LABEL_LENGTH = 80;
|
|
142
|
+
var EXTERNAL_ADMIN_LINK_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
143
|
+
function tryNormalizeHttpUrl(input) {
|
|
144
|
+
if (typeof input !== "string" || !input.trim()) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const url = new URL(input.trim());
|
|
149
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
return url.toString().replace(/\/$/, "");
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function normalizeShopifyExternalSource(shopify) {
|
|
158
|
+
if (!shopify || typeof shopify !== "object") {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const adminUrl = tryNormalizeHttpUrl(shopify.adminUrl);
|
|
162
|
+
const storeUrl = tryNormalizeHttpUrl(shopify.storeUrl);
|
|
163
|
+
if (!adminUrl && !storeUrl) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
...adminUrl ? { adminUrl } : {},
|
|
168
|
+
...storeUrl ? { storeUrl } : {}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function normalizeExternalAdminLinkEntries(links) {
|
|
172
|
+
if (!Array.isArray(links)) {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
176
|
+
const out = [];
|
|
177
|
+
for (const raw of links) {
|
|
178
|
+
if (!raw || typeof raw !== "object" || out.length >= MAX_EXTERNAL_ADMIN_LINKS) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const id = typeof raw.id === "string" ? raw.id.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") : "";
|
|
182
|
+
const label = typeof raw.label === "string" ? raw.label.trim().slice(0, MAX_EXTERNAL_ADMIN_LINK_LABEL_LENGTH) : "";
|
|
183
|
+
const url = tryNormalizeHttpUrl(raw.url);
|
|
184
|
+
if (!id || !label || !url || !EXTERNAL_ADMIN_LINK_ID_PATTERN.test(id) || seenIds.has(id)) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
seenIds.add(id);
|
|
188
|
+
out.push({ id, label, url });
|
|
189
|
+
}
|
|
190
|
+
return out;
|
|
191
|
+
}
|
|
192
|
+
function normalizeExternalSources(externalSources) {
|
|
193
|
+
if (!externalSources || typeof externalSources !== "object") {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
const studioUrl = tryNormalizeHttpUrl(externalSources.sanity?.studioUrl);
|
|
197
|
+
const shopify = normalizeShopifyExternalSource(externalSources.shopify);
|
|
198
|
+
const links = normalizeExternalAdminLinkEntries(externalSources.links);
|
|
199
|
+
if (!studioUrl && !shopify && links.length === 0) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
...studioUrl ? { sanity: { studioUrl } } : {},
|
|
204
|
+
...shopify ? { shopify } : {},
|
|
205
|
+
...links.length > 0 ? { links } : {}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function normalizeJmpContentManifest(input) {
|
|
209
|
+
if (!input || typeof input !== "object") {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
const routes = normalizeRoutes(input.routes);
|
|
213
|
+
const externalSources = normalizeExternalSources(input.externalSources);
|
|
214
|
+
const trust = normalizeTrust(input.trust);
|
|
215
|
+
const contact = normalizeContact(input.contact);
|
|
216
|
+
if (routes.length === 0 && !externalSources && !trust && !contact) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
version: JMP_CONTENT_MANIFEST_VERSION,
|
|
221
|
+
...routes.length > 0 ? { routes } : {},
|
|
222
|
+
...externalSources ? { externalSources } : {},
|
|
223
|
+
...trust ? { trust } : {},
|
|
224
|
+
...contact ? { contact } : {}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function normalizeRoutes(routes) {
|
|
228
|
+
if (!Array.isArray(routes)) {
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
const seen = /* @__PURE__ */ new Set();
|
|
232
|
+
const out = [];
|
|
233
|
+
for (const raw of routes) {
|
|
234
|
+
if (!raw || typeof raw !== "object") continue;
|
|
235
|
+
const wrapper = typeof raw.wrapper === "string" ? raw.wrapper.trim().toLowerCase() : "";
|
|
236
|
+
const pattern = typeof raw.pattern === "string" ? raw.pattern.trim() : "";
|
|
237
|
+
if (!wrapper || !pattern) continue;
|
|
238
|
+
const dedupe = `${wrapper}:${pattern}`;
|
|
239
|
+
if (seen.has(dedupe)) continue;
|
|
240
|
+
seen.add(dedupe);
|
|
241
|
+
out.push({
|
|
242
|
+
wrapper,
|
|
243
|
+
pattern,
|
|
244
|
+
...typeof raw.index === "string" && raw.index.trim() ? { index: raw.index.trim() } : {},
|
|
245
|
+
...typeof raw.label === "string" && raw.label.trim() ? { label: raw.label.trim() } : {}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
function normalizeTrust(trust) {
|
|
251
|
+
if (!trust || typeof trust !== "object") return null;
|
|
252
|
+
const blocks = normalizeTrustBlocks(trust.blocks);
|
|
253
|
+
const labels = normalizeTrustLabels(trust.labels);
|
|
254
|
+
const mergeTestimonialsReviews = trust.mergeTestimonialsReviews === true;
|
|
255
|
+
if (blocks.length === 0 && !labels && !mergeTestimonialsReviews) return null;
|
|
256
|
+
return {
|
|
257
|
+
...blocks.length > 0 ? { blocks } : {},
|
|
258
|
+
...labels ? { labels } : {},
|
|
259
|
+
...mergeTestimonialsReviews ? { mergeTestimonialsReviews: true } : {}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function normalizeTrustBlocks(blocks) {
|
|
263
|
+
if (!Array.isArray(blocks)) return [];
|
|
264
|
+
const seen = /* @__PURE__ */ new Set();
|
|
265
|
+
const out = [];
|
|
266
|
+
for (const raw of blocks) {
|
|
267
|
+
if (!raw || typeof raw !== "object") continue;
|
|
268
|
+
const section = typeof raw.section === "string" ? raw.section.trim().toLowerCase() : "";
|
|
269
|
+
if (!TRUST_SECTIONS.has(section) || seen.has(section)) continue;
|
|
270
|
+
seen.add(section);
|
|
271
|
+
out.push({
|
|
272
|
+
section,
|
|
273
|
+
...typeof raw.label === "string" && raw.label.trim() ? { label: raw.label.trim() } : {}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
return out;
|
|
277
|
+
}
|
|
278
|
+
function normalizeTrustLabels(labels) {
|
|
279
|
+
if (!labels || typeof labels !== "object") return null;
|
|
280
|
+
const out = {};
|
|
281
|
+
for (const [key, value] of Object.entries(labels)) {
|
|
282
|
+
if (TRUST_SECTIONS.has(key) && typeof value === "string" && value.trim()) {
|
|
283
|
+
out[key] = value.trim();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
287
|
+
}
|
|
288
|
+
function normalizeContact(contact) {
|
|
289
|
+
if (!contact || typeof contact !== "object") return null;
|
|
290
|
+
const extras = normalizeContactExtras(contact.extras);
|
|
291
|
+
return extras.length > 0 ? { extras } : null;
|
|
292
|
+
}
|
|
293
|
+
function normalizeContactExtras(extras) {
|
|
294
|
+
if (!Array.isArray(extras)) return [];
|
|
295
|
+
const seen = /* @__PURE__ */ new Set();
|
|
296
|
+
const out = [];
|
|
297
|
+
for (const raw of extras) {
|
|
298
|
+
if (!raw || typeof raw !== "object") continue;
|
|
299
|
+
const label = typeof raw.label === "string" ? raw.label.trim() : "";
|
|
300
|
+
const key = normalizeKey(
|
|
301
|
+
typeof raw.key === "string" && raw.key.trim() ? raw.key : label
|
|
302
|
+
);
|
|
303
|
+
if (!key || !label || !isValidExtraKey(key) || seen.has(key)) continue;
|
|
304
|
+
seen.add(key);
|
|
305
|
+
out.push({ key, label });
|
|
306
|
+
}
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
309
|
+
function stableJmpManifestFingerprint(manifest) {
|
|
310
|
+
return manifest ? JSON.stringify(manifest) : "";
|
|
311
|
+
}
|
|
312
|
+
function inferWrapperSlotsFromManifest(manifest) {
|
|
313
|
+
const slots = /* @__PURE__ */ new Set();
|
|
314
|
+
for (const route of manifest?.routes ?? []) {
|
|
315
|
+
slots.add(route.wrapper);
|
|
316
|
+
}
|
|
317
|
+
for (const block of manifest?.trust?.blocks ?? []) {
|
|
318
|
+
slots.add(`trust.${block.section}`);
|
|
319
|
+
slots.add("trust");
|
|
320
|
+
}
|
|
321
|
+
if (manifest?.contact?.extras?.length) {
|
|
322
|
+
slots.add("contact");
|
|
323
|
+
}
|
|
324
|
+
return [...slots];
|
|
325
|
+
}
|
|
326
|
+
|
|
124
327
|
// src/content/wrapper-discovery.ts
|
|
125
328
|
var WRAPPER_SLOT_ORDER = [
|
|
126
329
|
"blog",
|
|
@@ -149,16 +352,22 @@ function markContentWrapper(slot) {
|
|
|
149
352
|
function getDiscoveredContentWrappers() {
|
|
150
353
|
return sortWrapperSlots([...discovered]);
|
|
151
354
|
}
|
|
355
|
+
function resolveContentManifestForReport(config) {
|
|
356
|
+
return normalizeJmpContentManifest(config.contentManifest);
|
|
357
|
+
}
|
|
152
358
|
function resolveContentWrappersForReport(config) {
|
|
153
359
|
const explicit = config.contentWrappers ?? [];
|
|
154
360
|
const auto = getDiscoveredContentWrappers();
|
|
361
|
+
const fromManifest = inferWrapperSlotsFromManifest(
|
|
362
|
+
resolveContentManifestForReport(config)
|
|
363
|
+
);
|
|
155
364
|
if (config.contentWrapperMode === "manual") {
|
|
156
|
-
return sortWrapperSlots([...explicit]);
|
|
365
|
+
return sortWrapperSlots([...explicit, ...fromManifest]);
|
|
157
366
|
}
|
|
158
367
|
if (config.contentWrapperMode === "explicit-only") {
|
|
159
|
-
return sortWrapperSlots([...explicit]);
|
|
368
|
+
return sortWrapperSlots([...explicit, ...fromManifest]);
|
|
160
369
|
}
|
|
161
|
-
const merged = /* @__PURE__ */ new Set([...explicit, ...auto]);
|
|
370
|
+
const merged = /* @__PURE__ */ new Set([...explicit, ...auto, ...fromManifest]);
|
|
162
371
|
return sortWrapperSlots([...merged]);
|
|
163
372
|
}
|
|
164
373
|
function hasReportedContentWrappers(dedupeKey) {
|
|
@@ -219,7 +428,8 @@ async function registerJmpContentWrappers(config) {
|
|
|
219
428
|
if (!wrappers.length) {
|
|
220
429
|
return { ok: false, skipped: true };
|
|
221
430
|
}
|
|
222
|
-
const
|
|
431
|
+
const manifest = resolveContentManifestForReport(config);
|
|
432
|
+
const dedupeKey = `${config.trackingKey}:${[...wrappers].sort().join(",")}:${stableJmpManifestFingerprint(manifest)}`;
|
|
223
433
|
if (hasReportedContentWrappers(dedupeKey)) {
|
|
224
434
|
return { ok: true, wrappers };
|
|
225
435
|
}
|
|
@@ -232,7 +442,17 @@ async function registerJmpContentWrappers(config) {
|
|
|
232
442
|
}
|
|
233
443
|
const url = `${baseUrl}/api/content/capabilities`;
|
|
234
444
|
if (config.debug) {
|
|
235
|
-
console.debug("[@jmp-technologies/analytics]", "POST", url, {
|
|
445
|
+
console.debug("[@jmp-technologies/analytics]", "POST", url, {
|
|
446
|
+
wrappers,
|
|
447
|
+
manifest
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
const body = {
|
|
451
|
+
wrappers,
|
|
452
|
+
sdkVersion: JMP_ANALYTICS_SDK_VERSION
|
|
453
|
+
};
|
|
454
|
+
if (manifest) {
|
|
455
|
+
body.manifest = manifest;
|
|
236
456
|
}
|
|
237
457
|
const res = await resolvedFetch(url, {
|
|
238
458
|
method: "POST",
|
|
@@ -240,10 +460,7 @@ async function registerJmpContentWrappers(config) {
|
|
|
240
460
|
"Content-Type": "application/json",
|
|
241
461
|
"X-JMP-Tracking-Key": config.trackingKey
|
|
242
462
|
},
|
|
243
|
-
body: JSON.stringify(
|
|
244
|
-
wrappers,
|
|
245
|
-
sdkVersion: JMP_ANALYTICS_SDK_VERSION
|
|
246
|
-
})
|
|
463
|
+
body: JSON.stringify(body)
|
|
247
464
|
});
|
|
248
465
|
const text = await res.text();
|
|
249
466
|
if (!res.ok) {
|
|
@@ -322,7 +539,8 @@ function getJmpContentConfig() {
|
|
|
322
539
|
return {
|
|
323
540
|
baseUrl: analyticsUrl,
|
|
324
541
|
trackingKey,
|
|
325
|
-
contentWrappers: site?.contentWrappers ?? void 0
|
|
542
|
+
contentWrappers: site?.contentWrappers ?? void 0,
|
|
543
|
+
contentManifest: site?.contentManifest ?? void 0
|
|
326
544
|
};
|
|
327
545
|
}
|
|
328
546
|
|