onedollarstats 0.0.4 → 0.0.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 +21 -12
- package/dist/index.js +1 -4
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
# OneDollarStats
|
|
2
1
|
|
|
3
|
-
[](https://www.npmjs.com/package/onedollarstats)
|
|
4
|
-
[](https://onedollarstats.com/home)
|
|
5
2
|
|
|
6
|
-
|
|
3
|
+
<h1 align="center">
|
|
4
|
+
OneDollarStats
|
|
5
|
+
</h1>
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
<div align="center">
|
|
8
|
+
<h3>$1.00/month web analytics 🚀</h3>
|
|
9
|
+
<a href="https://onedollarstats.com">Website</a> •
|
|
10
|
+
<a href="https://docs.onedollarstats.com/get-started">Documentation</a> •
|
|
11
|
+
<a href="https://discord.gg/55EjXsFUuf">Discord</a>
|
|
12
|
+
</div>
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
- Automatic event tracking on clicks of elements with data-s-event attributes
|
|
13
|
-
- Zero dependencies, easy to integrate
|
|
14
|
+
### What's onedollarstats package?
|
|
15
|
+
**OneDollarStats** is a lightweight, zero-dependency analytics tracker for client applications that automatically tracks pageviews, UTM parameters, and custom events with minimal setup. It supports client-side, server-side, and hash-based navigation, collects UTM parameters automatically, tracks clicks on elements with `data-s-event` attributes, and integrates effortlessly without any external dependencies.
|
|
14
16
|
|
|
15
17
|
## Installation
|
|
16
18
|
|
|
@@ -113,9 +115,16 @@ event("Purchase", "/product", { amount: 1, color: "green" });
|
|
|
113
115
|
- `pathOrProps` – Optional, **string** represents the path, **object** represents custom properties.
|
|
114
116
|
- `props` – Optional, properties if the second argument is a path string.
|
|
115
117
|
|
|
116
|
-
|
|
118
|
+
## Autocapture
|
|
119
|
+
**Page view events**
|
|
120
|
+
|
|
121
|
+
List of attributes for tags that allow modifying the sent page view:
|
|
122
|
+
|
|
123
|
+
- `data-s-path` – Optional. Specifies the path representing the page where the event occurred. This attribute should be set on the `<body>` tag.
|
|
124
|
+
|
|
125
|
+
- `data-s-view-props` – Optional. Defines additional properties to include with the page view event. All properties from elements on the page with this attribute will be collected and sent together.
|
|
117
126
|
|
|
118
|
-
|
|
127
|
+
**Click events**
|
|
119
128
|
|
|
120
129
|
Automatically capture clicks on elements using these HTML attributes:
|
|
121
130
|
|
|
@@ -123,4 +132,4 @@ Automatically capture clicks on elements using these HTML attributes:
|
|
|
123
132
|
- `data-s-event-path` Optional, the path representing the page where the event occurred
|
|
124
133
|
- `data-s-event-props` – Optional, properties to send with the event
|
|
125
134
|
|
|
126
|
-
For full details, see the [
|
|
135
|
+
For full details, see the [Documentation](https://docs.onedollarstats.com).
|
package/dist/index.js
CHANGED
|
@@ -87,10 +87,7 @@ var _AnalyticsTracker = class _AnalyticsTracker {
|
|
|
87
87
|
if (this.config.autocollect) this.setupAutocollect();
|
|
88
88
|
}
|
|
89
89
|
static getInstance(userConfig = {}) {
|
|
90
|
-
if (!isClient())
|
|
91
|
-
console.warn("[onedollarstats] Running in non-browser environment. Returning no-op instance.");
|
|
92
|
-
return new _AnalyticsTracker(userConfig);
|
|
93
|
-
}
|
|
90
|
+
if (!isClient()) return new _AnalyticsTracker(userConfig);
|
|
94
91
|
if (!_AnalyticsTracker.instance) {
|
|
95
92
|
_AnalyticsTracker.instance = new _AnalyticsTracker(userConfig);
|
|
96
93
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/utils/environment.ts", "../src/utils/parse-utm-params.ts", "../src/utils/props-parser.ts", "../src/utils/resolve-path.ts", "../src/utils/should-track.ts", "../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["export const getEnvironment = (): {\n isLocalhost: boolean;\n isHeadlessBrowser: boolean;\n} => ({\n isLocalhost: /^localhost$|^127(\\.[0-9]+){0,2}\\.[0-9]+$|^\\[::1?\\]$/.test(location.hostname) || location.protocol === \"file:\",\n isHeadlessBrowser: Boolean(\n window.navigator.webdriver ||\n (\"_phantom\" in window && window._phantom) ||\n (\"__nightmare\" in window && window.__nightmare) ||\n (\"Cypress\" in window && window.Cypress)\n )\n});\nexport const isClient = (): boolean => {\n try {\n // Basic checks for window and document\n if (typeof window === \"undefined\" || typeof document === \"undefined\") return false;\n\n // Check for navigator safely\n const ua = typeof navigator !== \"undefined\" ? navigator.userAgent : \"\";\n if (/node|jsdom/i.test(ua)) return false;\n return true;\n } catch {\n return false;\n }\n};\n", "export const parseUtmParams = (urlSearchParams: URLSearchParams) => {\n const utm: Record<string, string | string[]> = {};\n\n [\"utm_campaign\", \"utm_source\", \"utm_medium\", \"utm_term\", \"utm_content\"].forEach((key) => {\n const values = urlSearchParams.getAll(key);\n if (values.length === 1) {\n utm[key] = values[0];\n } else if (values.length > 1) {\n utm[key] = values; // store array if multiple values\n }\n });\n\n return utm;\n};\n", "export const parseProps = (propsString: string): Record<string, string> | undefined => {\n if (!propsString) return undefined;\n // \"key1=value1;key2=value2\"\n\n const splittedProps = propsString.split(\";\");\n const propsObj: Record<string, string> = {};\n\n for (const keyValueString of splittedProps) {\n const keyValuePair = keyValueString.split(\"=\").map((el) => el.trim());\n if (keyValuePair.length !== 2 || keyValuePair[0] === \"\" || keyValuePair[1] === \"\") continue;\n // @ts-ignore\n propsObj[keyValuePair[0]] = keyValuePair[1];\n }\n\n return Object.keys(propsObj).length === 0 ? undefined : propsObj;\n};\n", "export const resolvePath = (pathOrProps?: string): string => {\n if (pathOrProps) return pathOrProps;\n\n const sources = [\n { value: document.body?.getAttribute(\"data-s-path\"), name: \"data-s-path\" },\n { value: document.body?.getAttribute(\"data-s:path\"), name: \"data-s:path\" },\n { value: document.querySelector('meta[name=\"stonks-path\"]')?.getAttribute(\"content\"), name: \"meta[name='stonks-path']\" }\n ];\n\n // Only keep sources that actually exist\n const existing = sources.filter(({ value }) => value);\n\n if (existing.length > 1) {\n console.warn(\"[onedollarstats] Multiple path sources found. Using priority order:\", existing.map(({ name }) => name).join(\" > \"));\n }\n\n // Return first available value, fallback to location.pathname\n return existing[0]?.value ?? location.pathname;\n};\n", "import type { AnalyticsConfig } from \"../types\";\n\nconst matchesPattern = (path: string, pattern: string): boolean => {\n // Escape special regex characters except '*' which becomes '.*'\n const escaped = pattern.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(/\\*/g, \".*\");\n return new RegExp(`^${escaped}$`).test(path);\n};\n\nexport const shouldTrackPath = (path: string, config: Required<AnalyticsConfig>): boolean => {\n // Exclude pages first\n if (config.excludePages.some((pattern) => matchesPattern(path, pattern))) return false;\n // If includePages is defined, only allow matching paths\n if (config.includePages.length && !config.includePages.some((pattern) => matchesPattern(path, pattern))) return false;\n return true;\n};\n", "import type { AnalyticsConfig, BaseProps, BodyToSend, Event, ViewArguments } from \"./types\";\nimport { getEnvironment, isClient } from \"./utils/environment\";\nimport { parseUtmParams } from \"./utils/parse-utm-params\";\nimport { parseProps } from \"./utils/props-parser\";\nimport { resolvePath } from \"./utils/resolve-path\";\nimport { shouldTrackPath } from \"./utils/should-track\";\n\nconst defaultConfig: Required<AnalyticsConfig> = {\n trackLocalhostAs: null,\n collectorUrl: \"https://collector.onedollarstats.com/events\",\n hashRouting: false,\n autocollect: true,\n excludePages: [],\n includePages: []\n};\n\nclass AnalyticsTracker {\n private static instance: AnalyticsTracker | null = null;\n\n private autocollectSetupDone = false;\n private config: Required<AnalyticsConfig>;\n private lastPage: string | null = null;\n\n public static getInstance(userConfig: AnalyticsConfig = {}): AnalyticsTracker {\n if (!isClient()) {\n console.warn(\"[onedollarstats] Running in non-browser environment. Returning no-op instance.\");\n return new AnalyticsTracker(userConfig); // Fresh no-op instance for SSR\n }\n\n if (!AnalyticsTracker.instance) {\n AnalyticsTracker.instance = new AnalyticsTracker(userConfig);\n }\n return AnalyticsTracker.instance;\n }\n\n private constructor(userConfig: AnalyticsConfig = {}) {\n this.config = { ...defaultConfig, ...userConfig };\n\n // Skip setup in non-client environments\n if (!isClient()) return;\n\n // Auto-start autocollect\n if (this.config.autocollect) this.setupAutocollect();\n }\n\n private async sendWithBeaconOrFetch(stringifiedBody: string): Promise<void> {\n // First fallback: try sendBeacon\n if (navigator.sendBeacon?.(this.config.collectorUrl, stringifiedBody)) return;\n\n // Second fallback: use fetch() with keepalive\n fetch(this.config.collectorUrl, {\n method: \"POST\",\n body: stringifiedBody,\n headers: { \"Content-Type\": \"application/json\" },\n keepalive: true\n }).catch((err: Error) => console.error(\"[onedollarstats] fetch() failed:\", err.message));\n }\n\n // Handles localhost replacement, referrer, UTM parameters, and debug mode.\n // Uses img beacon then `navigator.sendBeacon` if available, otherwise falls back to `fetch`.\n private async send(data: Event): Promise<void> {\n const { isLocalhost, isHeadlessBrowser } = getEnvironment();\n if ((isLocalhost && !this.config.trackLocalhostAs) || isHeadlessBrowser) return;\n\n const urlToSend = new URL(location.href);\n\n // Determine debug mode and handle localhost replacement\n let isDebug: boolean = false;\n if (isLocalhost && this.config.trackLocalhostAs && urlToSend.hostname !== this.config.trackLocalhostAs) {\n isDebug = true;\n urlToSend.hostname = this.config.trackLocalhostAs;\n }\n\n // Clean query string unless UTM is explicitly provided\n urlToSend.search = \"\";\n if (data.path) urlToSend.pathname = data.path;\n\n const cleanUrl = urlToSend.href.replace(/\\/$/, \"\");\n\n // Determine referrer\n let referrer: string | undefined = data.referrer;\n try {\n if (!referrer && document.referrer && document.referrer !== \"null\") {\n const referrerURL = new URL(document.referrer);\n if (referrerURL.hostname !== urlToSend.hostname) referrer = referrerURL.href;\n }\n } catch {} // ignore malformed referrer\n\n // Build request body\n const body: BodyToSend = {\n u: cleanUrl,\n e: [\n {\n t: data.type,\n h: this.config.hashRouting,\n r: referrer,\n p: data.props\n }\n ]\n };\n\n if (data.utm && Object.keys(data.utm).length > 0) body.qs = data.utm;\n if (isDebug) body.debug = true;\n\n // Prepare the event payload\n const stringifiedBody = JSON.stringify(body);\n // Encode for safe inclusion in query string using Base64\n const payloadBase64 = btoa(stringifiedBody);\n\n const safeGetThreshold = 1500; // limit for query-string-containing URLs\n const tryImageBeacon = payloadBase64.length <= safeGetThreshold;\n\n if (tryImageBeacon) {\n // Send via image beacon\n const img = new Image(1, 1);\n\n // If loading image fails (server unavailable, blocked, etc.)\n img.onerror = () => this.sendWithBeaconOrFetch(stringifiedBody);\n\n // Primary attempt: send data via image beacon (GET request with query string)\n img.src = `${this.config.collectorUrl}?data=${payloadBase64}`;\n } else await this.sendWithBeaconOrFetch(stringifiedBody);\n }\n\n // Prevents duplicate pageviews and respects include/exclude page rules. Automatically parses UTM parameters from URL.\n private trackPageView({ path, props }: ViewArguments, checkBlock: boolean = false) {\n if (!isClient()) return;\n\n const viewPath = resolvePath(path);\n\n const viewProps =\n props ||\n (() => {\n const newProps = {};\n const elements = document.querySelectorAll(\"[data-s\\\\:view-props], [data-s-view-props]\");\n\n for (const el of Array.from(elements)) {\n const propsString = el.getAttribute(\"data-s-view-props\") || el.getAttribute(\"data-s:view-props\");\n if (!propsString) continue;\n const parsedProps = parseProps(propsString);\n Object.assign(newProps, parsedProps);\n }\n\n return Object.keys(newProps).length ? newProps : undefined;\n })();\n\n // Skip duplicate pageviews or excluded pages\n if (!this.config.hashRouting && this.lastPage === viewPath) return;\n\n // Skip page if checkBlock is true and the path should be excluded\n if (checkBlock && !shouldTrackPath(viewPath, this.config)) return;\n\n this.lastPage = viewPath;\n\n const utm = parseUtmParams(new URLSearchParams(location.search));\n this.send({ type: \"PageView\", path: viewPath, props: viewProps, utm });\n }\n\n /**\n * Tracks a custom event.\n * Can accept path string or a props object.\n *\n * @param eventName Name of the event to track.\n * @param pathOrProps Optional path string or props object.\n * @param props Optional props object if path string is provided.\n */\n public async event(eventName: string, pathOrProps?: string | BaseProps, props?: BaseProps) {\n if (!isClient()) return;\n\n const { isLocalhost, isHeadlessBrowser } = getEnvironment();\n if ((isLocalhost && !this.config.trackLocalhostAs) || isHeadlessBrowser) return;\n\n const args: ViewArguments = {};\n if (typeof pathOrProps === \"string\") {\n args.path = resolvePath(pathOrProps);\n\n args.props = props;\n } else if (typeof pathOrProps === \"object\") args.props = pathOrProps;\n\n this.send({ type: eventName, ...args });\n }\n\n /**\n * Records a page view.\n * Can accept path string or a props object.\n *\n * @param pathOrProps Optional path string or props object.\n * @param props Optional props when first arg is a path string.\n */\n public async view(pathOrProps?: string | BaseProps, props?: BaseProps) {\n if (!isClient()) return;\n\n const args: ViewArguments = {};\n\n if (typeof pathOrProps === \"string\") {\n args.path = pathOrProps;\n args.props = props;\n } else if (typeof pathOrProps === \"object\") {\n args.props = pathOrProps;\n }\n\n this.trackPageView(args);\n }\n\n /**\n * Installs global DOM/window listeners exactly once for:\n * - visibilitychange\n * - history.pushState\n * - popstate\n * - hashchange\n * - click autocapture for elements annotated with `data-s:event` & `data-s-event`\n *\n */\n private setupAutocollect() {\n if (!isClient() || this.autocollectSetupDone) return;\n this.autocollectSetupDone = true;\n\n const handlePageView = () => this.trackPageView({}, true);\n\n // visibilitychange\n const onVisibility = () => {\n if (document.visibilityState === \"visible\") handlePageView();\n };\n document.addEventListener(\"visibilitychange\", onVisibility);\n\n // pushState\n const origPush = history.pushState.bind(history);\n history.pushState = (...args) => {\n origPush(...args);\n requestAnimationFrame(() => {\n handlePageView();\n });\n };\n\n // popstate\n window.addEventListener(\"popstate\", handlePageView);\n\n // hashchange\n window.addEventListener(\"hashchange\", handlePageView);\n\n // click autocapture\n const onClick: EventListener = (ev: Event) => {\n const clickEvent = ev as MouseEvent;\n if (clickEvent.type === \"auxclick\" && clickEvent.button !== 1) return;\n\n const target = clickEvent.target as Element | null;\n if (!target) return;\n\n // Check if inside <a> or <button>\n const insideInteractive = !!target.closest(\"a, button\");\n\n let el: Element | null = target;\n let depth = 0;\n\n while (el) {\n const eventName = el.getAttribute(\"data-s-event\") || el.getAttribute(\"data-s:event\");\n if (eventName) {\n const propsAttr = el.getAttribute(\"data-s-event-props\") || el.getAttribute(\"data-s:event-props\");\n const props = propsAttr ? parseProps(propsAttr) : undefined;\n const path = el.getAttribute(\"data-s-event-path\") || el.getAttribute(\"data-s:event-path\") || undefined;\n\n if ((path && !shouldTrackPath(path, this.config)) || !shouldTrackPath(location.pathname, this.config)) {\n return;\n }\n\n this.event(eventName, path ?? props, props);\n return;\n }\n\n el = el.parentElement;\n depth++;\n\n // If not in <a>/<button>, stop after 3 levels\n if (!insideInteractive && depth >= 3) break;\n }\n };\n\n document.addEventListener(\"click\", onClick);\n\n // Fire initial pageview if already visible\n if (document.visibilityState === \"visible\") handlePageView();\n }\n}\n\nexport const configure = (userConfig: AnalyticsConfig = {}) => {\n AnalyticsTracker.getInstance(userConfig);\n};\n\nexport const event = async (eventName: string, pathOrProps?: string | BaseProps, props?: BaseProps) => {\n const instance = AnalyticsTracker.getInstance();\n await instance.event(eventName, pathOrProps, props);\n};\n\nexport const view = async (pathOrProps?: string | BaseProps, props?: BaseProps) => {\n const instance = AnalyticsTracker.getInstance();\n await instance.view(pathOrProps, props);\n};\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["export const getEnvironment = (): {\n isLocalhost: boolean;\n isHeadlessBrowser: boolean;\n} => ({\n isLocalhost: /^localhost$|^127(\\.[0-9]+){0,2}\\.[0-9]+$|^\\[::1?\\]$/.test(location.hostname) || location.protocol === \"file:\",\n isHeadlessBrowser: Boolean(\n window.navigator.webdriver ||\n (\"_phantom\" in window && window._phantom) ||\n (\"__nightmare\" in window && window.__nightmare) ||\n (\"Cypress\" in window && window.Cypress)\n )\n});\nexport const isClient = (): boolean => {\n try {\n // Basic checks for window and document\n if (typeof window === \"undefined\" || typeof document === \"undefined\") return false;\n\n // Check for navigator safely\n const ua = typeof navigator !== \"undefined\" ? navigator.userAgent : \"\";\n if (/node|jsdom/i.test(ua)) return false;\n return true;\n } catch {\n return false;\n }\n};\n", "export const parseUtmParams = (urlSearchParams: URLSearchParams) => {\n const utm: Record<string, string | string[]> = {};\n\n [\"utm_campaign\", \"utm_source\", \"utm_medium\", \"utm_term\", \"utm_content\"].forEach((key) => {\n const values = urlSearchParams.getAll(key);\n if (values.length === 1) {\n utm[key] = values[0];\n } else if (values.length > 1) {\n utm[key] = values; // store array if multiple values\n }\n });\n\n return utm;\n};\n", "export const parseProps = (propsString: string): Record<string, string> | undefined => {\n if (!propsString) return undefined;\n // \"key1=value1;key2=value2\"\n\n const splittedProps = propsString.split(\";\");\n const propsObj: Record<string, string> = {};\n\n for (const keyValueString of splittedProps) {\n const keyValuePair = keyValueString.split(\"=\").map((el) => el.trim());\n if (keyValuePair.length !== 2 || keyValuePair[0] === \"\" || keyValuePair[1] === \"\") continue;\n // @ts-ignore\n propsObj[keyValuePair[0]] = keyValuePair[1];\n }\n\n return Object.keys(propsObj).length === 0 ? undefined : propsObj;\n};\n", "export const resolvePath = (pathOrProps?: string): string => {\n if (pathOrProps) return pathOrProps;\n\n const sources = [\n { value: document.body?.getAttribute(\"data-s-path\"), name: \"data-s-path\" },\n { value: document.body?.getAttribute(\"data-s:path\"), name: \"data-s:path\" },\n { value: document.querySelector('meta[name=\"stonks-path\"]')?.getAttribute(\"content\"), name: \"meta[name='stonks-path']\" }\n ];\n\n // Only keep sources that actually exist\n const existing = sources.filter(({ value }) => value);\n\n if (existing.length > 1) {\n console.warn(\"[onedollarstats] Multiple path sources found. Using priority order:\", existing.map(({ name }) => name).join(\" > \"));\n }\n\n // Return first available value, fallback to location.pathname\n return existing[0]?.value ?? location.pathname;\n};\n", "import type { AnalyticsConfig } from \"../types\";\n\nconst matchesPattern = (path: string, pattern: string): boolean => {\n // Escape special regex characters except '*' which becomes '.*'\n const escaped = pattern.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(/\\*/g, \".*\");\n return new RegExp(`^${escaped}$`).test(path);\n};\n\nexport const shouldTrackPath = (path: string, config: Required<AnalyticsConfig>): boolean => {\n // Exclude pages first\n if (config.excludePages.some((pattern) => matchesPattern(path, pattern))) return false;\n // If includePages is defined, only allow matching paths\n if (config.includePages.length && !config.includePages.some((pattern) => matchesPattern(path, pattern))) return false;\n return true;\n};\n", "import type { AnalyticsConfig, BaseProps, BodyToSend, Event, ViewArguments } from \"./types\";\nimport { getEnvironment, isClient } from \"./utils/environment\";\nimport { parseUtmParams } from \"./utils/parse-utm-params\";\nimport { parseProps } from \"./utils/props-parser\";\nimport { resolvePath } from \"./utils/resolve-path\";\nimport { shouldTrackPath } from \"./utils/should-track\";\n\nconst defaultConfig: Required<AnalyticsConfig> = {\n trackLocalhostAs: null,\n collectorUrl: \"https://collector.onedollarstats.com/events\",\n hashRouting: false,\n autocollect: true,\n excludePages: [],\n includePages: []\n};\n\nclass AnalyticsTracker {\n private static instance: AnalyticsTracker | null = null;\n\n private autocollectSetupDone = false;\n private config: Required<AnalyticsConfig>;\n private lastPage: string | null = null;\n\n public static getInstance(userConfig: AnalyticsConfig = {}): AnalyticsTracker {\n // Fresh no-op instance for SSR\n if (!isClient()) return new AnalyticsTracker(userConfig);\n\n if (!AnalyticsTracker.instance) {\n AnalyticsTracker.instance = new AnalyticsTracker(userConfig);\n }\n return AnalyticsTracker.instance;\n }\n\n private constructor(userConfig: AnalyticsConfig = {}) {\n this.config = { ...defaultConfig, ...userConfig };\n\n // Skip setup in non-client environments\n if (!isClient()) return;\n\n // Auto-start autocollect\n if (this.config.autocollect) this.setupAutocollect();\n }\n\n private async sendWithBeaconOrFetch(stringifiedBody: string): Promise<void> {\n // First fallback: try sendBeacon\n if (navigator.sendBeacon?.(this.config.collectorUrl, stringifiedBody)) return;\n\n // Second fallback: use fetch() with keepalive\n fetch(this.config.collectorUrl, {\n method: \"POST\",\n body: stringifiedBody,\n headers: { \"Content-Type\": \"application/json\" },\n keepalive: true\n }).catch((err: Error) => console.error(\"[onedollarstats] fetch() failed:\", err.message));\n }\n\n // Handles localhost replacement, referrer, UTM parameters, and debug mode.\n // Uses img beacon then `navigator.sendBeacon` if available, otherwise falls back to `fetch`.\n private async send(data: Event): Promise<void> {\n const { isLocalhost, isHeadlessBrowser } = getEnvironment();\n if ((isLocalhost && !this.config.trackLocalhostAs) || isHeadlessBrowser) return;\n\n const urlToSend = new URL(location.href);\n\n // Determine debug mode and handle localhost replacement\n let isDebug: boolean = false;\n if (isLocalhost && this.config.trackLocalhostAs && urlToSend.hostname !== this.config.trackLocalhostAs) {\n isDebug = true;\n urlToSend.hostname = this.config.trackLocalhostAs;\n }\n\n // Clean query string unless UTM is explicitly provided\n urlToSend.search = \"\";\n if (data.path) urlToSend.pathname = data.path;\n\n const cleanUrl = urlToSend.href.replace(/\\/$/, \"\");\n\n // Determine referrer\n let referrer: string | undefined = data.referrer;\n try {\n if (!referrer && document.referrer && document.referrer !== \"null\") {\n const referrerURL = new URL(document.referrer);\n if (referrerURL.hostname !== urlToSend.hostname) referrer = referrerURL.href;\n }\n } catch {} // ignore malformed referrer\n\n // Build request body\n const body: BodyToSend = {\n u: cleanUrl,\n e: [\n {\n t: data.type,\n h: this.config.hashRouting,\n r: referrer,\n p: data.props\n }\n ]\n };\n\n if (data.utm && Object.keys(data.utm).length > 0) body.qs = data.utm;\n if (isDebug) body.debug = true;\n\n // Prepare the event payload\n const stringifiedBody = JSON.stringify(body);\n // Encode for safe inclusion in query string using Base64\n const payloadBase64 = btoa(stringifiedBody);\n\n const safeGetThreshold = 1500; // limit for query-string-containing URLs\n const tryImageBeacon = payloadBase64.length <= safeGetThreshold;\n\n if (tryImageBeacon) {\n // Send via image beacon\n const img = new Image(1, 1);\n\n // If loading image fails (server unavailable, blocked, etc.)\n img.onerror = () => this.sendWithBeaconOrFetch(stringifiedBody);\n\n // Primary attempt: send data via image beacon (GET request with query string)\n img.src = `${this.config.collectorUrl}?data=${payloadBase64}`;\n } else await this.sendWithBeaconOrFetch(stringifiedBody);\n }\n\n // Prevents duplicate pageviews and respects include/exclude page rules. Automatically parses UTM parameters from URL.\n private trackPageView({ path, props }: ViewArguments, checkBlock: boolean = false) {\n if (!isClient()) return;\n\n const viewPath = resolvePath(path);\n\n const viewProps =\n props ||\n (() => {\n const newProps = {};\n const elements = document.querySelectorAll(\"[data-s\\\\:view-props], [data-s-view-props]\");\n\n for (const el of Array.from(elements)) {\n const propsString = el.getAttribute(\"data-s-view-props\") || el.getAttribute(\"data-s:view-props\");\n if (!propsString) continue;\n const parsedProps = parseProps(propsString);\n Object.assign(newProps, parsedProps);\n }\n\n return Object.keys(newProps).length ? newProps : undefined;\n })();\n\n // Skip duplicate pageviews or excluded pages\n if (!this.config.hashRouting && this.lastPage === viewPath) return;\n\n // Skip page if checkBlock is true and the path should be excluded\n if (checkBlock && !shouldTrackPath(viewPath, this.config)) return;\n\n this.lastPage = viewPath;\n\n const utm = parseUtmParams(new URLSearchParams(location.search));\n this.send({ type: \"PageView\", path: viewPath, props: viewProps, utm });\n }\n\n /**\n * Tracks a custom event.\n * Can accept path string or a props object.\n *\n * @param eventName Name of the event to track.\n * @param pathOrProps Optional path string or props object.\n * @param props Optional props object if path string is provided.\n */\n public async event(eventName: string, pathOrProps?: string | BaseProps, props?: BaseProps) {\n if (!isClient()) return;\n\n const { isLocalhost, isHeadlessBrowser } = getEnvironment();\n if ((isLocalhost && !this.config.trackLocalhostAs) || isHeadlessBrowser) return;\n\n const args: ViewArguments = {};\n if (typeof pathOrProps === \"string\") {\n args.path = resolvePath(pathOrProps);\n\n args.props = props;\n } else if (typeof pathOrProps === \"object\") args.props = pathOrProps;\n\n this.send({ type: eventName, ...args });\n }\n\n /**\n * Records a page view.\n * Can accept path string or a props object.\n *\n * @param pathOrProps Optional path string or props object.\n * @param props Optional props when first arg is a path string.\n */\n public async view(pathOrProps?: string | BaseProps, props?: BaseProps) {\n if (!isClient()) return;\n\n const args: ViewArguments = {};\n\n if (typeof pathOrProps === \"string\") {\n args.path = pathOrProps;\n args.props = props;\n } else if (typeof pathOrProps === \"object\") {\n args.props = pathOrProps;\n }\n\n this.trackPageView(args);\n }\n\n /**\n * Installs global DOM/window listeners exactly once for:\n * - visibilitychange\n * - history.pushState\n * - popstate\n * - hashchange\n * - click autocapture for elements annotated with `data-s:event` & `data-s-event`\n *\n */\n private setupAutocollect() {\n if (!isClient() || this.autocollectSetupDone) return;\n this.autocollectSetupDone = true;\n\n const handlePageView = () => this.trackPageView({}, true);\n\n // visibilitychange\n const onVisibility = () => {\n if (document.visibilityState === \"visible\") handlePageView();\n };\n document.addEventListener(\"visibilitychange\", onVisibility);\n\n // pushState\n const origPush = history.pushState.bind(history);\n history.pushState = (...args) => {\n origPush(...args);\n requestAnimationFrame(() => {\n handlePageView();\n });\n };\n\n // popstate\n window.addEventListener(\"popstate\", handlePageView);\n\n // hashchange\n window.addEventListener(\"hashchange\", handlePageView);\n\n // click autocapture\n const onClick: EventListener = (ev: Event) => {\n const clickEvent = ev as MouseEvent;\n if (clickEvent.type === \"auxclick\" && clickEvent.button !== 1) return;\n\n const target = clickEvent.target as Element | null;\n if (!target) return;\n\n // Check if inside <a> or <button>\n const insideInteractive = !!target.closest(\"a, button\");\n\n let el: Element | null = target;\n let depth = 0;\n\n while (el) {\n const eventName = el.getAttribute(\"data-s-event\") || el.getAttribute(\"data-s:event\");\n if (eventName) {\n const propsAttr = el.getAttribute(\"data-s-event-props\") || el.getAttribute(\"data-s:event-props\");\n const props = propsAttr ? parseProps(propsAttr) : undefined;\n const path = el.getAttribute(\"data-s-event-path\") || el.getAttribute(\"data-s:event-path\") || undefined;\n\n if ((path && !shouldTrackPath(path, this.config)) || !shouldTrackPath(location.pathname, this.config)) {\n return;\n }\n\n this.event(eventName, path ?? props, props);\n return;\n }\n\n el = el.parentElement;\n depth++;\n\n // If not in <a>/<button>, stop after 3 levels\n if (!insideInteractive && depth >= 3) break;\n }\n };\n\n document.addEventListener(\"click\", onClick);\n\n // Fire initial pageview if already visible\n if (document.visibilityState === \"visible\") handlePageView();\n }\n}\n\nexport const configure = (userConfig: AnalyticsConfig = {}) => {\n AnalyticsTracker.getInstance(userConfig);\n};\n\nexport const event = async (eventName: string, pathOrProps?: string | BaseProps, props?: BaseProps) => {\n const instance = AnalyticsTracker.getInstance();\n await instance.event(eventName, pathOrProps, props);\n};\n\nexport const view = async (pathOrProps?: string | BaseProps, props?: BaseProps) => {\n const instance = AnalyticsTracker.getInstance();\n await instance.view(pathOrProps, props);\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|