@arthurreira/analytics 0.11.0 → 0.12.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 CHANGED
@@ -1,29 +1,95 @@
1
1
  # @arthurreira/analytics
2
2
 
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.
3
+ Lightweight analytics SDK with three usage modes:
4
+
5
+ | Mode | Use when |
6
+ | --- | --- |
7
+ | `<Analytics />` component | Next.js / React apps — zero-config, drop it in the root layout |
8
+ | `useAnalytics` hook | React, need custom events from individual components |
9
+ | Vanilla script tag | Any HTML page — no framework required |
10
+
11
+ Tracks pageviews, clicks, scroll depth, text copies, JS errors, CTAs, searches, and session duration — with automatic session management and optional real-time presence via WebSocket.
12
+
13
+ ---
4
14
 
5
15
  ## Installation
6
16
 
17
+ ### React / Next.js
18
+
7
19
  ```bash
8
20
  pnpm add @arthurreira/analytics
9
21
  # or
10
22
  npm install @arthurreira/analytics
11
- # or
12
- yarn add @arthurreira/analytics
13
23
  ```
14
24
 
15
25
  **Peer dependencies:** `react ^18 || ^19`, `next ^14 || ^15 || ^16`
16
26
 
27
+ ### Vanilla JS (no package manager)
28
+
29
+ ```html
30
+ <script
31
+ src="https://unpkg.com/@arthurreira/analytics@0.12.0/dist/af-analytics.umd.js"
32
+ data-api-key="proj_xxx"
33
+ data-url="https://your-edge-worker.dev"
34
+ ></script>
35
+ ```
36
+
37
+ Drops a global `AfAnalytics` object and **auto-initialises on `DOMContentLoaded`** — no other code needed.
38
+
39
+ ### WordPress
40
+
41
+ #### Option A — `functions.php` (recommended for theme/child-theme)
42
+
43
+ Add to your theme's `functions.php`:
44
+
45
+ ```php
46
+ function af_analytics_enqueue() {
47
+ wp_enqueue_script(
48
+ 'af-analytics',
49
+ 'https://unpkg.com/@arthurreira/analytics@0.12.0/dist/af-analytics.umd.js',
50
+ [],
51
+ null,
52
+ true // load in footer
53
+ );
54
+ // Attach data attributes after the script tag is output
55
+ add_filter( 'script_loader_tag', function ( $tag, $handle ) {
56
+ if ( $handle !== 'af-analytics' ) return $tag;
57
+ return str_replace(
58
+ '<script ',
59
+ '<script data-api-key="proj_xxx" data-url="https://your-edge-worker.dev" ',
60
+ $tag
61
+ );
62
+ }, 10, 2 );
63
+ }
64
+ add_action( 'wp_enqueue_scripts', 'af_analytics_enqueue' );
65
+ ```
66
+
67
+ #### Option B — "Insert Headers and Footers" plugin (no code)
68
+
69
+ 1. Install the [Insert Headers and Footers](https://wordpress.org/plugins/insert-headers-and-footers/) plugin.
70
+ 2. Go to **Settings → Insert Headers and Footers**.
71
+ 3. Paste the snippet below into the **Scripts in Footer** box and save:
72
+
73
+ ```html
74
+ <script
75
+ src="https://unpkg.com/@arthurreira/analytics@0.12.0/dist/af-analytics.umd.js"
76
+ data-api-key="proj_xxx"
77
+ data-url="https://your-edge-worker.dev"
78
+ ></script>
79
+ ```
80
+
81
+ Replace `proj_xxx` with your project API key and `https://your-edge-worker.dev` with your Edge Worker URL. The script auto-tracks pageviews, clicks, scroll depth, and JS errors with no further configuration.
82
+
17
83
  ---
18
84
 
19
85
  ## Quick start
20
86
 
21
- ### Option A — Drop-in `<Analytics />` component (recommended)
87
+ ### Option A — `<Analytics />` component (Next.js, recommended)
22
88
 
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.
89
+ Add it once in your root layout. Automatically tracks pageviews, clicks, scroll-depth milestones (25 / 50 / 75 / 100 %), text copies, and uncaught JS errors.
24
90
 
25
91
  ```tsx
26
- // app/layout.tsx (Next.js App Router)
92
+ // app/layout.tsx
27
93
  import { Analytics } from '@arthurreira/analytics/client'
28
94
 
29
95
  export default function RootLayout({ children }: { children: React.ReactNode }) {
@@ -41,9 +107,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
41
107
  }
42
108
  ```
43
109
 
44
- ### Option B — `useAnalytics` hook (manual control)
110
+ ### Option B — `useAnalytics` hook (custom events)
45
111
 
46
- Use the hook directly when you need to fire custom events from your own components.
112
+ Use the hook when you need to fire custom events from your own components.
47
113
 
48
114
  ```tsx
49
115
  'use client'
@@ -65,40 +131,59 @@ export function SearchBar() {
65
131
  }
66
132
  ```
67
133
 
134
+ ### Option C — Vanilla JS script tag
135
+
136
+ Works with any HTML page or server-rendered site. Config is read from `data-*` attributes on the script tag itself.
137
+
138
+ ```html
139
+ <script
140
+ src="/sdk/af-analytics.umd.js"
141
+ data-api-key="proj_xxx"
142
+ data-url="https://your-edge-worker.dev"
143
+ ></script>
144
+ ```
145
+
146
+ Auto-tracks the same events as the React component. No configuration code required. Call `AfAnalytics.init()` manually if you need to reinitialise after the script was loaded dynamically.
147
+
68
148
  ---
69
149
 
70
150
  ## API reference
71
151
 
72
- ### `<Analytics apiUrl apiKey />`
152
+ ### `<Analytics />` props
73
153
 
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 |
154
+ | Prop | Type | Required | Description |
155
+ | --- | --- | --- | --- |
156
+ | `apiUrl` | `string` | yes | Base URL of your analytics backend (no trailing slash) |
157
+ | `apiKey` | `string` | yes | Project API key |
158
+ | `wsUrl` | `string` | no | WebSocket URL for real-time presence (default: `wss://edge.arthurreira.dev/realtime`) |
78
159
 
79
- Automatically tracks: **pageview**, **click**, **scroll** (depth milestones), **copy**, **error**.
160
+ Auto-tracks: **pageview**, **click**, **scroll** (depth milestones), **copy**, **error**.
80
161
 
81
162
  ---
82
163
 
83
- ### `useAnalytics(apiUrl, apiKey)`
164
+ ### `useAnalytics(apiUrl, apiKey, options?)`
84
165
 
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.
166
+ | Option | Type | Description |
167
+ | --- | --- | --- |
168
+ | `options.wsUrl` | `string` | Override the default presence WebSocket URL |
86
169
 
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 |
170
+ Events are queued internally until the session is ready — safe to call immediately on mount.
171
+
172
+ | Method | Signature | Description |
173
+ | --- | --- | --- |
174
+ | `trackPageview` | `(path: string) => void` | Manual pageview (SPA route changes) |
175
+ | `trackClick` | `(e: MouseEvent, element: HTMLElement) => void` | Click with position + element metadata |
176
+ | `trackScroll` | `(depth: number) => void` | Scroll depth percentage (0–100) |
177
+ | `trackCopy` | `() => void` | Text copy event (captures selected text, up to 200 chars) |
178
+ | `trackError` | `(error: Error) => void` | JS error with message + stack trace |
179
+ | `trackCTA` | `(ctaId: string, ctaVariant?: string) => void` | CTA button interaction |
180
+ | `trackSearch` | `(query: string) => void` | Search query |
96
181
 
97
182
  ---
98
183
 
99
- ### Server / build entry
184
+ ### Raw API helpers (server / build safe)
100
185
 
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.
186
+ The default entry exports the low-level functions with no browser globals — safe to import in server components or Node.js scripts.
102
187
 
103
188
  ```ts
104
189
  import {
@@ -113,47 +198,89 @@ import {
113
198
  } from '@arthurreira/analytics'
114
199
  ```
115
200
 
201
+ All helpers take `(apiUrl, apiKey, sessionId, ...)` as their first arguments and call your backend directly via `fetch`.
202
+
116
203
  ---
117
204
 
118
- ## Session management
205
+ ### Presence (real-time WebSocket)
206
+
207
+ Optional live-visitor presence tracking over WebSocket.
119
208
 
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.
209
+ ```ts
210
+ import { connectPresence, DEFAULT_WS_URL } from '@arthurreira/analytics'
211
+ import type { PresenceConnection } from '@arthurreira/analytics'
121
212
 
122
- Visitor identity is persisted across sessions via `af_analytics_visitor_id` (a random UUID stored in `localStorage`).
213
+ const conn: PresenceConnection = connectPresence(apiKey, sessionId, DEFAULT_WS_URL)
214
+
215
+ // later
216
+ conn.disconnect()
217
+ ```
218
+
219
+ The connection retries up to 3 times with exponential back-off on unexpected close. When used via the `<Analytics />` component or `useAnalytics`, presence connects automatically and disconnects on session end.
123
220
 
124
221
  ---
125
222
 
126
- ## Data collected
223
+ ## Package exports
127
224
 
128
- On session start the SDK sends:
225
+ | Import path | Format | Contents | Use in |
226
+ | --- | --- | --- | --- |
227
+ | `@arthurreira/analytics` | ESM | Raw API helpers + presence (no browser globals) | Server / Node |
228
+ | `@arthurreira/analytics/client` | ESM | `Analytics` component + `useAnalytics` hook | React client |
229
+ | `dist/af-analytics.umd.js` | IIFE | Self-contained auto-tracking script | Any HTML page |
129
230
 
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`)
231
+ ---
134
232
 
135
- On each event:
233
+ ## Session management
136
234
 
137
- - `session_id`, `event_type`, `path`, `page_url`
138
- - Event-specific fields (position, element, scroll depth, error stack, etc.)
235
+ - A session is created on first load and stored in `localStorage` (`af_session_id`).
236
+ - Sessions expire after **30 minutes of inactivity**.
237
+ - On tab hide (`visibilitychange → hidden`) or page unload (`beforeunload`) the SDK calls `navigator.sendBeacon` to close the session gracefully — no events are dropped.
238
+ - Visitor identity persists across sessions via `af_analytics_visitor_id` (a random UUID in `localStorage`).
139
239
 
140
240
  ---
141
241
 
142
- ## Environment variables
242
+ ## Data collected
143
243
 
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) |
244
+ ### On session start
245
+
246
+ | Field | Description |
247
+ | --- | --- |
248
+ | `visitor_id` | Persistent anonymous UUID |
249
+ | `language` | `navigator.language` |
250
+ | `screen_width / height` | Physical screen resolution |
251
+ | `viewport_width / height` | Current browser viewport |
252
+ | `referrer` | `document.referrer` |
253
+ | `landing_page` | `window.location.pathname` |
254
+ | `timezone` | IANA timezone string |
255
+ | `cpu_threads` | `navigator.hardwareConcurrency` |
256
+ | `memory_gb` | `navigator.deviceMemory` (Chrome only) |
257
+ | `gpu` | WebGL unmasked renderer string |
258
+ | `network_type` | `effectiveType` from Network Information API |
259
+ | `connection_speed_mbps` | `downlink` from Network Information API |
260
+ | `utm_source/medium/campaign/term/content` | UTM parameters from the landing URL |
261
+
262
+ ### On each event
263
+
264
+ `session_id`, `event_type`, `path`, `page_url`, plus event-specific fields:
265
+
266
+ | Event | Extra fields |
267
+ | --- | --- |
268
+ | `pageview` | `page_title`, `referrer`, `scroll_depth: 0` |
269
+ | `click` | `x_position`, `y_position`, `element_id`, `element_class`, `element_text` |
270
+ | `scroll` | `scroll_depth` (25 / 50 / 75 / 100) |
271
+ | `copy` | `copied_text` (up to 200 chars) |
272
+ | `error` | `error_message`, `stack_trace` |
273
+ | `cta_click` | `cta_id`, `cta_variant` |
274
+ | `search` | `search_query` |
148
275
 
149
276
  ---
150
277
 
151
- ## Package exports
278
+ ## Environment variables (Next.js)
152
279
 
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 |
280
+ | Variable | Description |
281
+ | --- | --- |
282
+ | `NEXT_PUBLIC_ANALYTICS_URL` | Backend base URL, e.g. `https://analytics.example.com` |
283
+ | `NEXT_PUBLIC_ANALYTICS_KEY` | Project API key |
157
284
 
158
285
  ---
159
286
 
@@ -164,9 +291,17 @@ pnpm install
164
291
  pnpm build
165
292
  ```
166
293
 
294
+ Outputs:
295
+
296
+ | File | Format | Entry |
297
+ | --- | --- | --- |
298
+ | `dist/index.js` + `dist/index.d.ts` | ESM | `src/index.ts` |
299
+ | `dist/client.js` + `dist/client.d.ts` | ESM | `src/client.ts` |
300
+ | `dist/af-analytics.umd.js` | IIFE/UMD | `src/vanilla.ts` |
301
+
167
302
  ## Publishing
168
303
 
169
- Tag a release (e.g. `v0.5.0`) after adding an `NPM_TOKEN` secret to the repository to trigger the CI publish workflow.
304
+ Tag a release after adding an `NPM_TOKEN` secret to the repository to trigger the CI publish workflow.
170
305
 
171
306
  ---
172
307
 
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+ var AfAnalytics = (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
11
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
12
+ var __spreadValues = (a, b) => {
13
+ for (var prop in b || (b = {}))
14
+ if (__hasOwnProp.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ if (__getOwnPropSymbols)
17
+ for (var prop of __getOwnPropSymbols(b)) {
18
+ if (__propIsEnum.call(b, prop))
19
+ __defNormalProp(a, prop, b[prop]);
20
+ }
21
+ return a;
22
+ };
23
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
24
+ var __export = (target, all) => {
25
+ for (var name in all)
26
+ __defProp(target, name, { get: all[name], enumerable: true });
27
+ };
28
+ var __copyProps = (to, from, except, desc) => {
29
+ if (from && typeof from === "object" || typeof from === "function") {
30
+ for (let key of __getOwnPropNames(from))
31
+ if (!__hasOwnProp.call(to, key) && key !== except)
32
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
33
+ }
34
+ return to;
35
+ };
36
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
37
+
38
+ // src/vanilla.ts
39
+ var vanilla_exports = {};
40
+ __export(vanilla_exports, {
41
+ init: () => init
42
+ });
43
+ var _currentScript = document.currentScript;
44
+ var SESSION_EXPIRY_MS = 30 * 60 * 1e3;
45
+ function readConfig() {
46
+ var _a, _b, _c, _d;
47
+ const script = _currentScript != null ? _currentScript : document.querySelector("script[data-api-key]");
48
+ const apiKey = (_b = (_a = script == null ? void 0 : script.dataset.apiKey) == null ? void 0 : _a.trim()) != null ? _b : "";
49
+ const apiUrl = ((_d = (_c = script == null ? void 0 : script.dataset.url) == null ? void 0 : _c.trim()) != null ? _d : "").replace(/\/$/, "");
50
+ if (!apiKey || !apiUrl) return null;
51
+ return { apiKey, apiUrl };
52
+ }
53
+ function generateId() {
54
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
55
+ return crypto.randomUUID();
56
+ }
57
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
58
+ const r = Math.random() * 16 | 0;
59
+ const v = c === "x" ? r : r & 3 | 8;
60
+ return v.toString(16);
61
+ });
62
+ }
63
+ function getVisitorId() {
64
+ const stored = localStorage.getItem("af_analytics_visitor_id");
65
+ if (stored) return stored;
66
+ const id = generateId();
67
+ localStorage.setItem("af_analytics_visitor_id", id);
68
+ return id;
69
+ }
70
+ function getStoredSession() {
71
+ const id = localStorage.getItem("af_session_id");
72
+ const lastActivity = localStorage.getItem("af_session_last_activity");
73
+ if (!id || !lastActivity) return null;
74
+ if (Date.now() - parseInt(lastActivity, 10) < SESSION_EXPIRY_MS) return id;
75
+ return null;
76
+ }
77
+ function touchSession() {
78
+ localStorage.setItem("af_session_last_activity", String(Date.now()));
79
+ }
80
+ function clearSession() {
81
+ localStorage.removeItem("af_session_id");
82
+ localStorage.removeItem("af_session_last_activity");
83
+ }
84
+ function getGpu() {
85
+ try {
86
+ const canvas = document.createElement("canvas");
87
+ const gl = canvas.getContext("webgl");
88
+ if (!gl) return null;
89
+ const ext = gl.getExtension("WEBGL_debug_renderer_info");
90
+ if (!ext) return null;
91
+ return gl.getParameter(ext.UNMASKED_RENDERER_WEBGL);
92
+ } catch (e) {
93
+ return null;
94
+ }
95
+ }
96
+ function send(apiUrl, apiKey, payload) {
97
+ fetch(`${apiUrl}/events`, {
98
+ method: "POST",
99
+ headers: { "X-API-Key": apiKey, "Content-Type": "application/json" },
100
+ body: JSON.stringify(payload),
101
+ keepalive: true
102
+ }).catch(() => {
103
+ });
104
+ }
105
+ function baseFields(sessionId, eventType) {
106
+ return {
107
+ session_id: sessionId,
108
+ event_type: eventType,
109
+ path: window.location.pathname,
110
+ page_url: window.location.href
111
+ };
112
+ }
113
+ async function createRemoteSession(apiUrl, apiKey, visitorId) {
114
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
115
+ const nav = navigator;
116
+ const conn = (_c = (_b = (_a = nav["connection"]) != null ? _a : nav["mozConnection"]) != null ? _b : nav["webkitConnection"]) != null ? _c : null;
117
+ const params = new URLSearchParams(window.location.search);
118
+ const body = {
119
+ visitor_id: visitorId,
120
+ language: (_d = navigator.language) != null ? _d : null,
121
+ screen_width: (_e = screen.width) != null ? _e : null,
122
+ screen_height: (_f = screen.height) != null ? _f : null,
123
+ viewport_width: (_g = window.innerWidth) != null ? _g : null,
124
+ viewport_height: (_h = window.innerHeight) != null ? _h : null,
125
+ referrer: document.referrer || null,
126
+ landing_page: window.location.pathname,
127
+ timezone: (_i = Intl.DateTimeFormat().resolvedOptions().timeZone) != null ? _i : null,
128
+ cpu_threads: (_j = navigator.hardwareConcurrency) != null ? _j : null,
129
+ memory_gb: (_k = nav["deviceMemory"]) != null ? _k : null,
130
+ gpu: getGpu(),
131
+ network_type: (_l = conn == null ? void 0 : conn["effectiveType"]) != null ? _l : null,
132
+ connection_speed_mbps: (_m = conn == null ? void 0 : conn["downlink"]) != null ? _m : null,
133
+ utm_source: params.get("utm_source"),
134
+ utm_medium: params.get("utm_medium"),
135
+ utm_campaign: params.get("utm_campaign"),
136
+ utm_term: params.get("utm_term"),
137
+ utm_content: params.get("utm_content")
138
+ };
139
+ const res = await fetch(`${apiUrl}/sessions`, {
140
+ method: "POST",
141
+ headers: { "X-API-Key": apiKey, "Content-Type": "application/json" },
142
+ body: JSON.stringify(body)
143
+ });
144
+ const data = await res.json();
145
+ return data.id;
146
+ }
147
+ async function getOrCreateSession(apiUrl, apiKey) {
148
+ const stored = getStoredSession();
149
+ if (stored) {
150
+ touchSession();
151
+ return stored;
152
+ }
153
+ const id = await createRemoteSession(apiUrl, apiKey, getVisitorId());
154
+ localStorage.setItem("af_session_id", id);
155
+ localStorage.setItem("af_session_last_activity", String(Date.now()));
156
+ return id;
157
+ }
158
+ function trackPageview(apiUrl, apiKey, sessionId) {
159
+ send(apiUrl, apiKey, __spreadProps(__spreadValues({}, baseFields(sessionId, "pageview")), {
160
+ page_title: document.title || null,
161
+ referrer: document.referrer || null,
162
+ scroll_depth: 0
163
+ }));
164
+ }
165
+ function trackClick(apiUrl, apiKey, sessionId, e) {
166
+ var _a;
167
+ const el = e.target;
168
+ send(apiUrl, apiKey, __spreadProps(__spreadValues({}, baseFields(sessionId, "click")), {
169
+ x_position: e.clientX,
170
+ y_position: e.clientY,
171
+ element_id: el.id || null,
172
+ element_class: el.className || null,
173
+ element_text: ((_a = el.innerText) == null ? void 0 : _a.slice(0, 100)) || null
174
+ }));
175
+ }
176
+ function trackScroll(apiUrl, apiKey, sessionId, depth) {
177
+ send(apiUrl, apiKey, __spreadProps(__spreadValues({}, baseFields(sessionId, "scroll")), { scroll_depth: depth }));
178
+ }
179
+ function trackError(apiUrl, apiKey, sessionId, error) {
180
+ send(apiUrl, apiKey, __spreadProps(__spreadValues({}, baseFields(sessionId, "error")), {
181
+ error_message: error.message,
182
+ stack_trace: error.stack || null
183
+ }));
184
+ }
185
+ function init() {
186
+ const config = readConfig();
187
+ if (!config) return;
188
+ const { apiKey, apiUrl } = config;
189
+ let sessionId = null;
190
+ const pending = [];
191
+ let sessionEnded = false;
192
+ let lastScrollDepth = 0;
193
+ function enqueue(fn) {
194
+ if (sessionId) {
195
+ touchSession();
196
+ fn(sessionId);
197
+ } else {
198
+ pending.push(fn);
199
+ }
200
+ }
201
+ getOrCreateSession(apiUrl, apiKey).then((id) => {
202
+ sessionId = id;
203
+ pending.splice(0).forEach((fn) => fn(id));
204
+ }).catch(() => {
205
+ });
206
+ enqueue((id) => trackPageview(apiUrl, apiKey, id));
207
+ window.addEventListener("click", (e) => {
208
+ enqueue((id) => trackClick(apiUrl, apiKey, id, e));
209
+ });
210
+ window.addEventListener(
211
+ "scroll",
212
+ () => {
213
+ const total = document.documentElement.scrollHeight - window.innerHeight;
214
+ if (total <= 0) return;
215
+ const depth = Math.round(window.scrollY / total * 100);
216
+ for (const milestone of [25, 50, 75, 100]) {
217
+ if (depth >= milestone && lastScrollDepth < milestone) {
218
+ lastScrollDepth = milestone;
219
+ enqueue((id) => trackScroll(apiUrl, apiKey, id, milestone));
220
+ }
221
+ }
222
+ },
223
+ { passive: true }
224
+ );
225
+ window.addEventListener("error", (e) => {
226
+ enqueue((id) => trackError(apiUrl, apiKey, id, new Error(e.message)));
227
+ });
228
+ const endSession = () => {
229
+ if (!sessionId || sessionEnded) return;
230
+ sessionEnded = true;
231
+ navigator.sendBeacon(`${apiUrl}/sessions/${sessionId}/end`);
232
+ clearSession();
233
+ };
234
+ document.addEventListener("visibilitychange", () => {
235
+ if (document.visibilityState === "hidden") endSession();
236
+ });
237
+ window.addEventListener("beforeunload", endSession);
238
+ }
239
+ if (document.readyState === "loading") {
240
+ document.addEventListener("DOMContentLoaded", init);
241
+ } else {
242
+ init();
243
+ }
244
+ return __toCommonJS(vanilla_exports);
245
+ })();
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  { "name": "@arthurreira/analytics",
2
- "version": "0.11.0",
2
+ "version": "0.12.0",
3
3
  "type": "module",
4
4
  "scripts": {
5
5
  "build": "tsup",