@arthurreira/analytics 0.5.0 → 0.6.0
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 +160 -13
- package/dist/client.js +18 -0
- package/dist/index.js +18 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,28 +1,175 @@
|
|
|
1
|
-
|
|
1
|
+
# @arthurreira/analytics
|
|
2
2
|
|
|
3
|
-
Lightweight analytics SDK
|
|
3
|
+
Lightweight analytics SDK for Next.js / React apps. Drop-in component + hook for tracking pageviews, clicks, scrolls, copies, errors, CTAs, and searches — with automatic session management.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Install from npm:
|
|
5
|
+
## Installation
|
|
8
6
|
|
|
7
|
+
```bash
|
|
9
8
|
pnpm add @arthurreira/analytics
|
|
9
|
+
# or
|
|
10
|
+
npm install @arthurreira/analytics
|
|
11
|
+
# or
|
|
12
|
+
yarn add @arthurreira/analytics
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Peer dependencies:** `react ^18 || ^19`, `next ^14 || ^15 || ^16`
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
### Option A — Drop-in `<Analytics />` component (recommended)
|
|
22
|
+
|
|
23
|
+
Add it once in your root layout. It automatically tracks pageviews, clicks, scroll depth milestones (25 / 50 / 75 / 100 %), text copies, and uncaught JS errors with zero extra code.
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
// app/layout.tsx (Next.js App Router)
|
|
27
|
+
import { Analytics } from '@arthurreira/analytics/client'
|
|
28
|
+
|
|
29
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
30
|
+
return (
|
|
31
|
+
<html>
|
|
32
|
+
<body>
|
|
33
|
+
{children}
|
|
34
|
+
<Analytics
|
|
35
|
+
apiUrl={process.env.NEXT_PUBLIC_ANALYTICS_URL!}
|
|
36
|
+
apiKey={process.env.NEXT_PUBLIC_ANALYTICS_KEY!}
|
|
37
|
+
/>
|
|
38
|
+
</body>
|
|
39
|
+
</html>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Option B — `useAnalytics` hook (manual control)
|
|
45
|
+
|
|
46
|
+
Use the hook directly when you need to fire custom events from your own components.
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
'use client'
|
|
50
|
+
import { useAnalytics } from '@arthurreira/analytics/client'
|
|
51
|
+
|
|
52
|
+
export function SearchBar() {
|
|
53
|
+
const { trackSearch, trackCTA } = useAnalytics(
|
|
54
|
+
process.env.NEXT_PUBLIC_ANALYTICS_URL!,
|
|
55
|
+
process.env.NEXT_PUBLIC_ANALYTICS_KEY!,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<input
|
|
60
|
+
onKeyDown={(e) => {
|
|
61
|
+
if (e.key === 'Enter') trackSearch(e.currentTarget.value)
|
|
62
|
+
}}
|
|
63
|
+
/>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## API reference
|
|
71
|
+
|
|
72
|
+
### `<Analytics apiUrl apiKey />`
|
|
73
|
+
|
|
74
|
+
| Prop | Type | Description |
|
|
75
|
+
| -------- | -------- | --------------------------------------------------------- |
|
|
76
|
+
| `apiUrl` | `string` | Base URL of your af-analytics backend (no trailing slash) |
|
|
77
|
+
| `apiKey` | `string` | Bearer token for your analytics project |
|
|
78
|
+
|
|
79
|
+
Automatically tracks: **pageview**, **click**, **scroll** (depth milestones), **copy**, **error**.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
### `useAnalytics(apiUrl, apiKey)`
|
|
84
|
+
|
|
85
|
+
Returns an object with the following tracking functions. Events are queued internally until the session is ready, so it is safe to call them immediately on mount.
|
|
10
86
|
|
|
11
|
-
|
|
87
|
+
| Method | Signature | Description |
|
|
88
|
+
| --------------- | ----------------------------------------------- | -------------------------------------------------------- |
|
|
89
|
+
| `trackPageview` | `(path: string) => void` | Manual pageview (e.g. SPA route changes) |
|
|
90
|
+
| `trackClick` | `(e: MouseEvent, element: HTMLElement) => void` | Click with position + element metadata |
|
|
91
|
+
| `trackScroll` | `(depth: number) => void` | Scroll depth percentage (0–100) |
|
|
92
|
+
| `trackCopy` | `() => void` | Text copy event (captures selected text up to 200 chars) |
|
|
93
|
+
| `trackError` | `(error: Error) => void` | JS error with message + stack trace |
|
|
94
|
+
| `trackCTA` | `(ctaId: string, ctaVariant?: string) => void` | CTA button interaction |
|
|
95
|
+
| `trackSearch` | `(query: string) => void` | Search query |
|
|
12
96
|
|
|
13
|
-
|
|
97
|
+
---
|
|
14
98
|
|
|
15
|
-
Server / build entry
|
|
99
|
+
### Server / build entry
|
|
16
100
|
|
|
17
|
-
|
|
101
|
+
The default entry (`@arthurreira/analytics`) exports the raw API helpers — safe to import in server components or build scripts because it contains no browser globals.
|
|
18
102
|
|
|
19
|
-
|
|
103
|
+
```ts
|
|
104
|
+
import {
|
|
105
|
+
createSession,
|
|
106
|
+
trackPageview,
|
|
107
|
+
trackClick,
|
|
108
|
+
trackScroll,
|
|
109
|
+
trackCopy,
|
|
110
|
+
trackError,
|
|
111
|
+
trackCTA,
|
|
112
|
+
trackSearch,
|
|
113
|
+
} from '@arthurreira/analytics'
|
|
114
|
+
```
|
|
20
115
|
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Session management
|
|
119
|
+
|
|
120
|
+
Sessions are created automatically on first load and stored in `localStorage` (`af_session_id`). A session expires after **30 minutes of inactivity**. When the user leaves or hides the tab the SDK fires a `sendBeacon` to close the session gracefully.
|
|
121
|
+
|
|
122
|
+
Visitor identity is persisted across sessions via `af_analytics_visitor_id` (a random UUID stored in `localStorage`).
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Data collected
|
|
127
|
+
|
|
128
|
+
On session start the SDK sends:
|
|
129
|
+
|
|
130
|
+
- Visitor ID, language, timezone
|
|
131
|
+
- Screen / viewport dimensions
|
|
132
|
+
- Referrer URL, landing page
|
|
133
|
+
- UTM parameters (`utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`)
|
|
134
|
+
|
|
135
|
+
On each event:
|
|
136
|
+
|
|
137
|
+
- `session_id`, `event_type`, `path`, `page_url`
|
|
138
|
+
- Event-specific fields (position, element, scroll depth, error stack, etc.)
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Environment variables
|
|
143
|
+
|
|
144
|
+
| Variable | Description |
|
|
145
|
+
| --------------------------- | ------------------------------------------------------- |
|
|
146
|
+
| `NEXT_PUBLIC_ANALYTICS_URL` | Backend base URL, e.g. `https://analytics.example.com/api` |
|
|
147
|
+
| `NEXT_PUBLIC_ANALYTICS_KEY` | Project API key (Bearer token) |
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Package exports
|
|
152
|
+
|
|
153
|
+
| Import path | Contents | Use in |
|
|
154
|
+
| -------------------------------- | ------------------------------------------- | ----------------- |
|
|
155
|
+
| `@arthurreira/analytics` | Raw API helpers (no browser globals) | Server / build |
|
|
156
|
+
| `@arthurreira/analytics/client` | `Analytics` component + `useAnalytics` hook | Client components |
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Building locally
|
|
161
|
+
|
|
162
|
+
```bash
|
|
21
163
|
pnpm install
|
|
22
164
|
pnpm build
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Publishing
|
|
168
|
+
|
|
169
|
+
Tag a release (e.g. `v0.5.0`) after adding an `NPM_TOKEN` secret to the repository to trigger the CI publish workflow.
|
|
23
170
|
|
|
24
|
-
|
|
171
|
+
---
|
|
25
172
|
|
|
26
|
-
|
|
173
|
+
## License
|
|
27
174
|
|
|
28
|
-
|
|
175
|
+
MIT
|
package/dist/client.js
CHANGED
|
@@ -14,6 +14,18 @@ var BASE_FIELDS = (sessionId, eventType, path) => ({
|
|
|
14
14
|
path,
|
|
15
15
|
page_url: typeof window !== "undefined" ? window.location.href : ""
|
|
16
16
|
});
|
|
17
|
+
function getGpu() {
|
|
18
|
+
try {
|
|
19
|
+
const canvas = document.createElement("canvas");
|
|
20
|
+
const gl = canvas.getContext("webgl");
|
|
21
|
+
if (!gl) return null;
|
|
22
|
+
const ext = gl.getExtension("WEBGL_debug_renderer_info");
|
|
23
|
+
if (!ext) return null;
|
|
24
|
+
return gl.getParameter(ext.UNMASKED_RENDERER_WEBGL);
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
17
29
|
async function createSession(apiUrl, apiKey, visitorId) {
|
|
18
30
|
const sessionData = { visitor_id: visitorId };
|
|
19
31
|
if (typeof window !== "undefined") {
|
|
@@ -26,6 +38,12 @@ async function createSession(apiUrl, apiKey, visitorId) {
|
|
|
26
38
|
sessionData.referrer = document.referrer || null;
|
|
27
39
|
sessionData.landing_page = window.location.pathname || null;
|
|
28
40
|
sessionData.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || null;
|
|
41
|
+
sessionData.cpu_threads = navigator.hardwareConcurrency ?? null;
|
|
42
|
+
sessionData.memory_gb = nav.deviceMemory ?? null;
|
|
43
|
+
sessionData.gpu = getGpu();
|
|
44
|
+
const conn = nav.connection ?? nav.mozConnection ?? nav.webkitConnection ?? null;
|
|
45
|
+
sessionData.network_type = conn?.effectiveType ?? null;
|
|
46
|
+
sessionData.connection_speed_mbps = conn?.downlink ?? null;
|
|
29
47
|
const params = new URLSearchParams(window.location.search);
|
|
30
48
|
sessionData.utm_source = params.get("utm_source");
|
|
31
49
|
sessionData.utm_medium = params.get("utm_medium");
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,18 @@ var BASE_FIELDS = (sessionId, eventType, path) => ({
|
|
|
5
5
|
path,
|
|
6
6
|
page_url: typeof window !== "undefined" ? window.location.href : ""
|
|
7
7
|
});
|
|
8
|
+
function getGpu() {
|
|
9
|
+
try {
|
|
10
|
+
const canvas = document.createElement("canvas");
|
|
11
|
+
const gl = canvas.getContext("webgl");
|
|
12
|
+
if (!gl) return null;
|
|
13
|
+
const ext = gl.getExtension("WEBGL_debug_renderer_info");
|
|
14
|
+
if (!ext) return null;
|
|
15
|
+
return gl.getParameter(ext.UNMASKED_RENDERER_WEBGL);
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
8
20
|
async function createSession(apiUrl, apiKey, visitorId) {
|
|
9
21
|
const sessionData = { visitor_id: visitorId };
|
|
10
22
|
if (typeof window !== "undefined") {
|
|
@@ -17,6 +29,12 @@ async function createSession(apiUrl, apiKey, visitorId) {
|
|
|
17
29
|
sessionData.referrer = document.referrer || null;
|
|
18
30
|
sessionData.landing_page = window.location.pathname || null;
|
|
19
31
|
sessionData.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || null;
|
|
32
|
+
sessionData.cpu_threads = navigator.hardwareConcurrency ?? null;
|
|
33
|
+
sessionData.memory_gb = nav.deviceMemory ?? null;
|
|
34
|
+
sessionData.gpu = getGpu();
|
|
35
|
+
const conn = nav.connection ?? nav.mozConnection ?? nav.webkitConnection ?? null;
|
|
36
|
+
sessionData.network_type = conn?.effectiveType ?? null;
|
|
37
|
+
sessionData.connection_speed_mbps = conn?.downlink ?? null;
|
|
20
38
|
const params = new URLSearchParams(window.location.search);
|
|
21
39
|
sessionData.utm_source = params.get("utm_source");
|
|
22
40
|
sessionData.utm_medium = params.get("utm_medium");
|
package/package.json
CHANGED