@jmp-technologies/analytics 0.1.3 → 0.1.5
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 +129 -2
- package/dist/chunk-B4GXBHXP.js +388 -0
- package/dist/chunk-B4GXBHXP.js.map +1 -0
- package/dist/chunk-YVG5NXAH.js +35 -0
- package/dist/chunk-YVG5NXAH.js.map +1 -0
- package/dist/index.cjs +149 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +64 -1
- package/dist/index.d.ts +64 -1
- package/dist/index.js +26 -248
- package/dist/index.js.map +1 -1
- package/dist/next.cjs +365 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +14 -0
- package/dist/next.d.ts +14 -0
- package/dist/next.js +37 -0
- package/dist/next.js.map +1 -0
- package/dist/react.cjs +339 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +14 -0
- package/dist/react.d.ts +14 -0
- package/dist/react.js +11 -0
- package/dist/react.js.map +1 -0
- package/package.json +33 -5
package/dist/index.js
CHANGED
|
@@ -1,251 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return window.innerWidth < 768 ? "mobile" : "desktop";
|
|
16
|
-
}
|
|
17
|
-
function getOrCreateSessionId() {
|
|
18
|
-
if (typeof window === "undefined" || !globalThis.crypto?.randomUUID) {
|
|
19
|
-
return void 0;
|
|
20
|
-
}
|
|
21
|
-
try {
|
|
22
|
-
let id = sessionStorage.getItem(SESSION_STORAGE_KEY);
|
|
23
|
-
if (!id) {
|
|
24
|
-
id = crypto.randomUUID();
|
|
25
|
-
sessionStorage.setItem(SESSION_STORAGE_KEY, id);
|
|
26
|
-
}
|
|
27
|
-
return id;
|
|
28
|
-
} catch {
|
|
29
|
-
return void 0;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function createJmpAnalytics(config) {
|
|
33
|
-
const baseUrl = normalizeBaseUrl(config.baseUrl);
|
|
34
|
-
const resolvedFetch = config.fetchImpl ?? (typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
|
|
35
|
-
if (!resolvedFetch) {
|
|
36
|
-
throw new Error(
|
|
37
|
-
"createJmpAnalytics: fetch is not available; pass fetchImpl in config"
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
const fetchFn = resolvedFetch;
|
|
41
|
-
let lastDedupePath = "";
|
|
42
|
-
let lastDedupeAt = 0;
|
|
43
|
-
async function send(body) {
|
|
44
|
-
const url = `${baseUrl}/api/events`;
|
|
45
|
-
const headers = {
|
|
46
|
-
"Content-Type": "application/json",
|
|
47
|
-
"X-JMP-Tracking-Key": config.trackingKey
|
|
48
|
-
};
|
|
49
|
-
const sid = getOrCreateSessionId();
|
|
50
|
-
const payload = { ...body, ...sid ? { sessionId: sid } : {} };
|
|
51
|
-
if (config.debug) {
|
|
52
|
-
console.debug("[@jmp-technologies/analytics]", "POST", url, payload);
|
|
53
|
-
}
|
|
54
|
-
const res = await fetchFn(url, {
|
|
55
|
-
method: "POST",
|
|
56
|
-
headers,
|
|
57
|
-
body: JSON.stringify(payload),
|
|
58
|
-
keepalive: true
|
|
59
|
-
});
|
|
60
|
-
if (!res.ok) {
|
|
61
|
-
const text = await res.text();
|
|
62
|
-
if (responseLooksLikeHtml(text)) {
|
|
63
|
-
throw new Error(
|
|
64
|
-
`JMP Analytics HTTP ${res.status} \u2014 ${url} returned HTML (not the JSON API). Set baseUrl / NEXT_PUBLIC_JMP_ANALYTICS_URL to your JMP Analytics dashboard origin (the deployment that includes POST /api/events), not a marketing or client site unless that codebase ships the same API route.`
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
let parsed = {};
|
|
68
|
-
try {
|
|
69
|
-
parsed = JSON.parse(text);
|
|
70
|
-
} catch {
|
|
71
|
-
parsed = { error: text };
|
|
72
|
-
}
|
|
73
|
-
const detail = parsed.error ?? text;
|
|
74
|
-
const segments = [
|
|
75
|
-
`JMP Analytics HTTP ${res.status}`,
|
|
76
|
-
parsed.code ? `[${parsed.code}]` : null,
|
|
77
|
-
detail,
|
|
78
|
-
parsed.hint ? `Hint: ${parsed.hint}` : null,
|
|
79
|
-
parsed.expectedDomain ? `Domain in JMP admin: ${parsed.expectedDomain}` : null
|
|
80
|
-
].filter(Boolean);
|
|
81
|
-
const message = segments.join(" \u2014 ");
|
|
82
|
-
if (config.debug) {
|
|
83
|
-
console.debug("[@jmp-technologies/analytics]", "error response", {
|
|
84
|
-
status: res.status,
|
|
85
|
-
...parsed,
|
|
86
|
-
raw: text
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
throw new Error(message);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
async function sendLead(body) {
|
|
93
|
-
const url = `${baseUrl}/api/leads`;
|
|
94
|
-
const headers = {
|
|
95
|
-
"Content-Type": "application/json",
|
|
96
|
-
"X-JMP-Tracking-Key": config.trackingKey
|
|
97
|
-
};
|
|
98
|
-
const sid = getOrCreateSessionId();
|
|
99
|
-
const payload = { ...body, ...sid ? { sessionId: sid } : {} };
|
|
100
|
-
if (config.debug) {
|
|
101
|
-
console.debug("[@jmp-technologies/analytics]", "POST", url, payload);
|
|
102
|
-
}
|
|
103
|
-
const res = await fetchFn(url, {
|
|
104
|
-
method: "POST",
|
|
105
|
-
headers,
|
|
106
|
-
body: JSON.stringify(payload),
|
|
107
|
-
keepalive: true
|
|
108
|
-
});
|
|
109
|
-
if (!res.ok) {
|
|
110
|
-
const text = await res.text();
|
|
111
|
-
if (responseLooksLikeHtml(text)) {
|
|
112
|
-
throw new Error(
|
|
113
|
-
`JMP Analytics HTTP ${res.status} \u2014 ${url} returned HTML (not the JSON API). Set baseUrl / NEXT_PUBLIC_JMP_ANALYTICS_URL to your JMP Analytics dashboard origin (the deployment that includes POST /api/leads).`
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
let parsed = {};
|
|
117
|
-
try {
|
|
118
|
-
parsed = JSON.parse(text);
|
|
119
|
-
} catch {
|
|
120
|
-
parsed = { error: text };
|
|
121
|
-
}
|
|
122
|
-
const detail = parsed.error ?? text;
|
|
123
|
-
const segments = [
|
|
124
|
-
`JMP Analytics HTTP ${res.status}`,
|
|
125
|
-
parsed.code ? `[${parsed.code}]` : null,
|
|
126
|
-
detail,
|
|
127
|
-
parsed.hint ? `Hint: ${parsed.hint}` : null,
|
|
128
|
-
parsed.expectedDomain ? `Domain in JMP admin: ${parsed.expectedDomain}` : null
|
|
129
|
-
].filter(Boolean);
|
|
130
|
-
const message = segments.join(" \u2014 ");
|
|
131
|
-
if (config.debug) {
|
|
132
|
-
console.debug("[@jmp-technologies/analytics]", "error response", {
|
|
133
|
-
status: res.status,
|
|
134
|
-
...parsed,
|
|
135
|
-
raw: text
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
throw new Error(message);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
function shouldDedupePageView(path) {
|
|
142
|
-
const ms = config.pageViewDedupeMs ?? 0;
|
|
143
|
-
if (ms <= 0 || typeof window === "undefined") {
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
const now = Date.now();
|
|
147
|
-
if (path === lastDedupePath && now - lastDedupeAt < ms) {
|
|
148
|
-
return true;
|
|
149
|
-
}
|
|
150
|
-
lastDedupePath = path;
|
|
151
|
-
lastDedupeAt = now;
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
function trackPageView(options) {
|
|
155
|
-
const path = options?.path ?? (typeof window !== "undefined" ? window.location.pathname : "/");
|
|
156
|
-
if (shouldDedupePageView(path)) {
|
|
157
|
-
return Promise.resolve();
|
|
158
|
-
}
|
|
159
|
-
return send({
|
|
160
|
-
type: "page_view",
|
|
161
|
-
path: path || "/",
|
|
162
|
-
source: options?.source ?? DEFAULT_SOURCE,
|
|
163
|
-
device: options?.device ?? inferDevice(),
|
|
164
|
-
country: options?.country,
|
|
165
|
-
metadata: options?.metadata && typeof options.metadata === "object" ? options.metadata : {}
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
function trackFormSubmit(path, metadata) {
|
|
169
|
-
return send({
|
|
170
|
-
type: "form_submit",
|
|
171
|
-
path: path || "/",
|
|
172
|
-
metadata: metadata ?? {}
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
function trackCallClick(path, metadata) {
|
|
176
|
-
return send({
|
|
177
|
-
type: "call_click",
|
|
178
|
-
path: path || "/",
|
|
179
|
-
metadata: metadata ?? {}
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
function trackLead(options) {
|
|
183
|
-
const path = options?.path ?? (typeof window !== "undefined" ? window.location.pathname : "/");
|
|
184
|
-
const body = { path: path || "/" };
|
|
185
|
-
if (options?.name) body.name = options.name;
|
|
186
|
-
if (options?.email) body.email = options.email;
|
|
187
|
-
if (options?.phone) body.phone = options.phone;
|
|
188
|
-
if (options?.message) body.message = options.message;
|
|
189
|
-
if (options?.sessionId) body.sessionId = options.sessionId;
|
|
190
|
-
return sendLead(body);
|
|
191
|
-
}
|
|
192
|
-
function track(type, payload) {
|
|
193
|
-
if (type === "page_view") {
|
|
194
|
-
return trackPageView({
|
|
195
|
-
path: payload.path,
|
|
196
|
-
source: payload.source,
|
|
197
|
-
device: payload.device,
|
|
198
|
-
country: payload.country,
|
|
199
|
-
metadata: payload.metadata
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
if (type === "form_submit") {
|
|
203
|
-
return trackFormSubmit(payload.path, payload.metadata);
|
|
204
|
-
}
|
|
205
|
-
return trackCallClick(payload.path, payload.metadata);
|
|
206
|
-
}
|
|
207
|
-
function subscribeToSpaNavigation() {
|
|
208
|
-
if (typeof window === "undefined") {
|
|
209
|
-
return () => {
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
let lastPath = window.location.pathname;
|
|
213
|
-
const run = () => {
|
|
214
|
-
const path = window.location.pathname;
|
|
215
|
-
if (path !== lastPath) {
|
|
216
|
-
lastPath = path;
|
|
217
|
-
void trackPageView({ path });
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
window.addEventListener("popstate", run);
|
|
221
|
-
const push = history.pushState.bind(history);
|
|
222
|
-
const replace = history.replaceState.bind(history);
|
|
223
|
-
history.pushState = (...args) => {
|
|
224
|
-
const r = push(...args);
|
|
225
|
-
queueMicrotask(run);
|
|
226
|
-
return r;
|
|
227
|
-
};
|
|
228
|
-
history.replaceState = (...args) => {
|
|
229
|
-
const r = replace(...args);
|
|
230
|
-
queueMicrotask(run);
|
|
231
|
-
return r;
|
|
232
|
-
};
|
|
233
|
-
return () => {
|
|
234
|
-
window.removeEventListener("popstate", run);
|
|
235
|
-
history.pushState = push;
|
|
236
|
-
history.replaceState = replace;
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
return {
|
|
240
|
-
track,
|
|
241
|
-
trackPageView,
|
|
242
|
-
trackFormSubmit,
|
|
243
|
-
trackCallClick,
|
|
244
|
-
trackLead,
|
|
245
|
-
subscribeToSpaNavigation
|
|
246
|
-
};
|
|
247
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,
|
|
3
|
+
createJmpAnalytics,
|
|
4
|
+
createJmpContentClient,
|
|
5
|
+
createJmpContentClientFromServerEnv,
|
|
6
|
+
getJmpBlogPost,
|
|
7
|
+
getJmpBlogPosts,
|
|
8
|
+
getJmpBrowserAnalyticsConfigFromEnv,
|
|
9
|
+
getJmpFaqs,
|
|
10
|
+
getJmpServerContentConfigFromEnv,
|
|
11
|
+
getOrCreateJmpAnalyticsFromBrowserEnv,
|
|
12
|
+
isJmpBrowserAnalyticsEnvConfigured,
|
|
13
|
+
isJmpServerContentEnvConfigured
|
|
14
|
+
} from "./chunk-B4GXBHXP.js";
|
|
248
15
|
export {
|
|
249
|
-
|
|
16
|
+
DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,
|
|
17
|
+
createJmpAnalytics,
|
|
18
|
+
createJmpContentClient,
|
|
19
|
+
createJmpContentClientFromServerEnv,
|
|
20
|
+
getJmpBlogPost,
|
|
21
|
+
getJmpBlogPosts,
|
|
22
|
+
getJmpBrowserAnalyticsConfigFromEnv,
|
|
23
|
+
getJmpFaqs,
|
|
24
|
+
getJmpServerContentConfigFromEnv,
|
|
25
|
+
getOrCreateJmpAnalyticsFromBrowserEnv,
|
|
26
|
+
isJmpBrowserAnalyticsEnvConfigured,
|
|
27
|
+
isJmpServerContentEnvConfigured
|
|
250
28
|
};
|
|
251
29
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export type JmpEventType = \"page_view\" | \"form_submit\" | \"call_click\"\n\nexport type JmpAnalyticsConfig = {\n /** Dashboard origin, e.g. https://analytics.example.com (no trailing slash) */\n baseUrl: string\n /** Per-website key from JMP admin */\n trackingKey: string\n /** Log requests to console */\n debug?: boolean\n /** Override fetch (tests, React Native) */\n fetchImpl?: typeof fetch\n /**\n * Suppress duplicate `page_view` for the same path within this window (ms).\n * Helps with React Strict Mode double-mount; typical: 500–1000.\n */\n pageViewDedupeMs?: number\n}\n\nexport type TrackPageViewOptions = {\n path?: string\n source?: string\n device?: string\n country?: string\n metadata?: Record<string, unknown>\n}\n\nexport type TrackLeadOptions = {\n /** Path where the lead was submitted, e.g. /contact */\n path?: string\n name?: string\n email?: string\n phone?: string\n message?: string\n /**\n * Optional session id (from createJmpAnalytics internal session) if your app wants to pass it explicitly.\n * Most callers should omit this.\n */\n sessionId?: string\n}\n\nconst DEFAULT_SOURCE = \"direct\"\nconst SESSION_STORAGE_KEY = \"jmp_analytics_sid\"\n\n/** CDN / Next.js often return HTML error pages; API errors are JSON */\nfunction responseLooksLikeHtml(body: string) {\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\nfunction normalizeBaseUrl(baseUrl: string) {\n return baseUrl.replace(/\\/$/, \"\")\n}\n\nfunction inferDevice(): string {\n if (typeof window === \"undefined\") {\n return \"unknown\"\n }\n return window.innerWidth < 768 ? \"mobile\" : \"desktop\"\n}\n\n/** Stable per-tab session id for bounce and unique session metrics (server-stored). */\nfunction getOrCreateSessionId(): string | undefined {\n if (typeof window === \"undefined\" || !globalThis.crypto?.randomUUID) {\n return undefined\n }\n try {\n let id = sessionStorage.getItem(SESSION_STORAGE_KEY)\n if (!id) {\n id = crypto.randomUUID()\n sessionStorage.setItem(SESSION_STORAGE_KEY, id)\n }\n return id\n } catch {\n return undefined\n }\n}\n\n/**\n * Create a tracker instance. Safe to call once per app and reuse.\n */\nexport function createJmpAnalytics(config: JmpAnalyticsConfig) {\n const baseUrl = normalizeBaseUrl(config.baseUrl)\n const resolvedFetch =\n config.fetchImpl ??\n (typeof globalThis !== \"undefined\" && globalThis.fetch\n ? globalThis.fetch.bind(globalThis)\n : undefined)\n\n if (!resolvedFetch) {\n throw new Error(\n \"createJmpAnalytics: fetch is not available; pass fetchImpl in config\"\n )\n }\n\n const fetchFn: typeof fetch = resolvedFetch\n\n let lastDedupePath = \"\"\n let lastDedupeAt = 0\n\n async function send(body: Record<string, unknown>) {\n const url = `${baseUrl}/api/events`\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-JMP-Tracking-Key\": config.trackingKey,\n }\n\n const sid = getOrCreateSessionId()\n const payload = { ...body, ...(sid ? { sessionId: sid } : {}) }\n\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"POST\", url, payload)\n }\n\n const res = await fetchFn(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n keepalive: true,\n })\n\n if (!res.ok) {\n const text = await res.text()\n if (responseLooksLikeHtml(text)) {\n throw new Error(\n `JMP Analytics HTTP ${res.status} — ${url} returned HTML (not the JSON API). ` +\n `Set baseUrl / NEXT_PUBLIC_JMP_ANALYTICS_URL to your JMP Analytics dashboard origin (the deployment that includes POST /api/events), ` +\n `not a marketing or client site unless that codebase ships the same API route.`\n )\n }\n let parsed: {\n error?: string\n code?: string\n hint?: string\n expectedDomain?: string\n } = {}\n try {\n parsed = JSON.parse(text) as typeof parsed\n } catch {\n parsed = { error: text }\n }\n const detail = parsed.error ?? text\n const segments = [\n `JMP Analytics HTTP ${res.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 ].filter(Boolean) as string[]\n const message = segments.join(\" — \")\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"error response\", {\n status: res.status,\n ...parsed,\n raw: text,\n })\n }\n throw new Error(message)\n }\n }\n\n async function sendLead(body: Record<string, unknown>) {\n const url = `${baseUrl}/api/leads`\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-JMP-Tracking-Key\": config.trackingKey,\n }\n\n const sid = getOrCreateSessionId()\n const payload = { ...body, ...(sid ? { sessionId: sid } : {}) }\n\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"POST\", url, payload)\n }\n\n const res = await fetchFn(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n keepalive: true,\n })\n\n if (!res.ok) {\n const text = await res.text()\n if (responseLooksLikeHtml(text)) {\n throw new Error(\n `JMP Analytics HTTP ${res.status} — ${url} returned HTML (not the JSON API). ` +\n `Set baseUrl / NEXT_PUBLIC_JMP_ANALYTICS_URL to your JMP Analytics dashboard origin (the deployment that includes POST /api/leads).`\n )\n }\n let parsed: {\n error?: string\n code?: string\n hint?: string\n expectedDomain?: string\n } = {}\n try {\n parsed = JSON.parse(text) as typeof parsed\n } catch {\n parsed = { error: text }\n }\n const detail = parsed.error ?? text\n const segments = [\n `JMP Analytics HTTP ${res.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 ].filter(Boolean) as string[]\n const message = segments.join(\" — \")\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"error response\", {\n status: res.status,\n ...parsed,\n raw: text,\n })\n }\n throw new Error(message)\n }\n }\n\n function shouldDedupePageView(path: string): boolean {\n const ms = config.pageViewDedupeMs ?? 0\n if (ms <= 0 || typeof window === \"undefined\") {\n return false\n }\n const now = Date.now()\n if (path === lastDedupePath && now - lastDedupeAt < ms) {\n return true\n }\n lastDedupePath = path\n lastDedupeAt = now\n return false\n }\n\n function trackPageView(options?: TrackPageViewOptions) {\n const path =\n options?.path ??\n (typeof window !== \"undefined\" ? window.location.pathname : \"/\")\n\n if (shouldDedupePageView(path)) {\n return Promise.resolve()\n }\n\n return send({\n type: \"page_view\",\n path: path || \"/\",\n source: options?.source ?? DEFAULT_SOURCE,\n device: options?.device ?? inferDevice(),\n country: options?.country,\n metadata:\n options?.metadata && typeof options.metadata === \"object\"\n ? options.metadata\n : {},\n })\n }\n\n function trackFormSubmit(\n path: string,\n metadata?: Record<string, unknown>\n ) {\n return send({\n type: \"form_submit\",\n path: path || \"/\",\n metadata: metadata ?? {},\n })\n }\n\n function trackCallClick(\n path: string,\n metadata?: Record<string, unknown>\n ) {\n return send({\n type: \"call_click\",\n path: path || \"/\",\n metadata: metadata ?? {},\n })\n }\n\n function trackLead(options?: TrackLeadOptions) {\n const path =\n options?.path ??\n (typeof window !== \"undefined\" ? window.location.pathname : \"/\")\n\n const body: Record<string, unknown> = { path: path || \"/\" }\n if (options?.name) body.name = options.name\n if (options?.email) body.email = options.email\n if (options?.phone) body.phone = options.phone\n if (options?.message) body.message = options.message\n if (options?.sessionId) body.sessionId = options.sessionId\n\n return sendLead(body)\n }\n\n function track(\n type: JmpEventType,\n payload: {\n path: string\n metadata?: Record<string, unknown>\n source?: string\n device?: string\n country?: string\n }\n ) {\n if (type === \"page_view\") {\n return trackPageView({\n path: payload.path,\n source: payload.source,\n device: payload.device,\n country: payload.country,\n metadata: payload.metadata,\n })\n }\n if (type === \"form_submit\") {\n return trackFormSubmit(payload.path, payload.metadata)\n }\n return trackCallClick(payload.path, payload.metadata)\n }\n\n /**\n * Fire `page_view` on browser back/forward and when `history.pushState` /\n * `replaceState` are used (many SPAs). Call once after creating the tracker.\n * Returns an unsubscribe function.\n */\n function subscribeToSpaNavigation(): () => void {\n if (typeof window === \"undefined\") {\n return () => {}\n }\n\n let lastPath = window.location.pathname\n\n const run = () => {\n const path = window.location.pathname\n if (path !== lastPath) {\n lastPath = path\n void trackPageView({ path })\n }\n }\n\n window.addEventListener(\"popstate\", run)\n\n const push = history.pushState.bind(history)\n const replace = history.replaceState.bind(history)\n\n history.pushState = (...args: Parameters<History[\"pushState\"]>) => {\n const r = push(...args)\n queueMicrotask(run)\n return r\n }\n history.replaceState = (...args: Parameters<History[\"replaceState\"]>) => {\n const r = replace(...args)\n queueMicrotask(run)\n return r\n }\n\n return () => {\n window.removeEventListener(\"popstate\", run)\n history.pushState = push\n history.replaceState = replace\n }\n }\n\n return {\n track,\n trackPageView,\n trackFormSubmit,\n trackCallClick,\n trackLead,\n subscribeToSpaNavigation,\n }\n}\n\nexport type JmpAnalytics = ReturnType<typeof createJmpAnalytics>\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/next.cjs
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/next.tsx
|
|
22
|
+
var next_exports = {};
|
|
23
|
+
__export(next_exports, {
|
|
24
|
+
JmpNextAnalytics: () => JmpNextAnalytics,
|
|
25
|
+
JmpNextPageViewTracker: () => JmpNextPageViewTracker,
|
|
26
|
+
JmpTrackedTelLink: () => JmpTrackedTelLink,
|
|
27
|
+
useJmpAnalytics: () => useJmpAnalytics
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(next_exports);
|
|
30
|
+
var import_react2 = require("react");
|
|
31
|
+
var import_navigation = require("next/navigation");
|
|
32
|
+
|
|
33
|
+
// src/index.ts
|
|
34
|
+
var DEFAULT_SOURCE = "direct";
|
|
35
|
+
var SESSION_STORAGE_KEY = "jmp_analytics_sid";
|
|
36
|
+
function responseLooksLikeHtml(body) {
|
|
37
|
+
const s = body.trimStart();
|
|
38
|
+
return s.startsWith("<!") || s.toLowerCase().startsWith("<html") || s.startsWith("<") && /<html[\s>]/i.test(s.slice(0, 500));
|
|
39
|
+
}
|
|
40
|
+
function normalizeBaseUrl(baseUrl) {
|
|
41
|
+
return baseUrl.replace(/\/$/, "");
|
|
42
|
+
}
|
|
43
|
+
function inferDevice() {
|
|
44
|
+
if (typeof window === "undefined") {
|
|
45
|
+
return "unknown";
|
|
46
|
+
}
|
|
47
|
+
return window.innerWidth < 768 ? "mobile" : "desktop";
|
|
48
|
+
}
|
|
49
|
+
function getOrCreateSessionId() {
|
|
50
|
+
if (typeof window === "undefined" || !globalThis.crypto?.randomUUID) {
|
|
51
|
+
return void 0;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
let id = sessionStorage.getItem(SESSION_STORAGE_KEY);
|
|
55
|
+
if (!id) {
|
|
56
|
+
id = crypto.randomUUID();
|
|
57
|
+
sessionStorage.setItem(SESSION_STORAGE_KEY, id);
|
|
58
|
+
}
|
|
59
|
+
return id;
|
|
60
|
+
} catch {
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function createJmpAnalytics(config) {
|
|
65
|
+
const baseUrl = normalizeBaseUrl(config.baseUrl);
|
|
66
|
+
const resolvedFetch = config.fetchImpl ?? (typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
|
|
67
|
+
if (!resolvedFetch) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
"createJmpAnalytics: fetch is not available; pass fetchImpl in config"
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const fetchFn = resolvedFetch;
|
|
73
|
+
let lastDedupePath = "";
|
|
74
|
+
let lastDedupeAt = 0;
|
|
75
|
+
async function send(body) {
|
|
76
|
+
const url = `${baseUrl}/api/events`;
|
|
77
|
+
const headers = {
|
|
78
|
+
"Content-Type": "application/json",
|
|
79
|
+
"X-JMP-Tracking-Key": config.trackingKey
|
|
80
|
+
};
|
|
81
|
+
const sid = getOrCreateSessionId();
|
|
82
|
+
const payload = { ...body, ...sid ? { sessionId: sid } : {} };
|
|
83
|
+
if (config.debug) {
|
|
84
|
+
console.debug("[@jmp-technologies/analytics]", "POST", url, payload);
|
|
85
|
+
}
|
|
86
|
+
const res = await fetchFn(url, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers,
|
|
89
|
+
body: JSON.stringify(payload),
|
|
90
|
+
keepalive: true
|
|
91
|
+
});
|
|
92
|
+
if (!res.ok) {
|
|
93
|
+
const text = await res.text();
|
|
94
|
+
if (responseLooksLikeHtml(text)) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`JMP Analytics HTTP ${res.status} \u2014 ${url} returned HTML (not the JSON API). Set baseUrl / NEXT_PUBLIC_JMP_ANALYTICS_URL to your JMP Analytics dashboard origin (the deployment that includes POST /api/events), not a marketing or client site unless that codebase ships the same API route.`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
let parsed = {};
|
|
100
|
+
try {
|
|
101
|
+
parsed = JSON.parse(text);
|
|
102
|
+
} catch {
|
|
103
|
+
parsed = { error: text };
|
|
104
|
+
}
|
|
105
|
+
const detail = parsed.error ?? text;
|
|
106
|
+
const segments = [
|
|
107
|
+
`JMP Analytics HTTP ${res.status}`,
|
|
108
|
+
parsed.code ? `[${parsed.code}]` : null,
|
|
109
|
+
detail,
|
|
110
|
+
parsed.hint ? `Hint: ${parsed.hint}` : null,
|
|
111
|
+
parsed.expectedDomain ? `Domain in JMP admin: ${parsed.expectedDomain}` : null
|
|
112
|
+
].filter(Boolean);
|
|
113
|
+
const message = segments.join(" \u2014 ");
|
|
114
|
+
if (config.debug) {
|
|
115
|
+
console.debug("[@jmp-technologies/analytics]", "error response", {
|
|
116
|
+
status: res.status,
|
|
117
|
+
...parsed,
|
|
118
|
+
raw: text
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
throw new Error(message);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function sendLead(body) {
|
|
125
|
+
const url = `${baseUrl}/api/leads`;
|
|
126
|
+
const headers = {
|
|
127
|
+
"Content-Type": "application/json",
|
|
128
|
+
"X-JMP-Tracking-Key": config.trackingKey
|
|
129
|
+
};
|
|
130
|
+
const sid = getOrCreateSessionId();
|
|
131
|
+
const payload = { ...body, ...sid ? { sessionId: sid } : {} };
|
|
132
|
+
if (config.debug) {
|
|
133
|
+
console.debug("[@jmp-technologies/analytics]", "POST", url, payload);
|
|
134
|
+
}
|
|
135
|
+
const res = await fetchFn(url, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers,
|
|
138
|
+
body: JSON.stringify(payload),
|
|
139
|
+
keepalive: true
|
|
140
|
+
});
|
|
141
|
+
if (!res.ok) {
|
|
142
|
+
const text = await res.text();
|
|
143
|
+
if (responseLooksLikeHtml(text)) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`JMP Analytics HTTP ${res.status} \u2014 ${url} returned HTML (not the JSON API). Set baseUrl / NEXT_PUBLIC_JMP_ANALYTICS_URL to your JMP Analytics dashboard origin (the deployment that includes POST /api/leads).`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
let parsed = {};
|
|
149
|
+
try {
|
|
150
|
+
parsed = JSON.parse(text);
|
|
151
|
+
} catch {
|
|
152
|
+
parsed = { error: text };
|
|
153
|
+
}
|
|
154
|
+
const detail = parsed.error ?? text;
|
|
155
|
+
const segments = [
|
|
156
|
+
`JMP Analytics HTTP ${res.status}`,
|
|
157
|
+
parsed.code ? `[${parsed.code}]` : null,
|
|
158
|
+
detail,
|
|
159
|
+
parsed.hint ? `Hint: ${parsed.hint}` : null,
|
|
160
|
+
parsed.expectedDomain ? `Domain in JMP admin: ${parsed.expectedDomain}` : null
|
|
161
|
+
].filter(Boolean);
|
|
162
|
+
const message = segments.join(" \u2014 ");
|
|
163
|
+
if (config.debug) {
|
|
164
|
+
console.debug("[@jmp-technologies/analytics]", "error response", {
|
|
165
|
+
status: res.status,
|
|
166
|
+
...parsed,
|
|
167
|
+
raw: text
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
throw new Error(message);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function shouldDedupePageView(path) {
|
|
174
|
+
const ms = config.pageViewDedupeMs ?? 0;
|
|
175
|
+
if (ms <= 0 || typeof window === "undefined") {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
const now = Date.now();
|
|
179
|
+
if (path === lastDedupePath && now - lastDedupeAt < ms) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
lastDedupePath = path;
|
|
183
|
+
lastDedupeAt = now;
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
function trackPageView(options) {
|
|
187
|
+
const path = options?.path ?? (typeof window !== "undefined" ? window.location.pathname : "/");
|
|
188
|
+
if (shouldDedupePageView(path)) {
|
|
189
|
+
return Promise.resolve();
|
|
190
|
+
}
|
|
191
|
+
return send({
|
|
192
|
+
type: "page_view",
|
|
193
|
+
path: path || "/",
|
|
194
|
+
source: options?.source ?? DEFAULT_SOURCE,
|
|
195
|
+
device: options?.device ?? inferDevice(),
|
|
196
|
+
country: options?.country,
|
|
197
|
+
metadata: options?.metadata && typeof options.metadata === "object" ? options.metadata : {}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function trackFormSubmit(path, metadata) {
|
|
201
|
+
return send({
|
|
202
|
+
type: "form_submit",
|
|
203
|
+
path: path || "/",
|
|
204
|
+
metadata: metadata ?? {}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function trackCallClick(path, metadata) {
|
|
208
|
+
return send({
|
|
209
|
+
type: "call_click",
|
|
210
|
+
path: path || "/",
|
|
211
|
+
metadata: metadata ?? {}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
function trackLead(options) {
|
|
215
|
+
const path = options?.path ?? (typeof window !== "undefined" ? window.location.pathname : "/");
|
|
216
|
+
const body = { path: path || "/" };
|
|
217
|
+
if (options?.name) body.name = options.name;
|
|
218
|
+
if (options?.email) body.email = options.email;
|
|
219
|
+
if (options?.phone) body.phone = options.phone;
|
|
220
|
+
if (options?.company) body.company = options.company;
|
|
221
|
+
if (options?.website) body.website = options.website;
|
|
222
|
+
if (options?.subject) body.subject = options.subject;
|
|
223
|
+
if (options?.message) body.message = options.message;
|
|
224
|
+
if (options?.metadata && typeof options.metadata === "object") {
|
|
225
|
+
body.metadata = options.metadata;
|
|
226
|
+
}
|
|
227
|
+
if (options?.sessionId) body.sessionId = options.sessionId;
|
|
228
|
+
return sendLead(body);
|
|
229
|
+
}
|
|
230
|
+
function track(type, payload) {
|
|
231
|
+
if (type === "page_view") {
|
|
232
|
+
return trackPageView({
|
|
233
|
+
path: payload.path,
|
|
234
|
+
source: payload.source,
|
|
235
|
+
device: payload.device,
|
|
236
|
+
country: payload.country,
|
|
237
|
+
metadata: payload.metadata
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
if (type === "form_submit") {
|
|
241
|
+
return trackFormSubmit(payload.path, payload.metadata);
|
|
242
|
+
}
|
|
243
|
+
return trackCallClick(payload.path, payload.metadata);
|
|
244
|
+
}
|
|
245
|
+
function subscribeToSpaNavigation() {
|
|
246
|
+
if (typeof window === "undefined") {
|
|
247
|
+
return () => {
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
let lastPath = window.location.pathname;
|
|
251
|
+
const run = () => {
|
|
252
|
+
const path = window.location.pathname;
|
|
253
|
+
if (path !== lastPath) {
|
|
254
|
+
lastPath = path;
|
|
255
|
+
void trackPageView({ path });
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
window.addEventListener("popstate", run);
|
|
259
|
+
const push = history.pushState.bind(history);
|
|
260
|
+
const replace = history.replaceState.bind(history);
|
|
261
|
+
history.pushState = (...args) => {
|
|
262
|
+
const r = push(...args);
|
|
263
|
+
queueMicrotask(run);
|
|
264
|
+
return r;
|
|
265
|
+
};
|
|
266
|
+
history.replaceState = (...args) => {
|
|
267
|
+
const r = replace(...args);
|
|
268
|
+
queueMicrotask(run);
|
|
269
|
+
return r;
|
|
270
|
+
};
|
|
271
|
+
return () => {
|
|
272
|
+
window.removeEventListener("popstate", run);
|
|
273
|
+
history.pushState = push;
|
|
274
|
+
history.replaceState = replace;
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
track,
|
|
279
|
+
trackPageView,
|
|
280
|
+
trackFormSubmit,
|
|
281
|
+
trackCallClick,
|
|
282
|
+
trackLead,
|
|
283
|
+
subscribeToSpaNavigation
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
var DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS = 800;
|
|
287
|
+
function readProcessEnv(key) {
|
|
288
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
289
|
+
const v = process.env[key];
|
|
290
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
291
|
+
}
|
|
292
|
+
function getJmpBrowserAnalyticsConfigFromEnv(overrides) {
|
|
293
|
+
const baseUrl = readProcessEnv("NEXT_PUBLIC_JMP_ANALYTICS_URL");
|
|
294
|
+
const trackingKey = readProcessEnv("NEXT_PUBLIC_JMP_TRACKING_KEY");
|
|
295
|
+
if (!baseUrl || !trackingKey) return null;
|
|
296
|
+
return {
|
|
297
|
+
baseUrl,
|
|
298
|
+
trackingKey,
|
|
299
|
+
pageViewDedupeMs: DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,
|
|
300
|
+
...overrides
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
var jmpAnalyticsBrowserSingleton = null;
|
|
304
|
+
function getOrCreateJmpAnalyticsFromBrowserEnv(overrides) {
|
|
305
|
+
if (typeof window === "undefined") return null;
|
|
306
|
+
if (jmpAnalyticsBrowserSingleton) return jmpAnalyticsBrowserSingleton;
|
|
307
|
+
const config = getJmpBrowserAnalyticsConfigFromEnv(overrides);
|
|
308
|
+
if (!config) return null;
|
|
309
|
+
jmpAnalyticsBrowserSingleton = createJmpAnalytics(config);
|
|
310
|
+
return jmpAnalyticsBrowserSingleton;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/react.tsx
|
|
314
|
+
var import_react = require("react");
|
|
315
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
316
|
+
function useJmpAnalytics() {
|
|
317
|
+
return (0, import_react.useMemo)(() => getOrCreateJmpAnalyticsFromBrowserEnv(), []);
|
|
318
|
+
}
|
|
319
|
+
function JmpTrackedTelLink({
|
|
320
|
+
href,
|
|
321
|
+
onClick,
|
|
322
|
+
...rest
|
|
323
|
+
}) {
|
|
324
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
325
|
+
"a",
|
|
326
|
+
{
|
|
327
|
+
href,
|
|
328
|
+
...rest,
|
|
329
|
+
onClick: (e) => {
|
|
330
|
+
const client = getOrCreateJmpAnalyticsFromBrowserEnv();
|
|
331
|
+
const path = typeof window !== "undefined" ? window.location.pathname : "/";
|
|
332
|
+
void client?.trackCallClick(path);
|
|
333
|
+
onClick?.(e);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/next.tsx
|
|
340
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
341
|
+
function JmpNextPageViewTrackerInner() {
|
|
342
|
+
const pathname = (0, import_navigation.usePathname)();
|
|
343
|
+
const searchParams = (0, import_navigation.useSearchParams)();
|
|
344
|
+
const query = searchParams?.toString() ?? "";
|
|
345
|
+
const path = query ? `${pathname}?${query}` : pathname;
|
|
346
|
+
(0, import_react2.useEffect)(() => {
|
|
347
|
+
const client = getOrCreateJmpAnalyticsFromBrowserEnv();
|
|
348
|
+
void client?.trackPageView({ path });
|
|
349
|
+
}, [path]);
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
function JmpNextPageViewTracker() {
|
|
353
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react2.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JmpNextPageViewTrackerInner, {}) });
|
|
354
|
+
}
|
|
355
|
+
function JmpNextAnalytics() {
|
|
356
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JmpNextPageViewTracker, {});
|
|
357
|
+
}
|
|
358
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
359
|
+
0 && (module.exports = {
|
|
360
|
+
JmpNextAnalytics,
|
|
361
|
+
JmpNextPageViewTracker,
|
|
362
|
+
JmpTrackedTelLink,
|
|
363
|
+
useJmpAnalytics
|
|
364
|
+
});
|
|
365
|
+
//# sourceMappingURL=next.cjs.map
|