onedollarstats 0.0.1
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/LICENSE +21 -0
- package/README.md +122 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +261 -0
- package/dist/index.js.map +7 -0
- package/dist/types.d.ts +34 -0
- package/dist/utils/environment.d.ts +5 -0
- package/dist/utils/parse-utm-params.d.ts +1 -0
- package/dist/utils/props-parser.d.ts +1 -0
- package/dist/utils/should-track.d.ts +2 -0
- package/package.json +31 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Drizzle Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# OneDollarStats
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/onedollarstats)
|
|
4
|
+
[](https://onedollarstats.com/home)
|
|
5
|
+
|
|
6
|
+
A lightweight, zero-dependency analytics tracker for client apps. OneDollarStats automatically collects pageviews, UTM parameters, and custom events with minimal setup.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
-Automatic pageview tracking (supports client/server side navigation and hash routing)
|
|
11
|
+
-Automatic UTM parameter collection
|
|
12
|
+
-Automatic event tracking on clicks of elements with data-s-event attributes
|
|
13
|
+
-Zero dependencies, easy to integrate
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm i onedollarstats
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Getting Started
|
|
22
|
+
|
|
23
|
+
### Configure analytics
|
|
24
|
+
|
|
25
|
+
> ⚠️ Initialize analytics on every page for static sites, or at the root layout (app entrypoint) in SPA apps.
|
|
26
|
+
> Calling `view` or `event` before `configure` will automatically initialize the tracker with the default configuration.
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { configure } from "onedollarstats";
|
|
30
|
+
|
|
31
|
+
// Configure analytics
|
|
32
|
+
configure({
|
|
33
|
+
collectorUrl: "https://collector.onedollarstats.com/events",
|
|
34
|
+
autocollect: true, // automatically tracks pageviews & clicks
|
|
35
|
+
hashRouting: true // track SPA hash route changes
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### Track Pageviews Manually
|
|
40
|
+
|
|
41
|
+
By default, pageviews are tracked automatically. If you want to track them manually (for example, with autocollect: false), you can use the `view` function:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { view } from "onedollarstats";
|
|
45
|
+
|
|
46
|
+
// Simple pageview
|
|
47
|
+
view("/homepage");
|
|
48
|
+
|
|
49
|
+
// Pageview with extra properties
|
|
50
|
+
view("/checkout", { step: 2, plan: "pro" });
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
#### Track Custom Events Manually
|
|
54
|
+
|
|
55
|
+
The `event` function can accept different types of arguments depending on your needs:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { event } from "onedollarstats";
|
|
59
|
+
|
|
60
|
+
// Simple event
|
|
61
|
+
event("Purchase");
|
|
62
|
+
|
|
63
|
+
// Event with a path
|
|
64
|
+
event("Purchase", "/product");
|
|
65
|
+
|
|
66
|
+
// Event with properties
|
|
67
|
+
event("Purchase", { amount: 1, color: "green" });
|
|
68
|
+
|
|
69
|
+
// Event with path + properties
|
|
70
|
+
event("Purchase", "/product", { amount: 1, color: "green" });
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## API
|
|
74
|
+
|
|
75
|
+
#### `configure(config?: AnalyticsConfig)` initializes the tracker with your configuration.
|
|
76
|
+
|
|
77
|
+
**Config Options:**
|
|
78
|
+
|
|
79
|
+
| Option | Type | Default | Description |
|
|
80
|
+
| ------------------ | ---------------- | ----------------------------------------------- | ------------------------------------------ |
|
|
81
|
+
| `collectorUrl` | `string` | `"https://collector.onedollarstats.com/events"` | URL to send analytics events |
|
|
82
|
+
| `trackLocalhostAs` | `string \| null` | `null` | Replace localhost hostname for dev testing |
|
|
83
|
+
| `hashRouting` | `boolean` | `false` | Track hash route changes as pageviews |
|
|
84
|
+
| `autocollect` | `boolean` | `true` | Automatically track pageviews & clicks |
|
|
85
|
+
| `excludePages` | `string[]` | `[]` | Pages to ignore for automatic tracking |
|
|
86
|
+
| `includePages` | `string[]` | `[]` | Pages to explicitly include for tracking |
|
|
87
|
+
|
|
88
|
+
> **Notes:**
|
|
89
|
+
>
|
|
90
|
+
> - Manual calls of `view` or `event` **ignore** `excludePages`/`includePages`.
|
|
91
|
+
> - By default, events from `localhost` are ignored. Use the `trackLocalhostAs` option to simulate a hostname for local development.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
#### `view(pathOrProps?: string | Record<string, string>, props?: Record<string, string>)` sends a pageview event.
|
|
96
|
+
|
|
97
|
+
**Parameters:**
|
|
98
|
+
|
|
99
|
+
- `pathOrProps` – Optional, **string** represents the path, **object** represents custom properties.
|
|
100
|
+
- `props` – Optional, properties if the first argument is a path string.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
#### `event(eventName: string, pathOrProps?: string | Record<string, string>, props?: Record<string, string>)` sends a custom event.
|
|
105
|
+
|
|
106
|
+
**Parameters:**
|
|
107
|
+
|
|
108
|
+
- `eventName` – Name of the event.
|
|
109
|
+
- `pathOrProps` – Optional, **string** represents the path, **object** represents custom properties.
|
|
110
|
+
- `props` – Optional, properties if the second argument is a path string.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Click Autocapture
|
|
115
|
+
|
|
116
|
+
Automatically capture clicks on elements using these HTML attributes:
|
|
117
|
+
|
|
118
|
+
- `data-s-event`– Name of the event
|
|
119
|
+
- `data-s-event-path` Optional, the path representing the page where the event occurred
|
|
120
|
+
- `data-s-event-props` – Optional, properties to send with the event
|
|
121
|
+
|
|
122
|
+
For full details, see the [Click Autocapture documentation](https://docs.onedollarstats.com/send-events).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AnalyticsConfig, BaseProps } from "./types";
|
|
2
|
+
export declare const configure: (userConfig?: AnalyticsConfig) => void;
|
|
3
|
+
export declare const event: (eventName: string, pathOrProps?: string | BaseProps, props?: BaseProps) => Promise<void>;
|
|
4
|
+
export declare const view: (pathOrProps?: string | BaseProps, props?: BaseProps) => Promise<void>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// src/utils/environment.ts
|
|
2
|
+
var getEnvironment = () => ({
|
|
3
|
+
isLocalhost: /^localhost$|^127(\.[0-9]+){0,2}\.[0-9]+$|^\[::1?\]$/.test(location.hostname) || location.protocol === "file:",
|
|
4
|
+
isHeadlessBrowser: Boolean(
|
|
5
|
+
window.navigator.webdriver || "_phantom" in window && window._phantom || "__nightmare" in window && window.__nightmare || "Cypress" in window && window.Cypress
|
|
6
|
+
)
|
|
7
|
+
});
|
|
8
|
+
var isClient = () => {
|
|
9
|
+
try {
|
|
10
|
+
if (typeof window === "undefined" || typeof document === "undefined") return false;
|
|
11
|
+
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
12
|
+
if (/node|jsdom/i.test(ua)) return false;
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// src/utils/parse-utm-params.ts
|
|
20
|
+
var parseUtmParams = (urlSearchParams) => {
|
|
21
|
+
const utm = {};
|
|
22
|
+
["utm_campaign", "utm_source", "utm_medium", "utm_term", "utm_content"].forEach((key) => {
|
|
23
|
+
const values = urlSearchParams.getAll(key);
|
|
24
|
+
if (values.length === 1) {
|
|
25
|
+
utm[key] = values[0];
|
|
26
|
+
} else if (values.length > 1) {
|
|
27
|
+
utm[key] = values;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return utm;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/utils/props-parser.ts
|
|
34
|
+
var parseProps = (propsString) => {
|
|
35
|
+
if (!propsString) return void 0;
|
|
36
|
+
const splittedProps = propsString.split(";");
|
|
37
|
+
const propsObj = {};
|
|
38
|
+
for (const keyValueString of splittedProps) {
|
|
39
|
+
const keyValuePair = keyValueString.split("=").map((el) => el.trim());
|
|
40
|
+
if (keyValuePair.length !== 2 || keyValuePair[0] === "" || keyValuePair[1] === "") continue;
|
|
41
|
+
propsObj[keyValuePair[0]] = keyValuePair[1];
|
|
42
|
+
}
|
|
43
|
+
return Object.keys(propsObj).length === 0 ? void 0 : propsObj;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/utils/should-track.ts
|
|
47
|
+
var matchesPattern = (path, pattern) => {
|
|
48
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
49
|
+
return new RegExp(`^${escaped}$`).test(path);
|
|
50
|
+
};
|
|
51
|
+
var shouldTrackPath = (path, config) => {
|
|
52
|
+
if (config.excludePages.some((pattern) => matchesPattern(path, pattern))) return false;
|
|
53
|
+
if (config.includePages.length && !config.includePages.some((pattern) => matchesPattern(path, pattern))) return false;
|
|
54
|
+
return true;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/index.ts
|
|
58
|
+
var defaultConfig = {
|
|
59
|
+
trackLocalhostAs: null,
|
|
60
|
+
collectorUrl: "https://collector.onedollarstats.com/events",
|
|
61
|
+
hashRouting: false,
|
|
62
|
+
autocollect: true,
|
|
63
|
+
excludePages: [],
|
|
64
|
+
includePages: []
|
|
65
|
+
};
|
|
66
|
+
var _AnalyticsTracker = class _AnalyticsTracker {
|
|
67
|
+
constructor(userConfig = {}) {
|
|
68
|
+
this.autocollectSetupDone = false;
|
|
69
|
+
this.lastPage = null;
|
|
70
|
+
this.config = { ...defaultConfig, ...userConfig };
|
|
71
|
+
if (!isClient()) return;
|
|
72
|
+
if (this.config.autocollect) this.setupAutocollect();
|
|
73
|
+
}
|
|
74
|
+
static getInstance(userConfig = {}) {
|
|
75
|
+
if (!isClient()) {
|
|
76
|
+
console.warn("[onedollarstats] Running in non-browser environment. Returning no-op instance.");
|
|
77
|
+
return new _AnalyticsTracker(userConfig);
|
|
78
|
+
}
|
|
79
|
+
if (!_AnalyticsTracker.instance) {
|
|
80
|
+
_AnalyticsTracker.instance = new _AnalyticsTracker(userConfig);
|
|
81
|
+
}
|
|
82
|
+
return _AnalyticsTracker.instance;
|
|
83
|
+
}
|
|
84
|
+
async sendWithBeaconOrFetch(stringifiedBody) {
|
|
85
|
+
if (navigator.sendBeacon?.(this.config.collectorUrl, stringifiedBody)) return;
|
|
86
|
+
fetch(this.config.collectorUrl, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
body: stringifiedBody,
|
|
89
|
+
headers: { "Content-Type": "application/json" },
|
|
90
|
+
keepalive: true
|
|
91
|
+
}).catch((err) => console.error("[onedollarstats] fetch() failed:", err.message));
|
|
92
|
+
}
|
|
93
|
+
// Handles localhost replacement, referrer, UTM parameters, and debug mode.
|
|
94
|
+
// Uses img beacon then `navigator.sendBeacon` if available, otherwise falls back to `fetch`.
|
|
95
|
+
async send(data) {
|
|
96
|
+
const { isLocalhost, isHeadlessBrowser } = getEnvironment();
|
|
97
|
+
if (isLocalhost && !this.config.trackLocalhostAs || isHeadlessBrowser) return;
|
|
98
|
+
const urlToSend = new URL(location.href);
|
|
99
|
+
let isDebug = false;
|
|
100
|
+
if (isLocalhost && this.config.trackLocalhostAs && urlToSend.hostname !== this.config.trackLocalhostAs) {
|
|
101
|
+
isDebug = true;
|
|
102
|
+
urlToSend.hostname = this.config.trackLocalhostAs;
|
|
103
|
+
}
|
|
104
|
+
urlToSend.search = "";
|
|
105
|
+
if (data.path) urlToSend.pathname = data.path;
|
|
106
|
+
const cleanUrl = urlToSend.href.replace(/\/$/, "");
|
|
107
|
+
let referrer = data.referrer;
|
|
108
|
+
try {
|
|
109
|
+
if (!referrer && document.referrer && document.referrer !== "null") {
|
|
110
|
+
const referrerURL = new URL(document.referrer);
|
|
111
|
+
if (referrerURL.hostname !== urlToSend.hostname) referrer = referrerURL.href;
|
|
112
|
+
}
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
115
|
+
const body = {
|
|
116
|
+
u: cleanUrl,
|
|
117
|
+
e: [
|
|
118
|
+
{
|
|
119
|
+
t: data.type,
|
|
120
|
+
h: this.config.hashRouting,
|
|
121
|
+
r: referrer,
|
|
122
|
+
p: data.props
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
};
|
|
126
|
+
if (data.utm && Object.keys(data.utm).length > 0) body.qs = data.utm;
|
|
127
|
+
if (isDebug) body.debug = true;
|
|
128
|
+
const stringifiedBody = JSON.stringify(body);
|
|
129
|
+
const payloadBase64 = btoa(stringifiedBody);
|
|
130
|
+
const safeGetThreshold = 1500;
|
|
131
|
+
const tryImageBeacon = payloadBase64.length <= safeGetThreshold;
|
|
132
|
+
if (tryImageBeacon) {
|
|
133
|
+
const img = new Image(1, 1);
|
|
134
|
+
img.onerror = () => {
|
|
135
|
+
this.sendWithBeaconOrFetch(stringifiedBody).catch((err) => console.error("[onedollarstats] fallback failed:", err?.message || err));
|
|
136
|
+
};
|
|
137
|
+
img.src = `${this.config.collectorUrl}?data=${payloadBase64}`;
|
|
138
|
+
}
|
|
139
|
+
await this.sendWithBeaconOrFetch(stringifiedBody);
|
|
140
|
+
}
|
|
141
|
+
// Prevents duplicate pageviews and respects include/exclude page rules. Automatically parses UTM parameters from URL.
|
|
142
|
+
trackPageView({ path, props }, checkBlock = false) {
|
|
143
|
+
if (!isClient()) return;
|
|
144
|
+
const cleanPath = path || location.pathname;
|
|
145
|
+
if (!this.config.hashRouting && this.lastPage === cleanPath) return;
|
|
146
|
+
if (checkBlock && !shouldTrackPath(cleanPath, this.config)) return;
|
|
147
|
+
this.lastPage = cleanPath;
|
|
148
|
+
const utm = parseUtmParams(new URLSearchParams(location.search));
|
|
149
|
+
this.send({ type: "PageView", path: cleanPath, props, utm });
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Tracks a custom event.
|
|
153
|
+
* Can accept path string or a props object.
|
|
154
|
+
*
|
|
155
|
+
* @param eventName Name of the event to track.
|
|
156
|
+
* @param pathOrProps Optional path string or props object.
|
|
157
|
+
* @param props Optional props object if path string is provided.
|
|
158
|
+
*/
|
|
159
|
+
async event(eventName, pathOrProps, props) {
|
|
160
|
+
if (!isClient()) return;
|
|
161
|
+
const { isLocalhost, isHeadlessBrowser } = getEnvironment();
|
|
162
|
+
if (isLocalhost && !this.config.trackLocalhostAs || isHeadlessBrowser) return;
|
|
163
|
+
const args = {};
|
|
164
|
+
if (typeof pathOrProps === "string") {
|
|
165
|
+
args.path = pathOrProps;
|
|
166
|
+
args.props = props;
|
|
167
|
+
} else if (typeof pathOrProps === "object") args.props = pathOrProps;
|
|
168
|
+
this.send({ type: eventName, ...args });
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Records a page view.
|
|
172
|
+
* Can accept path string or a props object.
|
|
173
|
+
*
|
|
174
|
+
* @param pathOrProps Optional path string or props object.
|
|
175
|
+
* @param props Optional props when first arg is a path string.
|
|
176
|
+
*/
|
|
177
|
+
async view(pathOrProps, props) {
|
|
178
|
+
if (!isClient()) return;
|
|
179
|
+
const args = {};
|
|
180
|
+
if (typeof pathOrProps === "string") {
|
|
181
|
+
args.path = pathOrProps;
|
|
182
|
+
args.props = props;
|
|
183
|
+
} else if (typeof pathOrProps === "object") {
|
|
184
|
+
args.props = pathOrProps;
|
|
185
|
+
}
|
|
186
|
+
this.trackPageView(args);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Installs global DOM/window listeners exactly once for:
|
|
190
|
+
* - visibilitychange
|
|
191
|
+
* - history.pushState
|
|
192
|
+
* - popstate
|
|
193
|
+
* - hashchange
|
|
194
|
+
* - click autocapture for elements annotated with `data-s:event` & `data-s-event`
|
|
195
|
+
*
|
|
196
|
+
*/
|
|
197
|
+
setupAutocollect() {
|
|
198
|
+
if (!isClient() || this.autocollectSetupDone) return;
|
|
199
|
+
this.autocollectSetupDone = true;
|
|
200
|
+
const handlePageView = () => this.trackPageView({ path: location.pathname }, true);
|
|
201
|
+
const onVisibility = () => {
|
|
202
|
+
if (document.visibilityState === "visible") handlePageView();
|
|
203
|
+
};
|
|
204
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
205
|
+
const origPush = history.pushState.bind(history);
|
|
206
|
+
history.pushState = (...args) => {
|
|
207
|
+
origPush(...args);
|
|
208
|
+
requestAnimationFrame(() => {
|
|
209
|
+
handlePageView();
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
window.addEventListener("popstate", handlePageView);
|
|
213
|
+
window.addEventListener("hashchange", handlePageView);
|
|
214
|
+
const onClick = (ev) => {
|
|
215
|
+
const clickEvent = ev;
|
|
216
|
+
if (clickEvent.type === "auxclick" && clickEvent.button !== 1) return;
|
|
217
|
+
const target = clickEvent.target;
|
|
218
|
+
if (!target) return;
|
|
219
|
+
const insideInteractive = !!target.closest("a, button");
|
|
220
|
+
let el = target;
|
|
221
|
+
let depth = 0;
|
|
222
|
+
while (el) {
|
|
223
|
+
const eventName = el.getAttribute("data-s:event") ?? el.getAttribute("data-s-event");
|
|
224
|
+
if (eventName) {
|
|
225
|
+
const propsAttr = el.getAttribute("data-s:event-props") ?? el.getAttribute("data-s-event-props");
|
|
226
|
+
const props = propsAttr ? parseProps(propsAttr) : void 0;
|
|
227
|
+
const path = el.getAttribute("data-s:event-path") || el.getAttribute("data-s-event-path") || void 0;
|
|
228
|
+
if (path && !shouldTrackPath(path, this.config) || !shouldTrackPath(location.pathname, this.config)) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
this.event(eventName, path ?? props, props);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
el = el.parentElement;
|
|
235
|
+
depth++;
|
|
236
|
+
if (!insideInteractive && depth >= 3) break;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
document.addEventListener("click", onClick);
|
|
240
|
+
if (document.visibilityState === "visible") handlePageView();
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
_AnalyticsTracker.instance = null;
|
|
244
|
+
var AnalyticsTracker = _AnalyticsTracker;
|
|
245
|
+
var configure = (userConfig = {}) => {
|
|
246
|
+
AnalyticsTracker.getInstance(userConfig);
|
|
247
|
+
};
|
|
248
|
+
var event = async (eventName, pathOrProps, props) => {
|
|
249
|
+
const instance = AnalyticsTracker.getInstance();
|
|
250
|
+
await instance.event(eventName, pathOrProps, props);
|
|
251
|
+
};
|
|
252
|
+
var view = async (pathOrProps, props) => {
|
|
253
|
+
const instance = AnalyticsTracker.getInstance();
|
|
254
|
+
await instance.view(pathOrProps, props);
|
|
255
|
+
};
|
|
256
|
+
export {
|
|
257
|
+
configure,
|
|
258
|
+
event,
|
|
259
|
+
view
|
|
260
|
+
};
|
|
261
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/utils/environment.ts", "../src/utils/parse-utm-params.ts", "../src/utils/props-parser.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", "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 { 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 = () => {\n this.sendWithBeaconOrFetch(stringifiedBody).catch((err) => console.error(\"[onedollarstats] fallback failed:\", err?.message || err));\n };\n\n // Primary attempt: send data via image beacon (GET request with query string)\n img.src = `${this.config.collectorUrl}?data=${payloadBase64}`;\n }\n\n 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 cleanPath = path || location.pathname;\n\n // Skip duplicate pageviews or excluded pages\n if (!this.config.hashRouting && this.lastPage === cleanPath) return;\n\n // Skip page if checkBlock is true and the path should be excluded\n if (checkBlock && !shouldTrackPath(cleanPath, this.config)) return;\n\n this.lastPage = cleanPath;\n\n const utm = parseUtmParams(new URLSearchParams(location.search));\n this.send({ type: \"PageView\", path: cleanPath, props, 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 = pathOrProps;\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({ path: location.pathname }, 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
|
+
"names": []
|
|
7
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
type UtmParams = Record<string, string | string[]>;
|
|
2
|
+
export type BaseProps = Record<string, string>;
|
|
3
|
+
type MinimizedEvent = {
|
|
4
|
+
t: string;
|
|
5
|
+
h?: boolean;
|
|
6
|
+
r?: string;
|
|
7
|
+
p?: BaseProps;
|
|
8
|
+
};
|
|
9
|
+
export type Event = {
|
|
10
|
+
type: string;
|
|
11
|
+
path?: string;
|
|
12
|
+
props?: BaseProps;
|
|
13
|
+
utm?: UtmParams;
|
|
14
|
+
referrer?: string;
|
|
15
|
+
};
|
|
16
|
+
export type BodyToSend = {
|
|
17
|
+
u: string;
|
|
18
|
+
e: [MinimizedEvent];
|
|
19
|
+
qs?: UtmParams;
|
|
20
|
+
debug?: boolean;
|
|
21
|
+
};
|
|
22
|
+
export type ViewArguments = {
|
|
23
|
+
path?: string;
|
|
24
|
+
props?: BaseProps;
|
|
25
|
+
};
|
|
26
|
+
export type AnalyticsConfig = {
|
|
27
|
+
collectorUrl?: string;
|
|
28
|
+
trackLocalhostAs?: string | null;
|
|
29
|
+
hashRouting?: boolean;
|
|
30
|
+
autocollect?: boolean;
|
|
31
|
+
excludePages?: string[];
|
|
32
|
+
includePages?: string[];
|
|
33
|
+
};
|
|
34
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const parseUtmParams: (urlSearchParams: URLSearchParams) => Record<string, string | string[]>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const parseProps: (propsString: string) => Record<string, string> | undefined;
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "onedollarstats",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A lightweight, zero-dependency analytics tracker for frontend apps",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc --emitDeclarationOnly && esbuild src/index.ts --bundle --outfile=dist/index.js --platform=browser --format=esm --target=es2020 --sourcemap"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"analytics",
|
|
16
|
+
"tracking",
|
|
17
|
+
"lightweight",
|
|
18
|
+
"zero-dependency",
|
|
19
|
+
"javascript",
|
|
20
|
+
"typescript",
|
|
21
|
+
"events",
|
|
22
|
+
"onedollarstats"
|
|
23
|
+
],
|
|
24
|
+
"author": "",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"packageManager": "pnpm@10.16.1",
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"esbuild": "^0.25.10",
|
|
29
|
+
"typescript": "^5.9.2"
|
|
30
|
+
}
|
|
31
|
+
}
|