@jmp-technologies/analytics 0.1.17 → 0.1.19
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/README.md +3 -1
- package/dist/{chunk-3ELQETAA.js → chunk-EPNF76VK.js} +2 -2
- package/dist/{chunk-MUHLIN7P.js → chunk-GB37RVJJ.js} +339 -103
- package/dist/chunk-GB37RVJJ.js.map +1 -0
- package/dist/index.cjs +348 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -35
- package/dist/index.d.ts +112 -35
- package/dist/index.js +21 -5
- package/dist/next/content.cjs +322 -0
- package/dist/next/content.cjs.map +1 -0
- package/dist/next/content.d.cts +37 -0
- package/dist/next/content.d.ts +37 -0
- package/dist/next/content.js +172 -0
- package/dist/next/content.js.map +1 -0
- package/dist/next/instrumentation.cjs +131 -0
- package/dist/next/instrumentation.cjs.map +1 -0
- package/dist/next/instrumentation.d.cts +7 -0
- package/dist/next/instrumentation.d.ts +7 -0
- package/dist/next/instrumentation.js +18 -0
- package/dist/next/instrumentation.js.map +1 -0
- package/dist/next/middleware.cjs +147 -0
- package/dist/next/middleware.cjs.map +1 -0
- package/dist/next/middleware.d.cts +13 -0
- package/dist/next/middleware.d.ts +13 -0
- package/dist/next/middleware.js +22 -0
- package/dist/next/middleware.js.map +1 -0
- package/dist/next.cjs +322 -86
- package/dist/next.cjs.map +1 -1
- package/dist/next.d.cts +24 -8
- package/dist/next.d.ts +24 -8
- package/dist/next.js +41 -13
- package/dist/next.js.map +1 -1
- package/dist/react.cjs +279 -69
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +2 -2
- package/docs/legal-and-privacy.md +1 -0
- package/package.json +17 -2
- package/dist/chunk-MUHLIN7P.js.map +0 -1
- /package/dist/{chunk-3ELQETAA.js.map → chunk-EPNF76VK.js.map} +0 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,19 +1,95 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Classify outbound contact links for auto-tracking.
|
|
3
|
+
*
|
|
4
|
+
* Keep channel set in sync with `lib/ingest/contact-click.ts` on the dashboard side.
|
|
5
|
+
*
|
|
6
|
+
* Channels detected from `href` alone (no widget / iframe detection):
|
|
7
|
+
*
|
|
8
|
+
* - `call` — `tel:` (delegates to existing `trackCallClick`)
|
|
9
|
+
* - `email` — `mailto:` (delegates to existing `trackEmailClick`)
|
|
10
|
+
* - `sms` — `sms:` / `smsto:`
|
|
11
|
+
* - `whatsapp` — `wa.me`, `api.whatsapp.com`, `whatsapp.com/send`
|
|
12
|
+
* - `maps` — Google Maps, Apple Maps, `maps.app.goo.gl`, `geo:`
|
|
13
|
+
* - `booking` — fixed allowlist of common scheduling hosts (see {@link BOOKING_LINK_HOSTS})
|
|
14
|
+
*
|
|
15
|
+
* `linkHost` returned in `contact_click` results is the hostname only — never the full URL
|
|
16
|
+
* (avoids leaking phone numbers in `tel:`/`sms:` URLs and PII-laden booking query strings).
|
|
17
|
+
*/
|
|
18
|
+
type ContactLinkChannel = "sms" | "whatsapp" | "maps" | "booking";
|
|
19
|
+
type ClassifiedContactLink = {
|
|
20
|
+
kind: "call";
|
|
21
|
+
} | {
|
|
22
|
+
kind: "email";
|
|
23
|
+
} | {
|
|
24
|
+
kind: "contact_click";
|
|
25
|
+
channel: ContactLinkChannel;
|
|
26
|
+
linkHost: string | null;
|
|
27
|
+
};
|
|
28
|
+
/** v1 booking hosts (~80%+ SMB coverage). Add hosts here when a client uses something rare. */
|
|
29
|
+
declare const BOOKING_LINK_HOSTS: readonly ["calendly.com", "cal.com", "acuityscheduling.com", "calendar.google.com", "calendar.app.google", "bookings.microsoft.com", "outlook.office.com", "outlook.office365.com", "book.ms", "setmore.com", "simplybook.me", "oncehub.com", "scheduleonce.com", "youcanbook.me", "savvycal.com", "tidycal.com", "zohobookings.com", "square.site", "squareup.com", "meetings.hubspot.com", "jobber.com", "housecallpro.com", "servicetitan.com", "thryv.com", "honeybook.com", "dubsado.com", "fresha.com", "booksy.com", "vagaro.com", "mindbodyonline.com", "mindbody.io", "janeapp.com", "cliniko.com", "practicebetter.io"];
|
|
30
|
+
/** Returns `null` when the link should not be tracked as a contact link. */
|
|
31
|
+
declare function classifyContactHref(href: string): ClassifiedContactLink | null;
|
|
32
|
+
|
|
33
|
+
/** Inline script for Next.js `beforeInteractive` — capture referrer/UTM only (no network). */
|
|
34
|
+
declare function buildLandingCaptureInlineScript(): string;
|
|
35
|
+
|
|
36
|
+
/** Per-site values used when the dashboard profile is missing or fields are empty. */
|
|
37
|
+
type JmpSiteContactFallback = {
|
|
38
|
+
displayName?: string;
|
|
39
|
+
founderName?: string;
|
|
40
|
+
email?: string;
|
|
41
|
+
phone?: string | null;
|
|
42
|
+
linkedin?: string | null;
|
|
43
|
+
hours?: string | null;
|
|
44
|
+
serviceArea?: string | null;
|
|
45
|
+
dashboardDemoUrl?: string;
|
|
46
|
+
profilePhotoSrc?: string;
|
|
47
|
+
privacyUrl?: string;
|
|
48
|
+
termsUrl?: string;
|
|
49
|
+
};
|
|
50
|
+
type JmpSiteDefinition = {
|
|
51
|
+
/** Content tabs this site uses — reported via instrumentation capabilities. */
|
|
52
|
+
contentWrappers: JmpContentWrapper[];
|
|
53
|
+
/**
|
|
54
|
+
* Public site origin (no trailing slash), e.g. `https://example.com`.
|
|
55
|
+
* Used to resolve relative profile image paths in structured data.
|
|
56
|
+
*/
|
|
57
|
+
siteUrl?: string;
|
|
58
|
+
contactFallback?: JmpSiteContactFallback;
|
|
59
|
+
/**
|
|
60
|
+
* `extras` keys to read from the dashboard site profile (hyphenated keys from admin).
|
|
61
|
+
*/
|
|
62
|
+
contactExtraKeys?: {
|
|
63
|
+
founderName?: readonly string[];
|
|
64
|
+
dashboardDemoUrl?: readonly string[];
|
|
65
|
+
profilePhotoSrc?: readonly string[];
|
|
66
|
+
hours?: readonly string[];
|
|
67
|
+
serviceArea?: readonly string[];
|
|
68
|
+
};
|
|
4
69
|
};
|
|
5
70
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
71
|
+
* Register site-specific JMP integration options once (typically from `jmp.config.ts`).
|
|
72
|
+
* Call at module load time before server content or instrumentation runs.
|
|
8
73
|
*/
|
|
9
|
-
declare function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
trackingKey: string;
|
|
14
|
-
}): string;
|
|
74
|
+
declare function defineJmpSite(definition: JmpSiteDefinition): JmpSiteDefinition;
|
|
75
|
+
declare function getJmpSiteDefinition(): JmpSiteDefinition | null;
|
|
76
|
+
/** Server content + lead config: env vars + wrappers from {@link defineJmpSite}. */
|
|
77
|
+
declare function getJmpContentConfig(): JmpContentConfig | null;
|
|
15
78
|
|
|
16
|
-
|
|
79
|
+
/** Published package version — sent with capabilities for admin readiness. */
|
|
80
|
+
declare const JMP_ANALYTICS_SDK_VERSION = "0.1.19";
|
|
81
|
+
/** `localStorage` key when {@link JmpAnalyticsConfig.requireConsent} is enabled. */
|
|
82
|
+
declare const JMP_TRACKING_CONSENT_STORAGE_KEY = "jmp_analytics_consent_v1";
|
|
83
|
+
/** Whether analytics events may be sent (after CMP / cookie banner). */
|
|
84
|
+
declare function isJmpTrackingConsentGranted(): boolean;
|
|
85
|
+
/** Subscribe to consent changes (for React `useSyncExternalStore`). */
|
|
86
|
+
declare function subscribeJmpTrackingConsent(listener: () => void): () => void;
|
|
87
|
+
/**
|
|
88
|
+
* Grant or deny analytics. Persists in `localStorage` when `requireConsent` is used.
|
|
89
|
+
* Call from your cookie banner before tracking runs.
|
|
90
|
+
*/
|
|
91
|
+
declare function setJmpTrackingConsent(granted: boolean): void;
|
|
92
|
+
type JmpEventType = "page_view" | "form_submit" | "call_click" | "email_click" | "contact_click";
|
|
17
93
|
type JmpAnalyticsConfig = {
|
|
18
94
|
/** Dashboard origin, e.g. https://analytics.example.com (no trailing slash) */
|
|
19
95
|
baseUrl: string;
|
|
@@ -28,6 +104,21 @@ type JmpAnalyticsConfig = {
|
|
|
28
104
|
* Helps with React Strict Mode double-mount; typical: 500–1000.
|
|
29
105
|
*/
|
|
30
106
|
pageViewDedupeMs?: number;
|
|
107
|
+
/**
|
|
108
|
+
* When true, no events are sent until {@link setJmpTrackingConsent}(true).
|
|
109
|
+
* Pair with your cookie banner; see `NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT`.
|
|
110
|
+
*/
|
|
111
|
+
requireConsent?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* When true, register a single delegated `click` listener that classifies links
|
|
114
|
+
* (SMS, WhatsApp, maps, scheduling) and records them via {@link trackContactClick}.
|
|
115
|
+
*
|
|
116
|
+
* Off by default — pair with privacy disclosure / consent before enabling.
|
|
117
|
+
* `tel:` / `mailto:` continue to use {@link trackCallClick} / {@link trackEmailClick}.
|
|
118
|
+
*
|
|
119
|
+
* Opt out per-link with `data-jmp-no-track` on an `<a>` (or an ancestor).
|
|
120
|
+
*/
|
|
121
|
+
autoTrackContactLinks?: boolean;
|
|
31
122
|
};
|
|
32
123
|
type JmpContentConfig = Pick<JmpAnalyticsConfig, "baseUrl" | "trackingKey" | "debug" | "fetchImpl"> & {
|
|
33
124
|
/**
|
|
@@ -36,15 +127,9 @@ type JmpContentConfig = Pick<JmpAnalyticsConfig, "baseUrl" | "trackingKey" | "de
|
|
|
36
127
|
* Call {@link registerJmpContentWrappers} at startup / deploy.
|
|
37
128
|
*/
|
|
38
129
|
contentWrappers?: JmpContentWrapper[];
|
|
39
|
-
/**
|
|
40
|
-
* @deprecated Use `contentWrappers` (e.g. `trust.testimonials` instead of `testimonials`).
|
|
41
|
-
*/
|
|
42
|
-
trustSlots?: JmpTrustSlot[];
|
|
43
130
|
};
|
|
44
131
|
/** Top-level and trust sub-wrappers the client can declare. */
|
|
45
132
|
type JmpContentWrapper = "blog" | "faq" | "contact" | "trust" | "trust.summary" | "trust.testimonials" | "trust.reviews" | "trust.stats" | "trust.logos" | "trust.credentials";
|
|
46
|
-
/** @deprecated Trust-only shorthand; prefer `JmpContentWrapper`. */
|
|
47
|
-
type JmpTrustSlot = "summary" | "testimonials" | "reviews" | "stats" | "logos" | "credentials";
|
|
48
133
|
type JmpBlogPost = {
|
|
49
134
|
id: string;
|
|
50
135
|
title: string;
|
|
@@ -241,8 +326,16 @@ declare function createJmpAnalytics(config: JmpAnalyticsConfig): {
|
|
|
241
326
|
trackFormSubmit: (path: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
242
327
|
trackCallClick: (path: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
243
328
|
trackEmailClick: (path: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
329
|
+
trackContactClick: (path: string, options: {
|
|
330
|
+
channel: ContactLinkChannel;
|
|
331
|
+
linkHost?: string | null;
|
|
332
|
+
metadata?: Record<string, unknown>;
|
|
333
|
+
}) => Promise<void>;
|
|
244
334
|
trackLead: (options?: TrackLeadOptions) => Promise<void>;
|
|
245
335
|
subscribeToSpaNavigation: () => () => void;
|
|
336
|
+
subscribeContactLinkAutoTrack: () => () => void;
|
|
337
|
+
setTrackingConsent: typeof setJmpTrackingConsent;
|
|
338
|
+
isTrackingConsentGranted: typeof isJmpTrackingConsentGranted;
|
|
246
339
|
};
|
|
247
340
|
type JmpAnalytics = ReturnType<typeof createJmpAnalytics>;
|
|
248
341
|
declare function trackJmpServerPageView(config: JmpContentConfig, options?: TrackServerPageViewOptions): Promise<void>;
|
|
@@ -263,14 +356,6 @@ declare function registerJmpContentWrappers(config: JmpContentConfig): Promise<{
|
|
|
263
356
|
ok: false;
|
|
264
357
|
skipped: true;
|
|
265
358
|
}>;
|
|
266
|
-
/** @deprecated Use {@link registerJmpContentWrappers} with `contentWrappers`. */
|
|
267
|
-
declare function registerJmpTrustCapabilities(config: JmpContentConfig): Promise<{
|
|
268
|
-
ok: true;
|
|
269
|
-
slots: JmpTrustSlot[];
|
|
270
|
-
} | {
|
|
271
|
-
ok: false;
|
|
272
|
-
skipped: true;
|
|
273
|
-
}>;
|
|
274
359
|
declare function createJmpContentClient(config: JmpContentConfig): {
|
|
275
360
|
getBlogPosts: () => Promise<JmpBlogPost[]>;
|
|
276
361
|
getBlogPost: (slug: string) => Promise<JmpBlogPost | null>;
|
|
@@ -285,14 +370,6 @@ declare function createJmpContentClient(config: JmpContentConfig): {
|
|
|
285
370
|
ok: false;
|
|
286
371
|
skipped: true;
|
|
287
372
|
}>;
|
|
288
|
-
/** @deprecated Use registerContentWrappers */
|
|
289
|
-
registerTrustCapabilities: () => Promise<{
|
|
290
|
-
ok: true;
|
|
291
|
-
slots: JmpTrustSlot[];
|
|
292
|
-
} | {
|
|
293
|
-
ok: false;
|
|
294
|
-
skipped: true;
|
|
295
|
-
}>;
|
|
296
373
|
};
|
|
297
374
|
/** Default `pageViewDedupeMs` when resolving browser config from env (Strict Mode / SPA). */
|
|
298
375
|
declare const DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS = 800;
|
|
@@ -321,4 +398,4 @@ type JmpContentClient = ReturnType<typeof createJmpContentClient>;
|
|
|
321
398
|
*/
|
|
322
399
|
declare function createJmpContentClientFromServerEnv(overrides?: Partial<JmpContentConfig>): JmpContentClient | null;
|
|
323
400
|
|
|
324
|
-
export { DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS, type JmpAnalytics, type JmpAnalyticsConfig, type JmpBlogPost, type JmpContentClient, type JmpContentConfig, type JmpContentWrapper, type JmpCredential, type JmpEventType, type JmpFaqItem, type JmpPartnerLogo, type JmpServerRequestLike, type
|
|
401
|
+
export { BOOKING_LINK_HOSTS, type ClassifiedContactLink, type ContactLinkChannel, DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS, JMP_ANALYTICS_SDK_VERSION, JMP_TRACKING_CONSENT_STORAGE_KEY, type JmpAnalytics, type JmpAnalyticsConfig, type JmpBlogPost, type JmpContentClient, type JmpContentConfig, type JmpContentWrapper, type JmpCredential, type JmpEventType, type JmpFaqItem, type JmpPartnerLogo, type JmpServerRequestLike, type JmpSiteContactFallback, type JmpSiteDefinition, type JmpSiteProfile, type JmpTestimonial, type JmpTrustBundle, type JmpTrustStat, type JmpTrustSummary, type LeadMetadata, type LeadMetadataValue, type TrackLeadOptions, type TrackPageViewOptions, type TrackServerPageViewOptions, buildLandingCaptureInlineScript, classifyContactHref, createJmpAnalytics, createJmpContentClient, createJmpContentClientFromServerEnv, defineJmpSite, getJmpBlogPost, getJmpBlogPosts, getJmpBrowserAnalyticsConfigFromEnv, getJmpContentConfig, getJmpFaqs, getJmpRobotsTxt, getJmpServerContentConfigFromEnv, getJmpSiteDefinition, getJmpSiteProfile, getJmpTrustBundle, getOrCreateJmpAnalyticsFromBrowserEnv, isJmpBrowserAnalyticsEnvConfigured, isJmpServerContentEnvConfigured, isJmpTrackingConsentGranted, registerJmpContentWrappers, setJmpTrackingConsent, subscribeJmpTrackingConsent, trackJmpServerPageView };
|
package/dist/index.js
CHANGED
|
@@ -1,45 +1,61 @@
|
|
|
1
1
|
import {
|
|
2
|
+
BOOKING_LINK_HOSTS,
|
|
2
3
|
DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,
|
|
4
|
+
JMP_ANALYTICS_SDK_VERSION,
|
|
5
|
+
JMP_TRACKING_CONSENT_STORAGE_KEY,
|
|
3
6
|
buildLandingCaptureInlineScript,
|
|
7
|
+
classifyContactHref,
|
|
4
8
|
createJmpAnalytics,
|
|
5
9
|
createJmpContentClient,
|
|
6
10
|
createJmpContentClientFromServerEnv,
|
|
11
|
+
defineJmpSite,
|
|
7
12
|
getJmpBlogPost,
|
|
8
13
|
getJmpBlogPosts,
|
|
9
14
|
getJmpBrowserAnalyticsConfigFromEnv,
|
|
15
|
+
getJmpContentConfig,
|
|
10
16
|
getJmpFaqs,
|
|
11
17
|
getJmpRobotsTxt,
|
|
12
18
|
getJmpServerContentConfigFromEnv,
|
|
19
|
+
getJmpSiteDefinition,
|
|
13
20
|
getJmpSiteProfile,
|
|
14
21
|
getJmpTrustBundle,
|
|
15
22
|
getOrCreateJmpAnalyticsFromBrowserEnv,
|
|
16
23
|
isJmpBrowserAnalyticsEnvConfigured,
|
|
17
24
|
isJmpServerContentEnvConfigured,
|
|
25
|
+
isJmpTrackingConsentGranted,
|
|
18
26
|
registerJmpContentWrappers,
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
setJmpTrackingConsent,
|
|
28
|
+
subscribeJmpTrackingConsent,
|
|
21
29
|
trackJmpServerPageView
|
|
22
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-GB37RVJJ.js";
|
|
23
31
|
export {
|
|
32
|
+
BOOKING_LINK_HOSTS,
|
|
24
33
|
DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,
|
|
34
|
+
JMP_ANALYTICS_SDK_VERSION,
|
|
35
|
+
JMP_TRACKING_CONSENT_STORAGE_KEY,
|
|
25
36
|
buildLandingCaptureInlineScript,
|
|
37
|
+
classifyContactHref,
|
|
26
38
|
createJmpAnalytics,
|
|
27
39
|
createJmpContentClient,
|
|
28
40
|
createJmpContentClientFromServerEnv,
|
|
41
|
+
defineJmpSite,
|
|
29
42
|
getJmpBlogPost,
|
|
30
43
|
getJmpBlogPosts,
|
|
31
44
|
getJmpBrowserAnalyticsConfigFromEnv,
|
|
45
|
+
getJmpContentConfig,
|
|
32
46
|
getJmpFaqs,
|
|
33
47
|
getJmpRobotsTxt,
|
|
34
48
|
getJmpServerContentConfigFromEnv,
|
|
49
|
+
getJmpSiteDefinition,
|
|
35
50
|
getJmpSiteProfile,
|
|
36
51
|
getJmpTrustBundle,
|
|
37
52
|
getOrCreateJmpAnalyticsFromBrowserEnv,
|
|
38
53
|
isJmpBrowserAnalyticsEnvConfigured,
|
|
39
54
|
isJmpServerContentEnvConfigured,
|
|
55
|
+
isJmpTrackingConsentGranted,
|
|
40
56
|
registerJmpContentWrappers,
|
|
41
|
-
|
|
42
|
-
|
|
57
|
+
setJmpTrackingConsent,
|
|
58
|
+
subscribeJmpTrackingConsent,
|
|
43
59
|
trackJmpServerPageView
|
|
44
60
|
};
|
|
45
61
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/next/content.ts
|
|
21
|
+
var content_exports = {};
|
|
22
|
+
__export(content_exports, {
|
|
23
|
+
absoluteSiteAssetUrl: () => absoluteSiteAssetUrl,
|
|
24
|
+
filterFeaturedTrustQuotes: () => filterFeaturedTrustQuotes,
|
|
25
|
+
getJmpContentConfig: () => getJmpContentConfig,
|
|
26
|
+
getResolvedSiteContact: () => getResolvedSiteContact,
|
|
27
|
+
loadBlogPost: () => loadBlogPost,
|
|
28
|
+
loadBlogPosts: () => loadBlogPosts,
|
|
29
|
+
loadFaqBySlug: () => loadFaqBySlug,
|
|
30
|
+
loadFaqs: () => loadFaqs,
|
|
31
|
+
loadSiteProfile: () => loadSiteProfile,
|
|
32
|
+
loadTrustBundle: () => loadTrustBundle,
|
|
33
|
+
resolveSiteContact: () => resolveSiteContact
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(content_exports);
|
|
36
|
+
var import_react = require("react");
|
|
37
|
+
|
|
38
|
+
// src/landing-capture.ts
|
|
39
|
+
var LANDING_REFERER_COOKIE_MAX_AGE_SEC = 30 * 60;
|
|
40
|
+
|
|
41
|
+
// src/site-config.ts
|
|
42
|
+
var registeredSite = null;
|
|
43
|
+
function readNextPublicEnv(key) {
|
|
44
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
45
|
+
const v = key === "NEXT_PUBLIC_JMP_ANALYTICS_URL" ? process.env.NEXT_PUBLIC_JMP_ANALYTICS_URL : process.env.NEXT_PUBLIC_JMP_TRACKING_KEY;
|
|
46
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
47
|
+
}
|
|
48
|
+
function getServerConfigFromEnv() {
|
|
49
|
+
const baseUrl = readNextPublicEnv("NEXT_PUBLIC_JMP_ANALYTICS_URL");
|
|
50
|
+
const trackingKey = readNextPublicEnv("NEXT_PUBLIC_JMP_TRACKING_KEY");
|
|
51
|
+
if (!baseUrl || !trackingKey) return null;
|
|
52
|
+
return { baseUrl, trackingKey };
|
|
53
|
+
}
|
|
54
|
+
function getJmpSiteDefinition() {
|
|
55
|
+
return registeredSite;
|
|
56
|
+
}
|
|
57
|
+
function getJmpContentConfig() {
|
|
58
|
+
const fromEnv = getServerConfigFromEnv();
|
|
59
|
+
if (!fromEnv) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const site = getJmpSiteDefinition();
|
|
63
|
+
return {
|
|
64
|
+
...fromEnv,
|
|
65
|
+
contentWrappers: site?.contentWrappers ?? fromEnv.contentWrappers
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/index.ts
|
|
70
|
+
function responseLooksLikeHtml(body) {
|
|
71
|
+
const s = body.trimStart();
|
|
72
|
+
return s.startsWith("<!") || s.toLowerCase().startsWith("<html") || s.startsWith("<") && /<html[\s>]/i.test(s.slice(0, 500));
|
|
73
|
+
}
|
|
74
|
+
function normalizeBaseUrl(baseUrl) {
|
|
75
|
+
return baseUrl.replace(/\/$/, "");
|
|
76
|
+
}
|
|
77
|
+
async function getJmpBlogPosts(config) {
|
|
78
|
+
const data = await getContent(
|
|
79
|
+
config,
|
|
80
|
+
"/api/content/blog"
|
|
81
|
+
);
|
|
82
|
+
return data.posts;
|
|
83
|
+
}
|
|
84
|
+
async function getJmpBlogPost(config, slug) {
|
|
85
|
+
try {
|
|
86
|
+
const data = await getContent(
|
|
87
|
+
config,
|
|
88
|
+
`/api/content/blog/${encodeURIComponent(slug)}`
|
|
89
|
+
);
|
|
90
|
+
return data.post;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (error instanceof Error && error.message.includes("HTTP 404")) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function getJmpFaqs(config) {
|
|
99
|
+
const data = await getContent(
|
|
100
|
+
config,
|
|
101
|
+
"/api/content/faqs"
|
|
102
|
+
);
|
|
103
|
+
return data.faqs;
|
|
104
|
+
}
|
|
105
|
+
async function getJmpTrustBundle(config) {
|
|
106
|
+
return getContent(config, "/api/content/trust");
|
|
107
|
+
}
|
|
108
|
+
async function getJmpSiteProfile(config) {
|
|
109
|
+
const data = await getContent(
|
|
110
|
+
config,
|
|
111
|
+
"/api/content/site"
|
|
112
|
+
);
|
|
113
|
+
return data.site;
|
|
114
|
+
}
|
|
115
|
+
async function getContent(config, path) {
|
|
116
|
+
const text = await getContentText(config, path);
|
|
117
|
+
try {
|
|
118
|
+
return JSON.parse(text);
|
|
119
|
+
} catch {
|
|
120
|
+
const baseUrl = normalizeBaseUrl(config.baseUrl);
|
|
121
|
+
const url = `${baseUrl}${path}`;
|
|
122
|
+
throw new Error(`JMP content HTTP 200 \u2014 ${url} returned invalid JSON.`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function getContentText(config, path) {
|
|
126
|
+
const baseUrl = normalizeBaseUrl(config.baseUrl);
|
|
127
|
+
const resolvedFetch = config.fetchImpl ?? (typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
|
|
128
|
+
if (!resolvedFetch) {
|
|
129
|
+
throw new Error("JMP content: fetch is not available; pass fetchImpl in config");
|
|
130
|
+
}
|
|
131
|
+
const url = `${baseUrl}${path}`;
|
|
132
|
+
if (config.debug) {
|
|
133
|
+
console.debug("[@jmp-technologies/analytics]", "GET", url);
|
|
134
|
+
}
|
|
135
|
+
const res = await resolvedFetch(url, {
|
|
136
|
+
method: "GET",
|
|
137
|
+
headers: {
|
|
138
|
+
"X-JMP-Tracking-Key": config.trackingKey
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
const text = await res.text();
|
|
142
|
+
if (!res.ok) {
|
|
143
|
+
if (responseLooksLikeHtml(text)) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`JMP content HTTP ${res.status} \u2014 ${url} returned HTML (not the JSON API). Set baseUrl to your JMP Analytics dashboard origin.`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
let parsed = {};
|
|
149
|
+
try {
|
|
150
|
+
parsed = JSON.parse(text);
|
|
151
|
+
} catch {
|
|
152
|
+
parsed = { error: text };
|
|
153
|
+
}
|
|
154
|
+
throw new Error(
|
|
155
|
+
[`JMP content HTTP ${res.status}`, parsed.error, parsed.hint].filter(Boolean).join(" \u2014 ")
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
return text;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/next/content.ts
|
|
162
|
+
var DEFAULT_EXTRA_KEYS = {
|
|
163
|
+
founderName: ["founder-name", "founder_name"],
|
|
164
|
+
dashboardDemoUrl: ["dashboard-demo-url", "sample-dashboard-url"],
|
|
165
|
+
profilePhotoSrc: ["profile-photo-url", "profile_photo_url"],
|
|
166
|
+
hours: ["hours", "business-hours"],
|
|
167
|
+
serviceArea: ["service-area", "service_area", "service-area-text"]
|
|
168
|
+
};
|
|
169
|
+
function sortBlogPosts(posts) {
|
|
170
|
+
return [...posts].sort((a, b) => {
|
|
171
|
+
const da = a.published_at ?? a.updated_at;
|
|
172
|
+
const db = b.published_at ?? b.updated_at;
|
|
173
|
+
return db.localeCompare(da);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
function sortFaqs(items) {
|
|
177
|
+
return [...items].sort((a, b) => a.sort_order - b.sort_order);
|
|
178
|
+
}
|
|
179
|
+
function readProfileExtra(profile, keys) {
|
|
180
|
+
for (const key of keys) {
|
|
181
|
+
const value = profile?.extras?.[key]?.trim();
|
|
182
|
+
if (value) {
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
function absoluteSiteAssetUrl(src) {
|
|
189
|
+
const trimmed = src.trim();
|
|
190
|
+
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
|
|
191
|
+
return trimmed;
|
|
192
|
+
}
|
|
193
|
+
const siteUrl = getJmpSiteDefinition()?.siteUrl?.replace(/\/$/, "");
|
|
194
|
+
if (!siteUrl) {
|
|
195
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
196
|
+
}
|
|
197
|
+
return `${siteUrl}${trimmed.startsWith("/") ? trimmed : `/${trimmed}`}`;
|
|
198
|
+
}
|
|
199
|
+
var loadSiteProfile = (0, import_react.cache)(async () => {
|
|
200
|
+
const config = getJmpContentConfig();
|
|
201
|
+
if (!config) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
return await getJmpSiteProfile(config);
|
|
206
|
+
} catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
function resolveSiteContact(profile) {
|
|
211
|
+
const site = getJmpSiteDefinition();
|
|
212
|
+
const fallback = site?.contactFallback ?? {};
|
|
213
|
+
const extraKeys = {
|
|
214
|
+
...DEFAULT_EXTRA_KEYS,
|
|
215
|
+
...site?.contactExtraKeys
|
|
216
|
+
};
|
|
217
|
+
const email = profile?.email?.trim() || fallback.email || "";
|
|
218
|
+
const linkedin = profile?.social?.linkedin?.trim() || fallback.linkedin || null;
|
|
219
|
+
const phone = profile?.phone?.trim() || fallback.phone || null;
|
|
220
|
+
return {
|
|
221
|
+
displayName: profile?.display_name?.trim() || profile?.name?.trim() || fallback.displayName || "",
|
|
222
|
+
founderName: readProfileExtra(profile, extraKeys.founderName ?? []) || fallback.founderName || "",
|
|
223
|
+
dashboardDemoUrl: readProfileExtra(profile, extraKeys.dashboardDemoUrl ?? []) || fallback.dashboardDemoUrl || "",
|
|
224
|
+
profilePhotoSrc: readProfileExtra(profile, extraKeys.profilePhotoSrc ?? []) || fallback.profilePhotoSrc || "",
|
|
225
|
+
email,
|
|
226
|
+
linkedin,
|
|
227
|
+
phone,
|
|
228
|
+
hours: readProfileExtra(profile, extraKeys.hours ?? []) || fallback.hours || null,
|
|
229
|
+
serviceArea: readProfileExtra(profile, extraKeys.serviceArea ?? []) || fallback.serviceArea || null,
|
|
230
|
+
privacyUrl: profile?.legal?.privacy_policy_url?.trim() || fallback.privacyUrl || "/privacy",
|
|
231
|
+
termsUrl: profile?.legal?.terms_url?.trim() || fallback.termsUrl || "/terms",
|
|
232
|
+
cta: {
|
|
233
|
+
label: profile?.cta?.label?.trim() || null,
|
|
234
|
+
url: profile?.cta?.url?.trim() || null
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
async function getResolvedSiteContact() {
|
|
239
|
+
const profile = await loadSiteProfile();
|
|
240
|
+
return resolveSiteContact(profile);
|
|
241
|
+
}
|
|
242
|
+
var loadBlogPosts = (0, import_react.cache)(async () => {
|
|
243
|
+
const c = getJmpContentConfig();
|
|
244
|
+
if (!c) {
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
const posts = await getJmpBlogPosts(c);
|
|
249
|
+
return sortBlogPosts(posts);
|
|
250
|
+
} catch {
|
|
251
|
+
return [];
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
var loadBlogPost = (0, import_react.cache)(async (slug) => {
|
|
255
|
+
const c = getJmpContentConfig();
|
|
256
|
+
if (!c) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
return await getJmpBlogPost(c, slug);
|
|
261
|
+
} catch {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
var loadFaqs = (0, import_react.cache)(async () => {
|
|
266
|
+
const c = getJmpContentConfig();
|
|
267
|
+
if (!c) {
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
const faqs = await getJmpFaqs(c);
|
|
272
|
+
return sortFaqs(faqs);
|
|
273
|
+
} catch {
|
|
274
|
+
return [];
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
async function loadFaqBySlug(slug) {
|
|
278
|
+
const normalized = slug.trim();
|
|
279
|
+
if (!normalized) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
const items = await loadFaqs();
|
|
283
|
+
return items.find((item) => item.slug === normalized || item.id === normalized) ?? null;
|
|
284
|
+
}
|
|
285
|
+
var loadTrustBundle = (0, import_react.cache)(async () => {
|
|
286
|
+
const c = getJmpContentConfig();
|
|
287
|
+
if (!c) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
return await getJmpTrustBundle(c);
|
|
292
|
+
} catch {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
function filterFeaturedTrustQuotes(testimonials, placement, limit = 2) {
|
|
297
|
+
return testimonials.filter((item) => {
|
|
298
|
+
if (!item.featured) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
const bucket = item.placement?.trim().toLowerCase();
|
|
302
|
+
if (placement === "reviews") {
|
|
303
|
+
return bucket === "reviews";
|
|
304
|
+
}
|
|
305
|
+
return !bucket || bucket === "testimonials";
|
|
306
|
+
}).sort((a, b) => a.sort_order - b.sort_order).slice(0, limit);
|
|
307
|
+
}
|
|
308
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
309
|
+
0 && (module.exports = {
|
|
310
|
+
absoluteSiteAssetUrl,
|
|
311
|
+
filterFeaturedTrustQuotes,
|
|
312
|
+
getJmpContentConfig,
|
|
313
|
+
getResolvedSiteContact,
|
|
314
|
+
loadBlogPost,
|
|
315
|
+
loadBlogPosts,
|
|
316
|
+
loadFaqBySlug,
|
|
317
|
+
loadFaqs,
|
|
318
|
+
loadSiteProfile,
|
|
319
|
+
loadTrustBundle,
|
|
320
|
+
resolveSiteContact
|
|
321
|
+
});
|
|
322
|
+
//# sourceMappingURL=content.cjs.map
|