onedollarstats 0.0.18 → 0.0.20
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 +39 -0
- package/dist/expo.d.ts +35 -0
- package/dist/expo.js +201 -0
- package/dist/expo.js.map +7 -0
- package/dist/index.js +28 -120
- package/dist/index.js.map +2 -2
- package/package.json +49 -14
package/README.md
CHANGED
|
@@ -119,6 +119,45 @@ event("Purchase", "/product", { amount: 1, color: "green" });
|
|
|
119
119
|
- `pathOrProps` – Optional, **string** represents the path, **object** represents custom properties.
|
|
120
120
|
- `props` – Optional, properties if the second argument is a path string.
|
|
121
121
|
|
|
122
|
+
## Expo / React Native
|
|
123
|
+
|
|
124
|
+
`onedollarstats/expo` is a dedicated entry point for Expo apps using `expo-router`. It auto-collects pageviews on route change and on app foreground, supports dynamic-route templates (`/profile/[id]` instead of `/profile/abc123`), and sends events natively on iOS/Android and via image beacon + `sendBeacon` on web.
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
// app/_layout.tsx
|
|
128
|
+
import { Stack } from 'expo-router';
|
|
129
|
+
import { OneDollarStatsProvider } from 'onedollarstats/expo';
|
|
130
|
+
|
|
131
|
+
export default function RootLayout() {
|
|
132
|
+
return (
|
|
133
|
+
<OneDollarStatsProvider config={{ hostname: 'example.com' }}>
|
|
134
|
+
<Stack screenOptions={{ headerShown: false }} />
|
|
135
|
+
</OneDollarStatsProvider>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Fire custom events or manual pageviews from any component with `useAnalytics()`:
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
import { useAnalytics } from 'onedollarstats/expo';
|
|
144
|
+
|
|
145
|
+
const { event, view } = useAnalytics();
|
|
146
|
+
|
|
147
|
+
event('signup', { plan: 'pro' }); // event with current route
|
|
148
|
+
event('signup', '/landing'); // event with explicit path
|
|
149
|
+
view({ campaign: 'spring' }); // pageview with just props
|
|
150
|
+
view('/landing', { campaign: 'spring' }); // pageview with explicit path
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Expo-specific config options:**
|
|
154
|
+
|
|
155
|
+
| Option | Type | Default | Description |
|
|
156
|
+
| ----------------------- | ---------- | ------- | -------------------------------------------------------------------------------------------- |
|
|
157
|
+
| `collapseDynamicRoutes` | `boolean` | `true` | Use `useSegments()` to record routes as templates (`/profile/[id]`) instead of concrete paths. Group segments like `(tabs)` are stripped. |
|
|
158
|
+
|
|
159
|
+
All other options (`hostname`, `collectorUrl`, `devmode`, `autocollect`, `excludePages`, `includePages`) behave the same as in the web tracker above.
|
|
160
|
+
|
|
122
161
|
## Autocapture
|
|
123
162
|
|
|
124
163
|
**Page view events**
|
package/dist/expo.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type MutableRefObject, type ReactNode } from 'react';
|
|
2
|
+
export type ExpoAnalyticsConfig = {
|
|
3
|
+
hostname: string;
|
|
4
|
+
collectorUrl?: string;
|
|
5
|
+
excludePages?: string[];
|
|
6
|
+
includePages?: string[];
|
|
7
|
+
autocollect?: boolean;
|
|
8
|
+
devmode?: boolean;
|
|
9
|
+
collapseDynamicRoutes?: boolean;
|
|
10
|
+
};
|
|
11
|
+
type InternalConfig = {
|
|
12
|
+
hostname: string;
|
|
13
|
+
collectorUrl: string;
|
|
14
|
+
autocollect: boolean;
|
|
15
|
+
devmode: boolean;
|
|
16
|
+
collapseDynamicRoutes: boolean;
|
|
17
|
+
excludePages?: string[];
|
|
18
|
+
includePages?: string[];
|
|
19
|
+
};
|
|
20
|
+
type ContextValue = {
|
|
21
|
+
config: InternalConfig;
|
|
22
|
+
lastPathRef: MutableRefObject<string | null>;
|
|
23
|
+
};
|
|
24
|
+
export type OneDollarStatsProviderProps = {
|
|
25
|
+
config: ExpoAnalyticsConfig;
|
|
26
|
+
children: ReactNode;
|
|
27
|
+
};
|
|
28
|
+
export declare function OneDollarStatsProvider({ config, children }: OneDollarStatsProviderProps): import("react").FunctionComponentElement<import("react").ProviderProps<ContextValue | null>>;
|
|
29
|
+
type Props = Record<string, string>;
|
|
30
|
+
export type AnalyticsAPI = {
|
|
31
|
+
event(eventName: string, pathOrProps?: string | Props, props?: Props): void;
|
|
32
|
+
view(pathOrProps?: string | Props, props?: Props): void;
|
|
33
|
+
};
|
|
34
|
+
export declare function useAnalytics(): AnalyticsAPI;
|
|
35
|
+
export {};
|
package/dist/expo.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
createElement,
|
|
4
|
+
useCallback,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef
|
|
9
|
+
} from "react";
|
|
10
|
+
import { AppState, Platform } from "react-native";
|
|
11
|
+
import { usePathname, useSegments } from "expo-router";
|
|
12
|
+
const Context = createContext(null);
|
|
13
|
+
function mergeConfig(config) {
|
|
14
|
+
return {
|
|
15
|
+
hostname: config.hostname,
|
|
16
|
+
collectorUrl: config.collectorUrl ?? "https://collector.onedollarstats.com/events",
|
|
17
|
+
autocollect: config.autocollect ?? true,
|
|
18
|
+
devmode: config.devmode ?? false,
|
|
19
|
+
collapseDynamicRoutes: config.collapseDynamicRoutes ?? true,
|
|
20
|
+
excludePages: config.excludePages,
|
|
21
|
+
includePages: config.includePages
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function isGroupSegment(segment) {
|
|
25
|
+
return /^\(.+\)$/.test(segment);
|
|
26
|
+
}
|
|
27
|
+
function collapsePath(segments) {
|
|
28
|
+
const visible = segments.filter((s) => !isGroupSegment(s));
|
|
29
|
+
if (visible.length === 0) return "/";
|
|
30
|
+
return "/" + visible.join("/");
|
|
31
|
+
}
|
|
32
|
+
function useTrackedPath(config) {
|
|
33
|
+
const pathname = usePathname();
|
|
34
|
+
const segments = useSegments();
|
|
35
|
+
return config.collapseDynamicRoutes ? collapsePath(segments) : pathname;
|
|
36
|
+
}
|
|
37
|
+
function isWebLocalhost() {
|
|
38
|
+
if (Platform.OS !== "web") return false;
|
|
39
|
+
if (typeof window === "undefined" || !window.location) return false;
|
|
40
|
+
const { hostname, protocol } = window.location;
|
|
41
|
+
return /^localhost$|^127(\.[0-9]+){0,2}\.[0-9]+$|^\[::1?\]$/.test(hostname) && (protocol === "http:" || protocol === "https:");
|
|
42
|
+
}
|
|
43
|
+
function useRequiredContext(caller) {
|
|
44
|
+
const ctx = useContext(Context);
|
|
45
|
+
if (!ctx) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`[onedollarstats] ${caller} must be used inside <OneDollarStatsProvider>. Wrap your root layout with the provider.`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return ctx;
|
|
51
|
+
}
|
|
52
|
+
function OneDollarStatsProvider({ config, children }) {
|
|
53
|
+
const merged = useMemo(
|
|
54
|
+
() => mergeConfig(config),
|
|
55
|
+
[
|
|
56
|
+
config.hostname,
|
|
57
|
+
config.collectorUrl,
|
|
58
|
+
config.autocollect,
|
|
59
|
+
config.devmode,
|
|
60
|
+
config.collapseDynamicRoutes,
|
|
61
|
+
config.excludePages,
|
|
62
|
+
config.includePages
|
|
63
|
+
]
|
|
64
|
+
);
|
|
65
|
+
const lastPathRef = useRef(null);
|
|
66
|
+
const announcedRef = useRef(false);
|
|
67
|
+
const trackedPath = useTrackedPath(merged);
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (announcedRef.current) return;
|
|
70
|
+
if (merged.devmode && isWebLocalhost()) {
|
|
71
|
+
console.log(
|
|
72
|
+
`[onedollarstats]
|
|
73
|
+
OneDollarStats connected! Tracking localhost as ${merged.hostname}`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
announcedRef.current = true;
|
|
77
|
+
}, [merged.devmode, merged.hostname]);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!merged.autocollect) return;
|
|
80
|
+
if (isExcluded(trackedPath, merged)) return;
|
|
81
|
+
if (lastPathRef.current === trackedPath) return;
|
|
82
|
+
lastPathRef.current = trackedPath;
|
|
83
|
+
send("PageView", trackedPath, merged);
|
|
84
|
+
}, [trackedPath, merged]);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
const handler = (state) => {
|
|
87
|
+
if (state !== "active") return;
|
|
88
|
+
if (!merged.autocollect) return;
|
|
89
|
+
const current = lastPathRef.current;
|
|
90
|
+
if (!current || isExcluded(current, merged)) return;
|
|
91
|
+
send("PageView", current, merged);
|
|
92
|
+
};
|
|
93
|
+
const sub = AppState.addEventListener("change", handler);
|
|
94
|
+
return () => sub.remove();
|
|
95
|
+
}, [merged]);
|
|
96
|
+
const value = useMemo(
|
|
97
|
+
() => ({ config: merged, lastPathRef }),
|
|
98
|
+
[merged]
|
|
99
|
+
);
|
|
100
|
+
return createElement(Context.Provider, { value }, children);
|
|
101
|
+
}
|
|
102
|
+
function useAnalytics() {
|
|
103
|
+
const ctx = useRequiredContext("useAnalytics");
|
|
104
|
+
const trackedPath = useTrackedPath(ctx.config);
|
|
105
|
+
const event = useCallback(
|
|
106
|
+
(eventName, pathOrProps, props) => {
|
|
107
|
+
const targetPath = typeof pathOrProps === "string" ? pathOrProps : trackedPath;
|
|
108
|
+
const eventProps = typeof pathOrProps === "object" ? pathOrProps : props;
|
|
109
|
+
send(eventName, targetPath, ctx.config, eventProps);
|
|
110
|
+
},
|
|
111
|
+
[ctx, trackedPath]
|
|
112
|
+
);
|
|
113
|
+
const view = useCallback(
|
|
114
|
+
(pathOrProps, props) => {
|
|
115
|
+
const targetPath = typeof pathOrProps === "string" ? pathOrProps : trackedPath;
|
|
116
|
+
const viewProps = typeof pathOrProps === "object" ? pathOrProps : props;
|
|
117
|
+
send("PageView", targetPath, ctx.config, viewProps);
|
|
118
|
+
},
|
|
119
|
+
[ctx, trackedPath]
|
|
120
|
+
);
|
|
121
|
+
return { event, view };
|
|
122
|
+
}
|
|
123
|
+
function shouldSkipSend(config) {
|
|
124
|
+
if (Platform.OS !== "web") return false;
|
|
125
|
+
if (isWebLocalhost() && !config.devmode) return true;
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
function devLog(label, url, props) {
|
|
129
|
+
let msg = `[onedollarstats]
|
|
130
|
+
Event name: ${label}
|
|
131
|
+
Event collected from: ${url}`;
|
|
132
|
+
if (props && Object.keys(props).length > 0) {
|
|
133
|
+
msg += `
|
|
134
|
+
Props: ${JSON.stringify(props, null, 2)}`;
|
|
135
|
+
}
|
|
136
|
+
console.log(msg);
|
|
137
|
+
}
|
|
138
|
+
const SAFE_GET_THRESHOLD = 1500;
|
|
139
|
+
function send(eventName, path, config, props) {
|
|
140
|
+
if (shouldSkipSend(config)) return;
|
|
141
|
+
const url = `https://${config.hostname}${path}`;
|
|
142
|
+
if (config.devmode) devLog(eventName, url, props);
|
|
143
|
+
const body = JSON.stringify({
|
|
144
|
+
u: url,
|
|
145
|
+
e: [{ t: eventName, ...props && { p: props } }]
|
|
146
|
+
});
|
|
147
|
+
if (Platform.OS === "web") {
|
|
148
|
+
sendWeb(config.collectorUrl, body);
|
|
149
|
+
} else {
|
|
150
|
+
sendNative(config.collectorUrl, body);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function sendNative(collectorUrl, body) {
|
|
154
|
+
fetch(collectorUrl, {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: { "Content-Type": "application/json" },
|
|
157
|
+
body
|
|
158
|
+
}).catch(() => {
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function sendWeb(collectorUrl, body) {
|
|
162
|
+
const bytes = new TextEncoder().encode(body);
|
|
163
|
+
const bin = String.fromCharCode(...bytes);
|
|
164
|
+
const payloadBase64 = btoa(bin);
|
|
165
|
+
if (payloadBase64.length <= SAFE_GET_THRESHOLD) {
|
|
166
|
+
const img = new Image(1, 1);
|
|
167
|
+
img.onerror = () => sendBeaconOrFetch(collectorUrl, body);
|
|
168
|
+
img.src = `${collectorUrl}?data=${payloadBase64}`;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
sendBeaconOrFetch(collectorUrl, body);
|
|
172
|
+
}
|
|
173
|
+
function sendBeaconOrFetch(collectorUrl, body) {
|
|
174
|
+
if (typeof navigator !== "undefined" && navigator.sendBeacon?.(collectorUrl, body)) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
fetch(collectorUrl, {
|
|
178
|
+
method: "POST",
|
|
179
|
+
headers: { "Content-Type": "application/json" },
|
|
180
|
+
body,
|
|
181
|
+
keepalive: true
|
|
182
|
+
}).catch(() => {
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
function pathMatches(path, prefix) {
|
|
186
|
+
return path === prefix || path.startsWith(prefix.endsWith("/") ? prefix : prefix + "/");
|
|
187
|
+
}
|
|
188
|
+
function isExcluded(path, config) {
|
|
189
|
+
if (config.includePages?.length) {
|
|
190
|
+
return !config.includePages.some((p) => pathMatches(path, p));
|
|
191
|
+
}
|
|
192
|
+
if (config.excludePages?.length) {
|
|
193
|
+
return config.excludePages.some((p) => pathMatches(path, p));
|
|
194
|
+
}
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
export {
|
|
198
|
+
OneDollarStatsProvider,
|
|
199
|
+
useAnalytics
|
|
200
|
+
};
|
|
201
|
+
//# sourceMappingURL=expo.js.map
|
package/dist/expo.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/expo.ts"],
|
|
4
|
+
"sourcesContent": ["import {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n type MutableRefObject,\n type ReactNode\n} from 'react';\nimport { AppState, Platform, type AppStateStatus } from 'react-native';\nimport { usePathname, useSegments } from 'expo-router';\n\nexport type ExpoAnalyticsConfig = {\n hostname: string;\n collectorUrl?: string;\n excludePages?: string[];\n includePages?: string[];\n autocollect?: boolean;\n devmode?: boolean;\n collapseDynamicRoutes?: boolean;\n};\n\ntype InternalConfig = {\n hostname: string;\n collectorUrl: string;\n autocollect: boolean;\n devmode: boolean;\n collapseDynamicRoutes: boolean;\n excludePages?: string[];\n includePages?: string[];\n};\n\n// TODO(page-scope): restore when page-scope detection is designed.\n/*\ntype OverrideSource = 'hook' | 'component';\ntype Override = { realPath: string; customPath: string; source: OverrideSource } | null;\ntype PropsOverride =\n | { realPath: string; props: Record<string, string>; source: OverrideSource }\n | null;\n*/\n\ntype ContextValue = {\n config: InternalConfig;\n lastPathRef: MutableRefObject<string | null>;\n // TODO(page-scope): restore when page-scope detection is designed.\n // overrideRef: MutableRefObject<Override>;\n // propsOverrideRef: MutableRefObject<PropsOverride>;\n};\n\nconst Context = createContext<ContextValue | null>(null);\n\n// TODO(page-scope): restore when page-scope detection is designed.\n/*\nfunction resolvePath(pathname: string, overrideRef: MutableRefObject<Override>): string {\n const o = overrideRef.current;\n return o && o.realPath === pathname ? o.customPath : pathname;\n}\n\nfunction resolveProps(\n pathname: string,\n propsOverrideRef: MutableRefObject<PropsOverride>\n): Record<string, string> | undefined {\n const o = propsOverrideRef.current;\n return o && o.realPath === pathname ? o.props : undefined;\n}\n\nfunction mergeProps(\n screenProps: Record<string, string> | undefined,\n explicitProps: Record<string, string> | undefined\n): Record<string, string> | undefined {\n if (!screenProps && !explicitProps) return undefined;\n if (!screenProps) return explicitProps;\n if (!explicitProps) return screenProps;\n return { ...screenProps, ...explicitProps };\n}\n*/\n\nfunction mergeConfig(config: ExpoAnalyticsConfig): InternalConfig {\n return {\n hostname: config.hostname,\n collectorUrl: config.collectorUrl ?? 'https://collector.onedollarstats.com/events',\n autocollect: config.autocollect ?? true,\n devmode: config.devmode ?? false,\n collapseDynamicRoutes: config.collapseDynamicRoutes ?? true,\n excludePages: config.excludePages,\n includePages: config.includePages\n };\n}\n\nfunction isGroupSegment(segment: string): boolean {\n return /^\\(.+\\)$/.test(segment);\n}\n\nfunction collapsePath(segments: readonly string[]): string {\n const visible = segments.filter(s => !isGroupSegment(s));\n if (visible.length === 0) return '/';\n return '/' + visible.join('/');\n}\n\nfunction useTrackedPath(config: InternalConfig): string {\n const pathname = usePathname();\n const segments = useSegments();\n return config.collapseDynamicRoutes ? collapsePath(segments as readonly string[]) : pathname;\n}\n\nfunction isWebLocalhost(): boolean {\n if (Platform.OS !== 'web') return false;\n if (typeof window === 'undefined' || !window.location) return false;\n const { hostname, protocol } = window.location;\n return (\n /^localhost$|^127(\\.[0-9]+){0,2}\\.[0-9]+$|^\\[::1?\\]$/.test(hostname) &&\n (protocol === 'http:' || protocol === 'https:')\n );\n}\n\nfunction useRequiredContext(caller: string): ContextValue {\n const ctx = useContext(Context);\n if (!ctx) {\n throw new Error(\n `[onedollarstats] ${caller} must be used inside <OneDollarStatsProvider>. ` +\n `Wrap your root layout with the provider.`\n );\n }\n return ctx;\n}\n\nexport type OneDollarStatsProviderProps = {\n config: ExpoAnalyticsConfig;\n children: ReactNode;\n};\n\nexport function OneDollarStatsProvider({ config, children }: OneDollarStatsProviderProps) {\n const merged = useMemo(\n () => mergeConfig(config),\n [\n config.hostname,\n config.collectorUrl,\n config.autocollect,\n config.devmode,\n config.collapseDynamicRoutes,\n config.excludePages,\n config.includePages\n ]\n );\n\n const lastPathRef = useRef<string | null>(null);\n // TODO(page-scope): restore when page-scope detection is designed.\n // const overrideRef = useRef<Override>(null);\n // const propsOverrideRef = useRef<PropsOverride>(null);\n const announcedRef = useRef(false);\n const trackedPath = useTrackedPath(merged);\n\n useEffect(() => {\n if (announcedRef.current) return;\n if (merged.devmode && isWebLocalhost()) {\n console.log(\n `[onedollarstats]\\nOneDollarStats connected! Tracking localhost as ${merged.hostname}`\n );\n }\n announcedRef.current = true;\n }, [merged.devmode, merged.hostname]);\n\n useEffect(() => {\n if (!merged.autocollect) return;\n if (isExcluded(trackedPath, merged)) return;\n if (lastPathRef.current === trackedPath) return;\n lastPathRef.current = trackedPath;\n send('PageView', trackedPath, merged);\n }, [trackedPath, merged]);\n\n useEffect(() => {\n const handler = (state: AppStateStatus) => {\n if (state !== 'active') return;\n if (!merged.autocollect) return;\n const current = lastPathRef.current;\n if (!current || isExcluded(current, merged)) return;\n send('PageView', current, merged);\n };\n const sub = AppState.addEventListener('change', handler);\n return () => sub.remove();\n }, [merged]);\n\n const value = useMemo<ContextValue>(\n () => ({ config: merged, lastPathRef }),\n [merged]\n );\n\n return createElement(Context.Provider, { value }, children);\n}\n\ntype Props = Record<string, string>;\n\nexport type AnalyticsAPI = {\n event(eventName: string, pathOrProps?: string | Props, props?: Props): void;\n view(pathOrProps?: string | Props, props?: Props): void;\n};\n\nexport function useAnalytics(): AnalyticsAPI {\n const ctx = useRequiredContext('useAnalytics');\n const trackedPath = useTrackedPath(ctx.config);\n\n const event = useCallback(\n (eventName: string, pathOrProps?: string | Props, props?: Props) => {\n const targetPath = typeof pathOrProps === 'string' ? pathOrProps : trackedPath;\n const eventProps = typeof pathOrProps === 'object' ? pathOrProps : props;\n send(eventName, targetPath, ctx.config, eventProps);\n },\n [ctx, trackedPath]\n );\n\n const view = useCallback(\n (pathOrProps?: string | Props, props?: Props) => {\n const targetPath = typeof pathOrProps === 'string' ? pathOrProps : trackedPath;\n const viewProps = typeof pathOrProps === 'object' ? pathOrProps : props;\n send('PageView', targetPath, ctx.config, viewProps);\n },\n [ctx, trackedPath]\n );\n\n return { event, view };\n}\n\n// TODO(page-scope): restore when page-scope detection is designed.\n/*\nexport function useAnalyticsPath(customPath: string): void {\n const ctx = useRequiredContext('useAnalyticsPath');\n const pathname = usePathname();\n useEffect(() => {\n const entry: Override = { realPath: pathname, customPath, source: 'hook' };\n ctx.overrideRef.current = entry;\n return () => {\n if (ctx.overrideRef.current === entry) ctx.overrideRef.current = null;\n };\n }, [ctx, pathname, customPath]);\n}\n\nexport type AnalyticsPathProps = { path: string };\n\nexport function AnalyticsPath({ path }: AnalyticsPathProps): null {\n const ctx = useRequiredContext('AnalyticsPath');\n const pathname = usePathname();\n useEffect(() => {\n const existing = ctx.overrideRef.current;\n if (existing && existing.realPath === pathname && existing.source === 'hook') return;\n const entry: Override = { realPath: pathname, customPath: path, source: 'component' };\n ctx.overrideRef.current = entry;\n return () => {\n if (ctx.overrideRef.current === entry) ctx.overrideRef.current = null;\n };\n }, [ctx, pathname, path]);\n return null;\n}\n\nexport function useAnalyticsProps(props: Record<string, string>): void {\n const ctx = useRequiredContext('useAnalyticsProps');\n const pathname = usePathname();\n useEffect(() => {\n const entry: PropsOverride = { realPath: pathname, props, source: 'hook' };\n ctx.propsOverrideRef.current = entry;\n return () => {\n if (ctx.propsOverrideRef.current === entry) ctx.propsOverrideRef.current = null;\n };\n }, [ctx, pathname, props]);\n}\n\nexport type AnalyticsPropsProps = Record<string, string>;\n\nexport function AnalyticsProps(props: AnalyticsPropsProps): null {\n const ctx = useRequiredContext('AnalyticsProps');\n const pathname = usePathname();\n useEffect(() => {\n const existing = ctx.propsOverrideRef.current;\n if (existing && existing.realPath === pathname && existing.source === 'hook') return;\n const entry: PropsOverride = { realPath: pathname, props, source: 'component' };\n ctx.propsOverrideRef.current = entry;\n return () => {\n if (ctx.propsOverrideRef.current === entry) ctx.propsOverrideRef.current = null;\n };\n }, [ctx, pathname, props]);\n return null;\n}\n*/\n\nfunction shouldSkipSend(config: InternalConfig): boolean {\n if (Platform.OS !== 'web') return false;\n if (isWebLocalhost() && !config.devmode) return true;\n return false;\n}\n\nfunction devLog(label: string, url: string, props?: Record<string, string>): void {\n let msg = `[onedollarstats]\\nEvent name: ${label}\\nEvent collected from: ${url}`;\n if (props && Object.keys(props).length > 0) {\n msg += `\\nProps: ${JSON.stringify(props, null, 2)}`;\n }\n console.log(msg);\n}\n\nconst SAFE_GET_THRESHOLD = 1500;\n\nfunction send(\n eventName: string,\n path: string,\n config: InternalConfig,\n props?: Record<string, string>\n): void {\n if (shouldSkipSend(config)) return;\n const url = `https://${config.hostname}${path}`;\n if (config.devmode) devLog(eventName, url, props);\n\n const body = JSON.stringify({\n u: url,\n e: [{ t: eventName, ...(props && { p: props }) }]\n });\n\n if (Platform.OS === 'web') {\n sendWeb(config.collectorUrl, body);\n } else {\n sendNative(config.collectorUrl, body);\n }\n}\n\nfunction sendNative(collectorUrl: string, body: string): void {\n fetch(collectorUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body\n }).catch(() => {});\n}\n\nfunction sendWeb(collectorUrl: string, body: string): void {\n const bytes = new TextEncoder().encode(body);\n const bin = String.fromCharCode(...bytes);\n const payloadBase64 = btoa(bin);\n\n if (payloadBase64.length <= SAFE_GET_THRESHOLD) {\n const img = new Image(1, 1);\n img.onerror = () => sendBeaconOrFetch(collectorUrl, body);\n img.src = `${collectorUrl}?data=${payloadBase64}`;\n return;\n }\n\n sendBeaconOrFetch(collectorUrl, body);\n}\n\nfunction sendBeaconOrFetch(collectorUrl: string, body: string): void {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon?.(collectorUrl, body)) {\n return;\n }\n\n fetch(collectorUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n keepalive: true\n }).catch(() => {});\n}\n\nfunction pathMatches(path: string, prefix: string): boolean {\n return path === prefix || path.startsWith(prefix.endsWith('/') ? prefix : prefix + '/');\n}\n\nfunction isExcluded(path: string, config: InternalConfig): boolean {\n if (config.includePages?.length) {\n return !config.includePages.some(p => pathMatches(path, p));\n }\n if (config.excludePages?.length) {\n return config.excludePages.some(p => pathMatches(path, p));\n }\n return false;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,64 +1,5 @@
|
|
|
1
1
|
// src/utils/bot.ts
|
|
2
|
-
var
|
|
3
|
-
// Search engines
|
|
4
|
-
{ pattern: /Googlebot/i, kind: "search_engine", name: "Googlebot" },
|
|
5
|
-
{ pattern: /Google-InspectionTool/i, kind: "search_engine", name: "Googlebot" },
|
|
6
|
-
{ pattern: /Storebot-Google/i, kind: "search_engine", name: "Googlebot" },
|
|
7
|
-
{ pattern: /AdsBot-Google/i, kind: "search_engine", name: "Google Ads" },
|
|
8
|
-
{ pattern: /Mediapartners-Google/i, kind: "search_engine", name: "Google Adsense" },
|
|
9
|
-
{ pattern: /bingbot/i, kind: "search_engine", name: "Bingbot" },
|
|
10
|
-
{ pattern: /msnbot/i, kind: "search_engine", name: "MSNBot" },
|
|
11
|
-
{ pattern: /YandexBot/i, kind: "search_engine", name: "YandexBot" },
|
|
12
|
-
{ pattern: /YandexAccessibilityBot/i, kind: "search_engine", name: "YandexBot" },
|
|
13
|
-
{ pattern: /Baiduspider/i, kind: "search_engine", name: "Baidu" },
|
|
14
|
-
{ pattern: /DuckDuckBot/i, kind: "search_engine", name: "DuckDuckBot" },
|
|
15
|
-
{ pattern: /Sogou/i, kind: "search_engine", name: "Sogou" },
|
|
16
|
-
{ pattern: /Exabot/i, kind: "search_engine", name: "Exabot" },
|
|
17
|
-
{ pattern: /ia_archiver/i, kind: "search_engine", name: "Alexa" },
|
|
18
|
-
{ pattern: /SemrushBot/i, kind: "search_engine", name: "SemrushBot" },
|
|
19
|
-
{ pattern: /AhrefsBot/i, kind: "search_engine", name: "AhrefsBot" },
|
|
20
|
-
{ pattern: /MJ12bot/i, kind: "search_engine", name: "MJ12bot" },
|
|
21
|
-
{ pattern: /DotBot/i, kind: "search_engine", name: "DotBot" },
|
|
22
|
-
{ pattern: /PetalBot/i, kind: "search_engine", name: "PetalBot" },
|
|
23
|
-
{ pattern: /Applebot/i, kind: "search_engine", name: "Applebot" },
|
|
24
|
-
{ pattern: /GPTBot/i, kind: "search_engine", name: "GPTBot" },
|
|
25
|
-
{ pattern: /ChatGPT-User/i, kind: "search_engine", name: "ChatGPT" },
|
|
26
|
-
{ pattern: /ClaudeBot/i, kind: "search_engine", name: "ClaudeBot" },
|
|
27
|
-
{ pattern: /CCBot/i, kind: "search_engine", name: "Common Crawl" },
|
|
28
|
-
{ pattern: /anthropic-ai/i, kind: "search_engine", name: "Anthropic" },
|
|
29
|
-
{ pattern: /PerplexityBot/i, kind: "search_engine", name: "PerplexityBot" },
|
|
30
|
-
// Social crawlers
|
|
31
|
-
{ pattern: /facebookexternalhit/i, kind: "social_crawler", name: "Facebook" },
|
|
32
|
-
{ pattern: /Facebot/i, kind: "social_crawler", name: "Facebook" },
|
|
33
|
-
{ pattern: /Twitterbot/i, kind: "social_crawler", name: "Twitter" },
|
|
34
|
-
{ pattern: /LinkedInBot/i, kind: "social_crawler", name: "LinkedIn" },
|
|
35
|
-
{ pattern: /Slackbot/i, kind: "social_crawler", name: "Slack" },
|
|
36
|
-
{ pattern: /Discordbot/i, kind: "social_crawler", name: "Discord" },
|
|
37
|
-
{ pattern: /TelegramBot/i, kind: "social_crawler", name: "Telegram" },
|
|
38
|
-
{ pattern: /WhatsApp/i, kind: "social_crawler", name: "WhatsApp" },
|
|
39
|
-
{ pattern: /Pinterestbot/i, kind: "social_crawler", name: "Pinterest" },
|
|
40
|
-
{ pattern: /Snapchat/i, kind: "social_crawler", name: "Snapchat" },
|
|
41
|
-
// Headless / automation
|
|
42
|
-
{ pattern: /HeadlessChrome/i, kind: "headless", name: "Headless Chrome" },
|
|
43
|
-
{ pattern: /PhantomJS/i, kind: "headless", name: "PhantomJS" },
|
|
44
|
-
{ pattern: /Selenium/i, kind: "automation", name: "Selenium" },
|
|
45
|
-
{ pattern: /Puppeteer/i, kind: "automation", name: "Puppeteer" },
|
|
46
|
-
// HTTP libraries
|
|
47
|
-
{ pattern: /curl\//i, kind: "library", name: "curl" },
|
|
48
|
-
{ pattern: /Wget\//i, kind: "library", name: "Wget" },
|
|
49
|
-
{ pattern: /python-requests/i, kind: "library", name: "Python Requests" },
|
|
50
|
-
{ pattern: /python-urllib/i, kind: "library", name: "Python urllib" },
|
|
51
|
-
{ pattern: /node-fetch/i, kind: "library", name: "node-fetch" },
|
|
52
|
-
{ pattern: /axios\//i, kind: "library", name: "Axios" },
|
|
53
|
-
{ pattern: /Go-http-client/i, kind: "library", name: "Go HTTP" },
|
|
54
|
-
{ pattern: /Java\//i, kind: "library", name: "Java HTTP" },
|
|
55
|
-
{ pattern: /libwww-perl/i, kind: "library", name: "Perl LWP" },
|
|
56
|
-
{ pattern: /Apache-HttpClient/i, kind: "library", name: "Apache HttpClient" },
|
|
57
|
-
{ pattern: /okhttp/i, kind: "library", name: "OkHttp" },
|
|
58
|
-
{ pattern: /Scrapy/i, kind: "library", name: "Scrapy" },
|
|
59
|
-
// Generic catch-all (must be last)
|
|
60
|
-
{ pattern: /bot|crawl|spider|slurp|fetch|archiver/i, kind: "unknown_bot", name: "generic" }
|
|
61
|
-
];
|
|
2
|
+
var BOT_UA_PATTERN = /Google-InspectionTool|Mediapartners-Google|Sogou|ChatGPT-User|anthropic-ai|facebookexternalhit|WhatsApp|Snapchat|HeadlessChrome|PhantomJS|Selenium|Puppeteer|curl\/|Wget\/|python-requests|python-urllib|axios\/|Go-http-client|Java\/|libwww-perl|Apache-HttpClient|okhttp|Scrapy|bot|crawl|spider|slurp|fetch|archiver/i;
|
|
62
3
|
var AUTOMATION_GLOBALS = [
|
|
63
4
|
// Selenium
|
|
64
5
|
"__selenium_unwrapped",
|
|
@@ -92,14 +33,10 @@ var AUTOMATION_GLOBALS = [
|
|
|
92
33
|
];
|
|
93
34
|
function detectBot() {
|
|
94
35
|
const signals = collectBotSignals();
|
|
95
|
-
const isBot = signals.userAgentBot
|
|
36
|
+
const isBot = signals.userAgentBot || signals.webdriver || signals.headless || signals.automationGlobals.length > 0 || signals.liesDetected > 2 || signals.liesDetected > 0 && signals.hasProxy;
|
|
96
37
|
let botKind = "human";
|
|
97
38
|
if (isBot) {
|
|
98
|
-
if (signals.
|
|
99
|
-
const ua = navigator.userAgent || "";
|
|
100
|
-
const match = BOT_PATTERNS.find((p) => p.pattern.test(ua));
|
|
101
|
-
botKind = match?.kind ?? "unknown_bot";
|
|
102
|
-
} else if (signals.headless) {
|
|
39
|
+
if (signals.headless) {
|
|
103
40
|
botKind = "headless";
|
|
104
41
|
} else if (signals.webdriver || signals.automationGlobals.length > 0) {
|
|
105
42
|
botKind = "automation";
|
|
@@ -122,11 +59,8 @@ function collectBotSignals() {
|
|
|
122
59
|
}
|
|
123
60
|
function detectUserAgentBot() {
|
|
124
61
|
const ua = navigator.userAgent || "";
|
|
125
|
-
if (!ua) return
|
|
126
|
-
|
|
127
|
-
if (pattern.test(ua)) return name;
|
|
128
|
-
}
|
|
129
|
-
return null;
|
|
62
|
+
if (!ua) return true;
|
|
63
|
+
return BOT_UA_PATTERN.test(ua);
|
|
130
64
|
}
|
|
131
65
|
function detectWebdriver() {
|
|
132
66
|
return !!navigator.webdriver;
|
|
@@ -160,50 +94,34 @@ function detectLies() {
|
|
|
160
94
|
let liesDetected = 0;
|
|
161
95
|
let hasProxy = false;
|
|
162
96
|
const apisToTest = [
|
|
163
|
-
[
|
|
164
|
-
[
|
|
165
|
-
[
|
|
166
|
-
[
|
|
167
|
-
[
|
|
168
|
-
[
|
|
169
|
-
[
|
|
170
|
-
[
|
|
97
|
+
[Navigator.prototype, "userAgent"],
|
|
98
|
+
[Navigator.prototype, "languages"],
|
|
99
|
+
[Navigator.prototype, "platform"],
|
|
100
|
+
[Navigator.prototype, "hardwareConcurrency"],
|
|
101
|
+
[Navigator.prototype, "webdriver"],
|
|
102
|
+
[HTMLCanvasElement.prototype, "toDataURL"],
|
|
103
|
+
[CanvasRenderingContext2D.prototype, "fillText"],
|
|
104
|
+
[Date.prototype, "getTimezoneOffset"]
|
|
171
105
|
];
|
|
172
|
-
for (const [
|
|
106
|
+
for (const [proto, prop] of apisToTest) {
|
|
173
107
|
try {
|
|
174
|
-
const
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const proto = safeProto(protoName);
|
|
186
|
-
if (!proto) continue;
|
|
187
|
-
const d = Object.getOwnPropertyDescriptor(proto, prop);
|
|
188
|
-
if (d?.get) {
|
|
189
|
-
const gs = Function.prototype.toString.call(d.get);
|
|
190
|
-
if (!isNativeToString(gs)) liesDetected++;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
if (typeof val === "function") {
|
|
195
|
-
if (val.toString !== Function.prototype.toString) {
|
|
196
|
-
try {
|
|
197
|
-
const native = Function.prototype.toString.call(val);
|
|
198
|
-
const custom = val.toString();
|
|
199
|
-
if (native !== custom) {
|
|
200
|
-
liesDetected++;
|
|
201
|
-
hasProxy = true;
|
|
202
|
-
}
|
|
203
|
-
} catch {
|
|
108
|
+
const d = Object.getOwnPropertyDescriptor(proto, prop);
|
|
109
|
+
if (!d) continue;
|
|
110
|
+
const fn = d.value ?? d.get;
|
|
111
|
+
if (typeof fn !== "function") continue;
|
|
112
|
+
const str = Function.prototype.toString.call(fn);
|
|
113
|
+
if (!isNativeToString(str)) liesDetected++;
|
|
114
|
+
if (d.value && fn.toString !== Function.prototype.toString) {
|
|
115
|
+
try {
|
|
116
|
+
const native = Function.prototype.toString.call(fn);
|
|
117
|
+
const custom = fn.toString();
|
|
118
|
+
if (native !== custom) {
|
|
204
119
|
liesDetected++;
|
|
205
120
|
hasProxy = true;
|
|
206
121
|
}
|
|
122
|
+
} catch {
|
|
123
|
+
liesDetected++;
|
|
124
|
+
hasProxy = true;
|
|
207
125
|
}
|
|
208
126
|
}
|
|
209
127
|
} catch {
|
|
@@ -227,16 +145,6 @@ function detectMissingPlugins() {
|
|
|
227
145
|
function isNativeToString(str) {
|
|
228
146
|
return /^function\s[^{]*\{\s*\[native code\]\s*\}$/.test(str) || str === "function () { [native code] }" || /^\(\)\s*=>\s*\{\s*\[native code\]\s*\}$/.test(str);
|
|
229
147
|
}
|
|
230
|
-
function desc(proto, prop) {
|
|
231
|
-
return Object.getOwnPropertyDescriptor(proto, prop);
|
|
232
|
-
}
|
|
233
|
-
function safeProto(name) {
|
|
234
|
-
try {
|
|
235
|
-
return window[name]?.prototype ?? null;
|
|
236
|
-
} catch {
|
|
237
|
-
return null;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
148
|
|
|
241
149
|
// src/utils/environment.ts
|
|
242
150
|
var getEnvironment = () => ({
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/utils/bot.ts", "../src/utils/environment.ts", "../src/utils/merge-config.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": ["/**\n * Bot & crawler detection module.\n *\n * Detects:\n * - Known search engine crawlers (Googlebot, Bingbot, Yandex, Baidu, etc.)\n * - Social media crawlers (Facebook, Twitter, LinkedIn, etc.)\n * - Headless browsers (Puppeteer, Playwright, Selenium, PhantomJS)\n * - General automation tools via navigator.webdriver and injected globals\n * - API tampering / lie detection (prototype spoofing, proxy wrapping)\n */\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type BotKind =\n | 'search_engine' // Googlebot, Bingbot, Yandex, Baidu, DuckDuckBot, etc.\n | 'social_crawler' // Facebook, Twitter/X, LinkedIn, Slack, Discord, etc.\n | 'headless' // Headless Chrome/Firefox, PhantomJS\n | 'automation' // Selenium, Puppeteer, Playwright (non-headless)\n | 'library' // curl, wget, python-requests, node-fetch, etc.\n | 'unknown_bot' // Signals say bot, but can't classify further\n | 'human' // No bot signals detected\n\nexport interface BotSignals {\n /** navigator.userAgent matched a known bot pattern. */\n userAgentBot: string | null\n /** navigator.webdriver is true. */\n webdriver: boolean\n /** Headless browser indicators detected. */\n headless: boolean\n /** Automation globals found on window. */\n automationGlobals: string[]\n /** Number of API lies (toString/proxy tampering) detected. */\n liesDetected: number\n /** Proxy wrapping detected on native functions. */\n hasProxy: boolean\n /** navigator.languages is empty or missing. */\n missingLanguages: boolean\n /** navigator.plugins is empty (non-mobile). */\n missingPlugins: boolean\n}\n\nexport interface BotDetectionResult {\n /** True when any bot signal fires. */\n isBot: boolean\n /** Classified category of the detected bot. */\n botKind: BotKind\n /** Individual signal results for debugging / logging. */\n signals: BotSignals\n}\n\n// ---------------------------------------------------------------------------\n// Known bot UA patterns\n// ---------------------------------------------------------------------------\n\ninterface BotPattern {\n pattern: RegExp\n kind: BotKind\n name: string\n}\n\nconst BOT_PATTERNS: BotPattern[] = [\n // Search engines\n { pattern: /Googlebot/i, kind: 'search_engine', name: 'Googlebot' },\n { pattern: /Google-InspectionTool/i, kind: 'search_engine', name: 'Googlebot' },\n { pattern: /Storebot-Google/i, kind: 'search_engine', name: 'Googlebot' },\n { pattern: /AdsBot-Google/i, kind: 'search_engine', name: 'Google Ads' },\n { pattern: /Mediapartners-Google/i, kind: 'search_engine', name: 'Google Adsense' },\n { pattern: /bingbot/i, kind: 'search_engine', name: 'Bingbot' },\n { pattern: /msnbot/i, kind: 'search_engine', name: 'MSNBot' },\n { pattern: /YandexBot/i, kind: 'search_engine', name: 'YandexBot' },\n { pattern: /YandexAccessibilityBot/i, kind: 'search_engine', name: 'YandexBot' },\n { pattern: /Baiduspider/i, kind: 'search_engine', name: 'Baidu' },\n { pattern: /DuckDuckBot/i, kind: 'search_engine', name: 'DuckDuckBot' },\n { pattern: /Sogou/i, kind: 'search_engine', name: 'Sogou' },\n { pattern: /Exabot/i, kind: 'search_engine', name: 'Exabot' },\n { pattern: /ia_archiver/i, kind: 'search_engine', name: 'Alexa' },\n { pattern: /SemrushBot/i, kind: 'search_engine', name: 'SemrushBot' },\n { pattern: /AhrefsBot/i, kind: 'search_engine', name: 'AhrefsBot' },\n { pattern: /MJ12bot/i, kind: 'search_engine', name: 'MJ12bot' },\n { pattern: /DotBot/i, kind: 'search_engine', name: 'DotBot' },\n { pattern: /PetalBot/i, kind: 'search_engine', name: 'PetalBot' },\n { pattern: /Applebot/i, kind: 'search_engine', name: 'Applebot' },\n { pattern: /GPTBot/i, kind: 'search_engine', name: 'GPTBot' },\n { pattern: /ChatGPT-User/i, kind: 'search_engine', name: 'ChatGPT' },\n { pattern: /ClaudeBot/i, kind: 'search_engine', name: 'ClaudeBot' },\n { pattern: /CCBot/i, kind: 'search_engine', name: 'Common Crawl' },\n { pattern: /anthropic-ai/i, kind: 'search_engine', name: 'Anthropic' },\n { pattern: /PerplexityBot/i, kind: 'search_engine', name: 'PerplexityBot' },\n\n // Social crawlers\n { pattern: /facebookexternalhit/i, kind: 'social_crawler', name: 'Facebook' },\n { pattern: /Facebot/i, kind: 'social_crawler', name: 'Facebook' },\n { pattern: /Twitterbot/i, kind: 'social_crawler', name: 'Twitter' },\n { pattern: /LinkedInBot/i, kind: 'social_crawler', name: 'LinkedIn' },\n { pattern: /Slackbot/i, kind: 'social_crawler', name: 'Slack' },\n { pattern: /Discordbot/i, kind: 'social_crawler', name: 'Discord' },\n { pattern: /TelegramBot/i, kind: 'social_crawler', name: 'Telegram' },\n { pattern: /WhatsApp/i, kind: 'social_crawler', name: 'WhatsApp' },\n { pattern: /Pinterestbot/i, kind: 'social_crawler', name: 'Pinterest' },\n { pattern: /Snapchat/i, kind: 'social_crawler', name: 'Snapchat' },\n\n // Headless / automation\n { pattern: /HeadlessChrome/i, kind: 'headless', name: 'Headless Chrome' },\n { pattern: /PhantomJS/i, kind: 'headless', name: 'PhantomJS' },\n { pattern: /Selenium/i, kind: 'automation', name: 'Selenium' },\n { pattern: /Puppeteer/i, kind: 'automation', name: 'Puppeteer' },\n\n // HTTP libraries\n { pattern: /curl\\//i, kind: 'library', name: 'curl' },\n { pattern: /Wget\\//i, kind: 'library', name: 'Wget' },\n { pattern: /python-requests/i, kind: 'library', name: 'Python Requests' },\n { pattern: /python-urllib/i, kind: 'library', name: 'Python urllib' },\n { pattern: /node-fetch/i, kind: 'library', name: 'node-fetch' },\n { pattern: /axios\\//i, kind: 'library', name: 'Axios' },\n { pattern: /Go-http-client/i, kind: 'library', name: 'Go HTTP' },\n { pattern: /Java\\//i, kind: 'library', name: 'Java HTTP' },\n { pattern: /libwww-perl/i, kind: 'library', name: 'Perl LWP' },\n { pattern: /Apache-HttpClient/i, kind: 'library', name: 'Apache HttpClient' },\n { pattern: /okhttp/i, kind: 'library', name: 'OkHttp' },\n { pattern: /Scrapy/i, kind: 'library', name: 'Scrapy' },\n\n // Generic catch-all (must be last)\n { pattern: /bot|crawl|spider|slurp|fetch|archiver/i, kind: 'unknown_bot', name: 'generic' },\n]\n\n// ---------------------------------------------------------------------------\n// Automation globals injected by common frameworks\n// ---------------------------------------------------------------------------\n\nconst AUTOMATION_GLOBALS = [\n // Selenium\n '__selenium_unwrapped',\n '__selenium_evaluate',\n '__webdriver_evaluate',\n '__webdriver_script_fn',\n '__webdriver_script_func',\n '__webdriver_script_function',\n '__fxdriver_evaluate',\n '__fxdriver_unwrapped',\n '_Selenium_IDE_Recorder',\n // Puppeteer / CDP\n '__puppeteer_evaluation_script__',\n // PhantomJS\n 'callPhantom',\n '_phantom',\n 'phantom',\n // Nightmare.js\n '__nightmare',\n // Playwright (injects page.exposeFunction bindings)\n '__playwright',\n '__pw_manual',\n // CasperJS\n '__casper',\n // TestCafe\n '__testcafe',\n // WebDriver (generic)\n 'webdriver',\n 'domAutomation',\n 'domAutomationController',\n] as const\n\n// ---------------------------------------------------------------------------\n// Core detection\n// ---------------------------------------------------------------------------\n\n/**\n * Detect whether the current browser context is a bot or crawler.\n * Synchronous and lightweight \u2014 no async APIs needed.\n */\nexport function detectBot(): BotDetectionResult {\n const signals = collectBotSignals()\n\n const isBot =\n signals.userAgentBot !== null ||\n signals.webdriver ||\n signals.headless ||\n signals.automationGlobals.length > 0 ||\n signals.liesDetected > 2 ||\n (signals.liesDetected > 0 && signals.hasProxy)\n\n let botKind: BotKind = 'human'\n if (isBot) {\n if (signals.userAgentBot !== null) {\n // Find the matching pattern to determine kind\n const ua = navigator.userAgent || ''\n const match = BOT_PATTERNS.find(p => p.pattern.test(ua))\n botKind = match?.kind ?? 'unknown_bot'\n } else if (signals.headless) {\n botKind = 'headless'\n } else if (signals.webdriver || signals.automationGlobals.length > 0) {\n botKind = 'automation'\n } else {\n botKind = 'unknown_bot'\n }\n }\n\n return { isBot, botKind, signals }\n}\n\n// ---------------------------------------------------------------------------\n// Signal collectors\n// ---------------------------------------------------------------------------\n\nfunction collectBotSignals(): BotSignals {\n return {\n userAgentBot: detectUserAgentBot(),\n webdriver: detectWebdriver(),\n headless: detectHeadless(),\n automationGlobals: detectAutomationGlobals(),\n ...detectLies(),\n missingLanguages: detectMissingLanguages(),\n missingPlugins: detectMissingPlugins(),\n }\n}\n\n/** Check UA string against known bot patterns. */\nfunction detectUserAgentBot(): string | null {\n const ua = navigator.userAgent || ''\n if (!ua) return 'empty-ua'\n for (const { pattern, name } of BOT_PATTERNS) {\n if (pattern.test(ua)) return name\n }\n return null\n}\n\n/** navigator.webdriver is set by WebDriver-based automation. */\nfunction detectWebdriver(): boolean {\n return !!(navigator as any).webdriver\n}\n\n/** Headless browser indicators. */\nfunction detectHeadless(): boolean {\n const w = window as any\n const n = navigator as any\n\n // Chrome-specific: headless mode omits the chrome runtime object\n if (/Chrome/.test(n.userAgent) && !w.chrome) return true\n\n // HeadlessChrome in UA\n if (/HeadlessChrome/.test(n.userAgent)) return true\n\n // Notification permission is always \"denied\" in headless Chrome\n // (in headed mode it defaults to \"default\")\n try {\n if (Notification.permission === 'denied' && n.permissions) {\n // Double-check: headless also lacks plugins\n if ((!n.plugins || n.plugins.length === 0) && !/Mobile|Android/i.test(n.userAgent)) {\n return true\n }\n }\n } catch { /* permissions API unavailable */ }\n\n return false\n}\n\n/** Detect automation framework globals on window. */\nfunction detectAutomationGlobals(): string[] {\n const w = window as any\n return AUTOMATION_GLOBALS.filter(key => {\n try { return key in w && w[key] !== undefined }\n catch { return false }\n }) as string[]\n}\n\n/**\n * Lie detection \u2014 detect API tampering (proxies, toString spoofing).\n * Extracted from the fingerprint lies module; self-contained here.\n */\nfunction detectLies(): { liesDetected: number; hasProxy: boolean } {\n let liesDetected = 0\n let hasProxy = false\n\n const apisToTest: Array<[string, () => unknown]> = [\n ['Navigator.prototype.userAgent', () => desc(Navigator.prototype, 'userAgent')],\n ['Navigator.prototype.languages', () => desc(Navigator.prototype, 'languages')],\n ['Navigator.prototype.platform', () => desc(Navigator.prototype, 'platform')],\n ['Navigator.prototype.hardwareConcurrency', () => desc(Navigator.prototype, 'hardwareConcurrency')],\n ['Navigator.prototype.webdriver', () => desc(Navigator.prototype, 'webdriver')],\n ['HTMLCanvasElement.prototype.toDataURL', () => HTMLCanvasElement.prototype.toDataURL],\n ['CanvasRenderingContext2D.prototype.fillText', () => CanvasRenderingContext2D.prototype.fillText],\n ['Date.prototype.getTimezoneOffset', () => Date.prototype.getTimezoneOffset],\n ]\n\n for (const [name, accessor] of apisToTest) {\n try {\n const val = accessor()\n if (val === undefined || val === null) continue\n\n // toString format check\n if (typeof val === 'function') {\n const str = Function.prototype.toString.call(val)\n if (!isNativeToString(str)) liesDetected++\n }\n\n // Getter integrity check\n if (name.includes('.prototype.') && typeof val !== 'function') {\n const parts = name.split('.')\n const protoName = parts[0]\n const prop = parts[parts.length - 1]\n if (protoName && prop) {\n const proto = safeProto(protoName)\n if (!proto) continue\n const d = Object.getOwnPropertyDescriptor(proto, prop)\n if (d?.get) {\n const gs = Function.prototype.toString.call(d.get)\n if (!isNativeToString(gs)) liesDetected++\n }\n }\n }\n\n // Proxy detection\n if (typeof val === 'function') {\n if (val.toString !== Function.prototype.toString) {\n try {\n const native = Function.prototype.toString.call(val)\n const custom = val.toString()\n if (native !== custom) { liesDetected++; hasProxy = true }\n } catch { liesDetected++; hasProxy = true }\n }\n }\n } catch { /* skip inaccessible */ }\n }\n\n // Meta-test: toString integrity\n try {\n const s = Function.prototype.toString.call(Function.prototype.toString)\n if (!isNativeToString(s)) liesDetected++\n } catch { /* skip */ }\n\n return { liesDetected, hasProxy }\n}\n\n/** Missing navigator.languages is a strong headless indicator. */\nfunction detectMissingLanguages(): boolean {\n const langs = navigator.languages\n return !langs || langs.length === 0\n}\n\n/** Missing plugins on desktop is a headless indicator. */\nfunction detectMissingPlugins(): boolean {\n if (/Mobile|Android/i.test(navigator.userAgent)) return false\n return !navigator.plugins || navigator.plugins.length === 0\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction isNativeToString(str: string): boolean {\n return /^function\\s[^{]*\\{\\s*\\[native code\\]\\s*\\}$/.test(str) ||\n str === 'function () { [native code] }' ||\n /^\\(\\)\\s*=>\\s*\\{\\s*\\[native code\\]\\s*\\}$/.test(str)\n}\n\nfunction desc(proto: object, prop: string) {\n return Object.getOwnPropertyDescriptor(proto, prop)\n}\n\nfunction safeProto(name: string): object | null {\n try { return (window as any)[name]?.prototype ?? null }\n catch { return null }\n}\n", "export const getEnvironment = (): {\n isLocalhost: boolean;\n isHeadlessBrowser: boolean;\n} => ({\n isLocalhost:\n (/^localhost$|^127(\\.[0-9]+){0,2}\\.[0-9]+$|^\\[::1?\\]$/.test(location.hostname) &&\n (location.protocol === \"http:\" || location.protocol === \"https:\")) ||\n 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", "import type { AnalyticsConfig, InternalAnalyticsConfig } from \"../types\";\nimport { getEnvironment } from \"./environment\";\n\nexport const defaultConfig: InternalAnalyticsConfig = {\n hostname: null,\n devmode: false,\n collectorUrl: \"https://collector.onedollarstats.com/events\",\n hashRouting: false,\n autocollect: true,\n excludePages: [],\n includePages: []\n};\n\nexport const mergeConfig = (userConfig: AnalyticsConfig = {}): InternalAnalyticsConfig => {\n const { isLocalhost } = getEnvironment();\n\n const devmode = !isLocalhost ? false : (userConfig.devmode ?? !!userConfig.trackLocalhostAs);\n\n let hostname: string | null;\n if (userConfig.hostname) {\n const trimmed = userConfig.hostname.trim();\n hostname = trimmed || null;\n } else if (devmode && userConfig.trackLocalhostAs) {\n hostname = userConfig.trackLocalhostAs;\n } else {\n hostname = null;\n }\n\n // Merge default config, user config, and computed values\n return { ...defaultConfig, ...userConfig, hostname, devmode };\n};\n", "export function parseUtmParams(urlSearchParams: URLSearchParams) {\n const utm: Record<string, string> = {};\n const keys = [\"utm_campaign\", \"utm_source\", \"utm_medium\", \"utm_term\", \"utm_content\"] as const;\n\n for (const key of keys) {\n const raw = urlSearchParams.get(key);\n if (!raw) continue;\n\n const decoded = decodeAndTrim(raw);\n if (decoded) {\n utm[key] = decoded;\n }\n }\n\n return utm;\n}\n\nfunction decodeAndTrim(value: string): string {\n let decoded = value;\n let previous = \"\";\n\n while (decoded !== previous) {\n previous = decoded;\n try {\n decoded = decodeURIComponent(decoded);\n } catch {\n return decoded.trim();\n }\n }\n\n return decoded.trim();\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[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 { InternalAnalyticsConfig } 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: InternalAnalyticsConfig): 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, InternalAnalyticsConfig, ViewArguments } from \"./types\";\nimport { detectBot } from \"./utils/bot\";\nimport { getEnvironment, isClient } from \"./utils/environment\";\nimport { mergeConfig } from \"./utils/merge-config\";\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\ndeclare const DEBUG_SCRIPT_URL: string;\n\nclass AnalyticsTracker {\n private static instance: AnalyticsTracker | null = null;\n\n private autocollectSetupDone = false;\n private config: InternalAnalyticsConfig;\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 = mergeConfig(userConfig);\n\n // Skip setup in non-client environments\n if (!isClient()) return;\n\n const { isLocalhost } = getEnvironment();\n\n // Debug log on localhost\n if (isLocalhost && this.config.devmode && this.config.hostname) {\n console.log(`[onedollarstats]\\nOneDollarStats connected! Tracking localhost as ${this.config.hostname}`);\n\n // Set up debug modal loading: store config + queue, then dynamically load the debug script\n window.__stonksDebugConfig = { hostname: this.config.hostname, collectorUrl: this.config.collectorUrl };\n window.__stonksModalQueue = [];\n window.__stonksModalReady = false;\n\n const debugScript = document.createElement(\"script\");\n debugScript.src = DEBUG_SCRIPT_URL;\n debugScript.onerror = () => {\n // If the debug script fails to load, mark as ready so queued events are not stuck\n window.__stonksModalReady = true;\n };\n document.head.appendChild(debugScript);\n }\n\n // Auto-start autocollect (always set up listeners; handlePageView checks config)\n this.setupAutocollect();\n }\n\n private async sendWithBeaconOrFetch(stringifiedBody: string, callback: (success: boolean) => void): Promise<void> {\n // First fallback: try sendBeacon\n if (navigator.sendBeacon?.(this.config.collectorUrl, stringifiedBody)) {\n callback(true);\n return;\n }\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 })\n .then(({ ok }) => callback(ok))\n .catch((err: Error) => {\n console.error(\"[onedollarstats] fetch() failed:\", err.message);\n callback(false);\n });\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.devmode) || isHeadlessBrowser) return;\n\n const { isBot, botKind } = detectBot();\n\n if (isBot && botKind !== \"human\") return;\n\n const urlToSend = new URL(this.config.hostname ? `https://${this.config.hostname}${location.pathname}` : location.href);\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 debug: this.config.devmode\n };\n\n if (data.utm && Object.keys(data.utm).length > 0) body.qs = data.utm;\n\n if (body.debug) {\n let logMessage = `[onedollarstats]\\nEvent name: ${data.type}\\nEvent collected from: ${cleanUrl}`;\n if (data.props && Object.keys(data.props).length > 0) logMessage += `\\nProps: ${JSON.stringify(data.props, null, 2)}`;\n if (referrer) logMessage += `\\nReferrer: ${referrer}`;\n if (this.config.hashRouting) logMessage += `\\nHashRouting: ${this.config.hashRouting}`;\n if (data.utm && Object.keys(data.utm).length > 0) logMessage += `\\nUTM: ${data.utm}`;\n\n console.log(logMessage);\n }\n\n // Prepare the event payload\n const stringifiedBody = JSON.stringify(body);\n\n // Encode for safe inclusion in a query string (UTF-8 \u2192 Base64)\n const bytes = new TextEncoder().encode(stringifiedBody); // UTF-8 \u2192 bytes\n const bin = String.fromCharCode(...bytes); // bytes \u2192 binary string\n const payloadBase64 = btoa(bin); // binary \u2192 Base64\n\n const safeGetThreshold = 1500; // limit for query-string-containing URLs\n const tryImageBeacon = payloadBase64.length <= safeGetThreshold;\n\n const onComplete = (success: boolean) => {\n const message = `${data.type} ${success ? \"sent\" : \"failed to send\"}`;\n if (window.__stonksModalReady) {\n window.__stonksModalLog?.(message, success);\n } else {\n window.__stonksModalQueue?.push([message, success]);\n }\n };\n\n if (tryImageBeacon) {\n // Send via image beacon\n const img = new Image(1, 1);\n\n img.onload = () => onComplete(true);\n // If loading image fails (server unavailable, blocked, etc.)\n img.onerror = () => this.sendWithBeaconOrFetch(stringifiedBody, onComplete);\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, onComplete);\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 // Collect props from DOM attributes\n const collectedProps: Record<string, string> = {};\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(collectedProps, parsedProps);\n }\n\n // Collect props from meta tag (overrides DOM attributes)\n const metaViewProps = document\n .querySelector('meta[name=\"stonks-props\"]')\n ?.getAttribute(\"content\");\n if (metaViewProps) {\n Object.assign(collectedProps, parseProps(metaViewProps));\n }\n\n // Explicit props override everything\n if (props) {\n Object.assign(collectedProps, props);\n }\n\n const viewProps = Object.keys(collectedProps).length > 0 ? collectedProps : undefined;\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, rawProps?: BaseProps) {\n if (!isClient()) return;\n\n const { isLocalhost, isHeadlessBrowser } = getEnvironment();\n if ((isLocalhost && !this.config.devmode) || isHeadlessBrowser) return;\n\n const path = resolvePath(typeof pathOrProps === \"string\" ? pathOrProps : undefined);\n const props = typeof pathOrProps === \"object\" ? pathOrProps : rawProps;\n\n this.send({ type: eventName, path, props });\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 = () => {\n // Check meta tag and body attribute for collection control\n const metaCollect = document\n .querySelector('meta[name=\"stonks-collect\"]')\n ?.getAttribute(\"content\");\n const bodyCollect =\n document.body?.getAttribute(\"data-s-collect\") ||\n document.body?.getAttribute(\"data-s:collect\");\n\n // Explicitly disabled\n if (metaCollect === \"false\" || bodyCollect === \"false\") {\n this.lastPage = null;\n return;\n }\n\n // If autocollect is off, only collect if explicitly enabled via meta/body\n if (!this.config.autocollect && metaCollect !== \"true\" && bodyCollect !== \"true\") {\n this.lastPage = null;\n return;\n }\n\n this.trackPageView({}, true);\n };\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": ["/**\n * Bot & crawler detection module.\n *\n * Detects:\n * - Known search engine crawlers (Googlebot, Bingbot, Yandex, Baidu, etc.)\n * - Social media crawlers (Facebook, Twitter, LinkedIn, etc.)\n * - Headless browsers (Puppeteer, Playwright, Selenium, PhantomJS)\n * - General automation tools via navigator.webdriver and injected globals\n * - API tampering / lie detection (prototype spoofing, proxy wrapping)\n */\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type BotKind =\n | 'search_engine' // Googlebot, Bingbot, Yandex, Baidu, DuckDuckBot, etc.\n | 'social_crawler' // Facebook, Twitter/X, LinkedIn, Slack, Discord, etc.\n | 'headless' // Headless Chrome/Firefox, PhantomJS\n | 'automation' // Selenium, Puppeteer, Playwright (non-headless)\n | 'library' // curl, wget, python-requests, node-fetch, etc.\n | 'unknown_bot' // Signals say bot, but can't classify further\n | 'human' // No bot signals detected\n\nexport interface BotSignals {\n /** navigator.userAgent matched a known bot pattern. */\n userAgentBot: boolean\n /** navigator.webdriver is true. */\n webdriver: boolean\n /** Headless browser indicators detected. */\n headless: boolean\n /** Automation globals found on window. */\n automationGlobals: string[]\n /** Number of API lies (toString/proxy tampering) detected. */\n liesDetected: number\n /** Proxy wrapping detected on native functions. */\n hasProxy: boolean\n /** navigator.languages is empty or missing. */\n missingLanguages: boolean\n /** navigator.plugins is empty (non-mobile). */\n missingPlugins: boolean\n}\n\nexport interface BotDetectionResult {\n /** True when any bot signal fires. */\n isBot: boolean\n /** Classified category of the detected bot. */\n botKind: BotKind\n /** Individual signal results for debugging / logging. */\n signals: BotSignals\n}\n\n// ---------------------------------------------------------------------------\n// Known bot UA patterns\n// ---------------------------------------------------------------------------\n\n// Single alternation. The generic tokens (bot, crawl, spider, slurp, fetch,\n// archiver) subsume most named crawlers; only names that don't contain one of\n// those tokens are listed explicitly.\nconst BOT_UA_PATTERN =\n /Google-InspectionTool|Mediapartners-Google|Sogou|ChatGPT-User|anthropic-ai|facebookexternalhit|WhatsApp|Snapchat|HeadlessChrome|PhantomJS|Selenium|Puppeteer|curl\\/|Wget\\/|python-requests|python-urllib|axios\\/|Go-http-client|Java\\/|libwww-perl|Apache-HttpClient|okhttp|Scrapy|bot|crawl|spider|slurp|fetch|archiver/i\n\n// ---------------------------------------------------------------------------\n// Automation globals injected by common frameworks\n// ---------------------------------------------------------------------------\n\nconst AUTOMATION_GLOBALS = [\n // Selenium\n '__selenium_unwrapped',\n '__selenium_evaluate',\n '__webdriver_evaluate',\n '__webdriver_script_fn',\n '__webdriver_script_func',\n '__webdriver_script_function',\n '__fxdriver_evaluate',\n '__fxdriver_unwrapped',\n '_Selenium_IDE_Recorder',\n // Puppeteer / CDP\n '__puppeteer_evaluation_script__',\n // PhantomJS\n 'callPhantom',\n '_phantom',\n 'phantom',\n // Nightmare.js\n '__nightmare',\n // Playwright (injects page.exposeFunction bindings)\n '__playwright',\n '__pw_manual',\n // CasperJS\n '__casper',\n // TestCafe\n '__testcafe',\n // WebDriver (generic)\n 'webdriver',\n 'domAutomation',\n 'domAutomationController',\n] as const\n\n// ---------------------------------------------------------------------------\n// Core detection\n// ---------------------------------------------------------------------------\n\n/**\n * Detect whether the current browser context is a bot or crawler.\n * Synchronous and lightweight \u2014 no async APIs needed.\n */\nexport function detectBot(): BotDetectionResult {\n const signals = collectBotSignals()\n\n const isBot =\n signals.userAgentBot ||\n signals.webdriver ||\n signals.headless ||\n signals.automationGlobals.length > 0 ||\n signals.liesDetected > 2 ||\n (signals.liesDetected > 0 && signals.hasProxy)\n\n let botKind: BotKind = 'human'\n if (isBot) {\n if (signals.headless) {\n botKind = 'headless'\n } else if (signals.webdriver || signals.automationGlobals.length > 0) {\n botKind = 'automation'\n } else {\n botKind = 'unknown_bot'\n }\n }\n\n return { isBot, botKind, signals }\n}\n\n// ---------------------------------------------------------------------------\n// Signal collectors\n// ---------------------------------------------------------------------------\n\nfunction collectBotSignals(): BotSignals {\n return {\n userAgentBot: detectUserAgentBot(),\n webdriver: detectWebdriver(),\n headless: detectHeadless(),\n automationGlobals: detectAutomationGlobals(),\n ...detectLies(),\n missingLanguages: detectMissingLanguages(),\n missingPlugins: detectMissingPlugins(),\n }\n}\n\n/** Check UA string against known bot patterns. */\nfunction detectUserAgentBot(): boolean {\n const ua = navigator.userAgent || ''\n if (!ua) return true\n return BOT_UA_PATTERN.test(ua)\n}\n\n/** navigator.webdriver is set by WebDriver-based automation. */\nfunction detectWebdriver(): boolean {\n return !!(navigator as any).webdriver\n}\n\n/** Headless browser indicators. */\nfunction detectHeadless(): boolean {\n const w = window as any\n const n = navigator as any\n\n // Chrome-specific: headless mode omits the chrome runtime object\n if (/Chrome/.test(n.userAgent) && !w.chrome) return true\n\n // HeadlessChrome in UA\n if (/HeadlessChrome/.test(n.userAgent)) return true\n\n // Notification permission is always \"denied\" in headless Chrome\n // (in headed mode it defaults to \"default\")\n try {\n if (Notification.permission === 'denied' && n.permissions) {\n // Double-check: headless also lacks plugins\n if ((!n.plugins || n.plugins.length === 0) && !/Mobile|Android/i.test(n.userAgent)) {\n return true\n }\n }\n } catch { /* permissions API unavailable */ }\n\n return false\n}\n\n/** Detect automation framework globals on window. */\nfunction detectAutomationGlobals(): string[] {\n const w = window as any\n return AUTOMATION_GLOBALS.filter(key => {\n try { return key in w && w[key] !== undefined }\n catch { return false }\n }) as string[]\n}\n\n/**\n * Lie detection \u2014 detect API tampering (proxies, toString spoofing).\n * Extracted from the fingerprint lies module; self-contained here.\n */\nfunction detectLies(): { liesDetected: number; hasProxy: boolean } {\n let liesDetected = 0\n let hasProxy = false\n\n const apisToTest: Array<[object, string]> = [\n [Navigator.prototype, 'userAgent'],\n [Navigator.prototype, 'languages'],\n [Navigator.prototype, 'platform'],\n [Navigator.prototype, 'hardwareConcurrency'],\n [Navigator.prototype, 'webdriver'],\n [HTMLCanvasElement.prototype, 'toDataURL'],\n [CanvasRenderingContext2D.prototype, 'fillText'],\n [Date.prototype, 'getTimezoneOffset'],\n ]\n\n for (const [proto, prop] of apisToTest) {\n try {\n const d = Object.getOwnPropertyDescriptor(proto, prop)\n if (!d) continue\n const fn = d.value ?? d.get\n if (typeof fn !== 'function') continue\n\n // toString integrity for methods and accessor getters\n const str = Function.prototype.toString.call(fn)\n if (!isNativeToString(str)) liesDetected++\n\n // Proxy detection \u2014 only for value-bound methods (not getters)\n if (d.value && fn.toString !== Function.prototype.toString) {\n try {\n const native = Function.prototype.toString.call(fn)\n const custom = fn.toString()\n if (native !== custom) { liesDetected++; hasProxy = true }\n } catch { liesDetected++; hasProxy = true }\n }\n } catch { /* skip inaccessible */ }\n }\n\n // Meta-test: toString integrity\n try {\n const s = Function.prototype.toString.call(Function.prototype.toString)\n if (!isNativeToString(s)) liesDetected++\n } catch { /* skip */ }\n\n return { liesDetected, hasProxy }\n}\n\n/** Missing navigator.languages is a strong headless indicator. */\nfunction detectMissingLanguages(): boolean {\n const langs = navigator.languages\n return !langs || langs.length === 0\n}\n\n/** Missing plugins on desktop is a headless indicator. */\nfunction detectMissingPlugins(): boolean {\n if (/Mobile|Android/i.test(navigator.userAgent)) return false\n return !navigator.plugins || navigator.plugins.length === 0\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction isNativeToString(str: string): boolean {\n return /^function\\s[^{]*\\{\\s*\\[native code\\]\\s*\\}$/.test(str) ||\n str === 'function () { [native code] }' ||\n /^\\(\\)\\s*=>\\s*\\{\\s*\\[native code\\]\\s*\\}$/.test(str)\n}\n", "export const getEnvironment = (): {\n isLocalhost: boolean;\n isHeadlessBrowser: boolean;\n} => ({\n isLocalhost:\n (/^localhost$|^127(\\.[0-9]+){0,2}\\.[0-9]+$|^\\[::1?\\]$/.test(location.hostname) &&\n (location.protocol === \"http:\" || location.protocol === \"https:\")) ||\n 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", "import type { AnalyticsConfig, InternalAnalyticsConfig } from \"../types\";\nimport { getEnvironment } from \"./environment\";\n\nexport const defaultConfig: InternalAnalyticsConfig = {\n hostname: null,\n devmode: false,\n collectorUrl: \"https://collector.onedollarstats.com/events\",\n hashRouting: false,\n autocollect: true,\n excludePages: [],\n includePages: []\n};\n\nexport const mergeConfig = (userConfig: AnalyticsConfig = {}): InternalAnalyticsConfig => {\n const { isLocalhost } = getEnvironment();\n\n const devmode = !isLocalhost ? false : (userConfig.devmode ?? !!userConfig.trackLocalhostAs);\n\n let hostname: string | null;\n if (userConfig.hostname) {\n const trimmed = userConfig.hostname.trim();\n hostname = trimmed || null;\n } else if (devmode && userConfig.trackLocalhostAs) {\n hostname = userConfig.trackLocalhostAs;\n } else {\n hostname = null;\n }\n\n // Merge default config, user config, and computed values\n return { ...defaultConfig, ...userConfig, hostname, devmode };\n};\n", "export function parseUtmParams(urlSearchParams: URLSearchParams) {\n const utm: Record<string, string> = {};\n const keys = [\"utm_campaign\", \"utm_source\", \"utm_medium\", \"utm_term\", \"utm_content\"] as const;\n\n for (const key of keys) {\n const raw = urlSearchParams.get(key);\n if (!raw) continue;\n\n const decoded = decodeAndTrim(raw);\n if (decoded) {\n utm[key] = decoded;\n }\n }\n\n return utm;\n}\n\nfunction decodeAndTrim(value: string): string {\n let decoded = value;\n let previous = \"\";\n\n while (decoded !== previous) {\n previous = decoded;\n try {\n decoded = decodeURIComponent(decoded);\n } catch {\n return decoded.trim();\n }\n }\n\n return decoded.trim();\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[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 { InternalAnalyticsConfig } 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: InternalAnalyticsConfig): 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, InternalAnalyticsConfig, ViewArguments } from \"./types\";\nimport { detectBot } from \"./utils/bot\";\nimport { getEnvironment, isClient } from \"./utils/environment\";\nimport { mergeConfig } from \"./utils/merge-config\";\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\ndeclare const DEBUG_SCRIPT_URL: string;\n\nclass AnalyticsTracker {\n private static instance: AnalyticsTracker | null = null;\n\n private autocollectSetupDone = false;\n private config: InternalAnalyticsConfig;\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 = mergeConfig(userConfig);\n\n // Skip setup in non-client environments\n if (!isClient()) return;\n\n const { isLocalhost } = getEnvironment();\n\n // Debug log on localhost\n if (isLocalhost && this.config.devmode && this.config.hostname) {\n console.log(`[onedollarstats]\\nOneDollarStats connected! Tracking localhost as ${this.config.hostname}`);\n\n // Set up debug modal loading: store config + queue, then dynamically load the debug script\n window.__stonksDebugConfig = { hostname: this.config.hostname, collectorUrl: this.config.collectorUrl };\n window.__stonksModalQueue = [];\n window.__stonksModalReady = false;\n\n const debugScript = document.createElement(\"script\");\n debugScript.src = DEBUG_SCRIPT_URL;\n debugScript.onerror = () => {\n // If the debug script fails to load, mark as ready so queued events are not stuck\n window.__stonksModalReady = true;\n };\n document.head.appendChild(debugScript);\n }\n\n // Auto-start autocollect (always set up listeners; handlePageView checks config)\n this.setupAutocollect();\n }\n\n private async sendWithBeaconOrFetch(stringifiedBody: string, callback: (success: boolean) => void): Promise<void> {\n // First fallback: try sendBeacon\n if (navigator.sendBeacon?.(this.config.collectorUrl, stringifiedBody)) {\n callback(true);\n return;\n }\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 })\n .then(({ ok }) => callback(ok))\n .catch((err: Error) => {\n console.error(\"[onedollarstats] fetch() failed:\", err.message);\n callback(false);\n });\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.devmode) || isHeadlessBrowser) return;\n\n const { isBot, botKind } = detectBot();\n\n if (isBot && botKind !== \"human\") return;\n\n const urlToSend = new URL(this.config.hostname ? `https://${this.config.hostname}${location.pathname}` : location.href);\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 debug: this.config.devmode\n };\n\n if (data.utm && Object.keys(data.utm).length > 0) body.qs = data.utm;\n\n if (body.debug) {\n let logMessage = `[onedollarstats]\\nEvent name: ${data.type}\\nEvent collected from: ${cleanUrl}`;\n if (data.props && Object.keys(data.props).length > 0) logMessage += `\\nProps: ${JSON.stringify(data.props, null, 2)}`;\n if (referrer) logMessage += `\\nReferrer: ${referrer}`;\n if (this.config.hashRouting) logMessage += `\\nHashRouting: ${this.config.hashRouting}`;\n if (data.utm && Object.keys(data.utm).length > 0) logMessage += `\\nUTM: ${data.utm}`;\n\n console.log(logMessage);\n }\n\n // Prepare the event payload\n const stringifiedBody = JSON.stringify(body);\n\n // Encode for safe inclusion in a query string (UTF-8 \u2192 Base64)\n const bytes = new TextEncoder().encode(stringifiedBody); // UTF-8 \u2192 bytes\n const bin = String.fromCharCode(...bytes); // bytes \u2192 binary string\n const payloadBase64 = btoa(bin); // binary \u2192 Base64\n\n const safeGetThreshold = 1500; // limit for query-string-containing URLs\n const tryImageBeacon = payloadBase64.length <= safeGetThreshold;\n\n const onComplete = (success: boolean) => {\n const message = `${data.type} ${success ? \"sent\" : \"failed to send\"}`;\n if (window.__stonksModalReady) {\n window.__stonksModalLog?.(message, success);\n } else {\n window.__stonksModalQueue?.push([message, success]);\n }\n };\n\n if (tryImageBeacon) {\n // Send via image beacon\n const img = new Image(1, 1);\n\n img.onload = () => onComplete(true);\n // If loading image fails (server unavailable, blocked, etc.)\n img.onerror = () => this.sendWithBeaconOrFetch(stringifiedBody, onComplete);\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, onComplete);\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 // Collect props from DOM attributes\n const collectedProps: Record<string, string> = {};\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(collectedProps, parsedProps);\n }\n\n // Collect props from meta tag (overrides DOM attributes)\n const metaViewProps = document\n .querySelector('meta[name=\"stonks-props\"]')\n ?.getAttribute(\"content\");\n if (metaViewProps) {\n Object.assign(collectedProps, parseProps(metaViewProps));\n }\n\n // Explicit props override everything\n if (props) {\n Object.assign(collectedProps, props);\n }\n\n const viewProps = Object.keys(collectedProps).length > 0 ? collectedProps : undefined;\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, rawProps?: BaseProps) {\n if (!isClient()) return;\n\n const { isLocalhost, isHeadlessBrowser } = getEnvironment();\n if ((isLocalhost && !this.config.devmode) || isHeadlessBrowser) return;\n\n const path = resolvePath(typeof pathOrProps === \"string\" ? pathOrProps : undefined);\n const props = typeof pathOrProps === \"object\" ? pathOrProps : rawProps;\n\n this.send({ type: eventName, path, props });\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 = () => {\n // Check meta tag and body attribute for collection control\n const metaCollect = document\n .querySelector('meta[name=\"stonks-collect\"]')\n ?.getAttribute(\"content\");\n const bodyCollect =\n document.body?.getAttribute(\"data-s-collect\") ||\n document.body?.getAttribute(\"data-s:collect\");\n\n // Explicitly disabled\n if (metaCollect === \"false\" || bodyCollect === \"false\") {\n this.lastPage = null;\n return;\n }\n\n // If autocollect is off, only collect if explicitly enabled via meta/body\n if (!this.config.autocollect && metaCollect !== \"true\" && bodyCollect !== \"true\") {\n this.lastPage = null;\n return;\n }\n\n this.trackPageView({}, true);\n };\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
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "onedollarstats",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"description": "A lightweight, zero-dependency analytics tracker for frontend apps",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -8,14 +8,23 @@
|
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
|
-
"
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./expo": {
|
|
14
|
+
"types": "./dist/expo.d.ts",
|
|
15
|
+
"react-native": "./dist/expo.js",
|
|
16
|
+
"browser": "./dist/expo.js",
|
|
17
|
+
"default": "./dist/expo.js"
|
|
12
18
|
}
|
|
13
19
|
},
|
|
14
20
|
"files": [
|
|
15
21
|
"dist/index.js",
|
|
16
22
|
"dist/index.js.map",
|
|
17
23
|
"dist/index.d.ts",
|
|
18
|
-
"dist/types.d.ts"
|
|
24
|
+
"dist/types.d.ts",
|
|
25
|
+
"dist/expo.js",
|
|
26
|
+
"dist/expo.js.map",
|
|
27
|
+
"dist/expo.d.ts"
|
|
19
28
|
],
|
|
20
29
|
"keywords": [
|
|
21
30
|
"analytics",
|
|
@@ -29,29 +38,55 @@
|
|
|
29
38
|
],
|
|
30
39
|
"author": "",
|
|
31
40
|
"license": "MIT",
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"expo-router": ">=3.0.0",
|
|
43
|
+
"react": ">=18.0.0",
|
|
44
|
+
"react-native": ">=0.73.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependenciesMeta": {
|
|
47
|
+
"expo-router": {
|
|
48
|
+
"optional": true
|
|
49
|
+
},
|
|
50
|
+
"react": {
|
|
51
|
+
"optional": true
|
|
52
|
+
},
|
|
53
|
+
"react-native": {
|
|
54
|
+
"optional": true
|
|
55
|
+
}
|
|
56
|
+
},
|
|
32
57
|
"devDependencies": {
|
|
58
|
+
"@testing-library/react": "^14.0.0",
|
|
33
59
|
"@types/jsdom": "^27.0.0",
|
|
60
|
+
"@types/react": "^18.0.0",
|
|
61
|
+
"@types/react-native": "^0.73.0",
|
|
34
62
|
"esbuild": "^0.25.10",
|
|
63
|
+
"expo-router": "^3.0.0",
|
|
35
64
|
"jsdom": "^26.0.0",
|
|
36
65
|
"puppeteer": "^24.1.1",
|
|
66
|
+
"react": "^18.0.0",
|
|
67
|
+
"react-native": "^0.73.0",
|
|
37
68
|
"tsx": "^4.19.2",
|
|
38
69
|
"typescript": "^5.9.2",
|
|
39
70
|
"vitest": "^4.0.18"
|
|
40
71
|
},
|
|
41
72
|
"scripts": {
|
|
42
|
-
"build": "tsc -p tsconfig.build.json --emitDeclarationOnly && rm -rf dist/utils dist/script.d.ts dist/debug-modal.d.ts dist/globals.d.ts && esbuild src/index.ts --bundle --outfile=dist/index.js --platform=browser --format=esm --target=es2020 --sourcemap --define:DEBUG_SCRIPT_URL='\"https://assets.onedollarstats.com/stonks-debug.js\"'",
|
|
43
|
-
"bundle": "esbuild src/script.ts --bundle --minify --format=iife --outfile=build/stonks.js --define:DEBUG_SCRIPT_URL='\"https://assets.onedollarstats.com/stonks-debug.js\"'",
|
|
44
|
-
"bundle:dev": "esbuild src/script.ts --bundle --minify --format=iife --outfile=build/stonks.js --define:DEBUG_SCRIPT_URL='\"https://assets.onedollarstats.com/stonks-debug-dev.js\"'",
|
|
45
|
-
"bundle:test": "esbuild src/script.ts --bundle --format=cjs --outfile=build/stonks.js --define:DEBUG_SCRIPT_URL='\"https://assets.onedollarstats.com/stonks-debug.js\"'",
|
|
46
|
-
"bundle:debug": "esbuild src/debug-modal.ts --bundle --minify --format=iife --outfile=build/stonks-debug.js",
|
|
73
|
+
"build": "tsc -p tsconfig.build.json --emitDeclarationOnly && rm -rf dist/utils dist/script.d.ts dist/debug-modal.d.ts dist/globals.d.ts && esbuild src/index.ts --bundle --outfile=dist/index.js --platform=browser --format=esm --target=es2020 --sourcemap --legal-comments=none --define:DEBUG_SCRIPT_URL='\"https://assets.onedollarstats.com/stonks-debug.js\"' && esbuild src/expo.ts --bundle=false --outfile=dist/expo.js --platform=neutral --format=esm --target=es2020 --sourcemap --legal-comments=none",
|
|
74
|
+
"bundle": "esbuild src/script.ts --bundle --minify --format=iife --outfile=build/stonks.js --legal-comments=none --define:DEBUG_SCRIPT_URL='\"https://assets.onedollarstats.com/stonks-debug.js\"'",
|
|
75
|
+
"bundle:dev": "esbuild src/script.ts --bundle --minify --format=iife --outfile=build/stonks.js --legal-comments=none --define:DEBUG_SCRIPT_URL='\"https://assets.onedollarstats.com/stonks-debug-dev.js\"'",
|
|
76
|
+
"bundle:test": "esbuild src/script.ts --bundle --format=cjs --outfile=build/stonks.js --legal-comments=none --define:DEBUG_SCRIPT_URL='\"https://assets.onedollarstats.com/stonks-debug.js\"'",
|
|
77
|
+
"bundle:debug": "esbuild src/debug-modal.ts --bundle --minify --format=iife --outfile=build/stonks-debug.js --legal-comments=none",
|
|
47
78
|
"test": "vitest run",
|
|
48
|
-
"test:
|
|
49
|
-
"test:
|
|
50
|
-
"test:
|
|
79
|
+
"test:unit": "vitest run --project 'Web Unit Tests' --project 'Expo Unit Tests'",
|
|
80
|
+
"test:unit:web": "vitest run --project 'Web Unit Tests'",
|
|
81
|
+
"test:unit:web:modal": "vitest run --project 'Web Unit Tests' src/utils/create-modal.test.ts",
|
|
82
|
+
"test:unit:expo": "vitest run --project 'Expo Unit Tests'",
|
|
83
|
+
"test:e2e": "vitest run --project 'Web E2E Tests' --project 'Package E2E Tests' --project 'Expo E2E Tests'",
|
|
84
|
+
"test:e2e:web": "vitest run --project 'Web E2E Tests'",
|
|
51
85
|
"test:e2e:package": "vitest run --project 'Package E2E Tests'",
|
|
52
|
-
"test:e2e": "vitest run --project '
|
|
53
|
-
"test:build
|
|
54
|
-
"test:build
|
|
86
|
+
"test:e2e:expo": "vitest run --project 'Expo E2E Tests'",
|
|
87
|
+
"test:build:e2e:web": "pnpm bundle:test && FORCE_BUILD=1 COPY_TRACKER=1 vitest run --project 'Web E2E Tests'",
|
|
88
|
+
"test:build:e2e:package": "pnpm build && FORCE_BUILD=1 COPY_PACKAGE=1 vitest run --project 'Package E2E Tests'",
|
|
89
|
+
"test:build:e2e:expo": "pnpm build && FORCE_BUILD=1 COPY_PACKAGE=1 vitest run --project 'Expo E2E Tests'",
|
|
55
90
|
"test:all": "pnpm bundle:test && pnpm build && FORCE_BUILD=1 COPY_TRACKER=1 COPY_PACKAGE=1 vitest run"
|
|
56
91
|
}
|
|
57
92
|
}
|