solo-analytics 0.3.0 → 0.3.2

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.
Files changed (2) hide show
  1. package/README.md +263 -63
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,119 +1,319 @@
1
1
  # Solo Analytics
2
2
 
3
- Client-side library that reads browser APIs and returns structured device, network, and environment data. One function call, typed output, no backend required.
3
+ **Read browser context once. Use it anywhere.**
4
+
5
+ Solo Analytics collects device, network, and environment data from the browser and returns it as a typed object. No backend, no vendor, no tracking — you decide what to do with the data.
4
6
 
5
7
  ```bash
6
8
  npm install solo-analytics
7
9
  ```
8
10
 
9
- ## Why this exists
11
+ ---
12
+
13
+ ## Quick start
14
+
15
+ ```typescript
16
+ import { useSoloAnalytics } from "solo-analytics";
17
+
18
+ const analytics = useSoloAnalytics();
19
+
20
+ // Shortcuts for common checks
21
+ console.log(analytics.isMobile); // true | false
22
+ console.log(analytics.browserName); // "Chrome", "Safari", "Firefox"…
23
+ console.log(analytics.osName); // "macOS", "Windows", "Android"…
24
+
25
+ // Full payload
26
+ console.log(analytics.data);
27
+
28
+ // Clean up when you're done (SPAs, component unmount)
29
+ analytics.destroy();
30
+ ```
31
+
32
+ That is the entire API surface for most use cases. Everything below explains what you get, how to configure it, and how to integrate it in your stack.
33
+
34
+ ---
35
+
36
+ ## What problem does this solve?
37
+
38
+ Most analytics SDKs are built to **send events to a vendor**. Solo Analytics does something different: it **reads context from the browser** and hands it back to you.
39
+
40
+ Typical uses:
41
+
42
+ | Goal | Example |
43
+ | ------------------------- | ------------------------------------------------------------------------------------------------------- |
44
+ | Enrich your own analytics | Attach `{ browser, device, network }` to every event you already send to PostHog, Mixpanel, or your API |
45
+ | Adapt the UI | Show a lite mode on slow connections (`effectiveType === "2g"`) or adjust layout for mobile |
46
+ | Debug client environments | One object instead of scattered `navigator` / `window` checks |
47
+
48
+ **What it is not:** a tracking product. It does not phone home, set cookies, or manage sessions.
49
+
50
+ ---
51
+
52
+ ## How it works
53
+
54
+ When you call `useSoloAnalytics()`, the library collects data in two phases:
55
+
56
+ ```
57
+ ┌─────────────────────────────────────────────────────────────┐
58
+ │ Phase 1 — Immediate (sync) │
59
+ │ User agent, screen, locale, network snapshot, performance │
60
+ └─────────────────────────────────────────────────────────────┘
61
+
62
+ ┌─────────────────────────────────────────────────────────────┐
63
+ │ Phase 2 — Deferred (async, on idle time by default) │
64
+ │ Incognito hint, battery, camera/mic, permission states │
65
+ └─────────────────────────────────────────────────────────────┘
66
+ ```
67
+
68
+ - **Sync data** is available right after initialization.
69
+ - **Async probes** run when the browser is idle (`requestIdleCallback`) so they do not block rendering. Call `refresh()` if you need them immediately.
10
70
 
11
- Most analytics SDKs are built to send events to a vendor. Solo Analytics does something narrower: it collects **context** from the browser—user agent, screen size, connection quality, locale, performance timing—and gives it back to you as a plain object.
71
+ Despite the `use` prefix (Vue convention), the function is **framework-agnostic**. Same API in React, Nuxt, Svelte, or plain JavaScript.
12
72
 
13
- Use it when you need to:
73
+ ---
14
74
 
15
- - Attach device context to your own analytics pipeline
16
- - Adapt UI or features based on connection, screen, or device type
17
- - Debug client environments without sprinkling `navigator` checks across your app
75
+ ## The data object
18
76
 
19
- It is not a tracking product. It does not phone home, store cookies, or manage sessions. You decide what to do with the data.
77
+ `analytics.data` is a `SoloAnalyticsInfo` object. Here is what each section contains and when you might use it.
20
78
 
21
- ## What you get
79
+ ### Browser, OS, and device
22
80
 
23
- | Area | Data |
24
- | --------------------- | ---------------------------------------------------------------- |
25
- | Browser / OS / device | Parsed UA, engine, vendor, mobile/tablet/desktop classification |
26
- | Screen | Dimensions, pixel ratio, orientation, touch points |
27
- | Network | Online status, effective type, downlink, RTT, save-data flag |
28
- | Performance | Navigation type, load timing, paint metrics, memory (Chrome) |
29
- | Environment | Timezone, languages, storage availability, DNT, iframe detection |
30
- | Optional probes | Incognito hint, camera/mic presence, battery, permission states |
81
+ Parsed from `navigator.userAgent`:
31
82
 
32
- Sync data (UA, screen, locale) is collected immediately. Heavier async probes run on idle time by default so they stay off the critical path.
83
+ ```typescript
84
+ analytics.data.browser; // name, version, engine, vendor…
85
+ analytics.data.os; // name, version, architecture
86
+ analytics.data.device; // type, isMobile, isTablet, isDesktop, touch…
87
+ ```
33
88
 
34
- ## Usage
89
+ Shortcuts on the return value:
35
90
 
36
91
  ```typescript
37
- import { useSoloAnalytics } from "solo-analytics";
92
+ analytics.isMobile; // analytics.data.device.isMobile
93
+ analytics.isTablet;
94
+ analytics.isDesktop;
95
+ analytics.browserName; // analytics.data.browser.name
96
+ analytics.osName; // analytics.data.os.name
97
+ ```
38
98
 
39
- const analytics = useSoloAnalytics();
99
+ ### Screen
40
100
 
41
- console.log(analytics.isMobile);
42
- console.log(analytics.browserName);
43
- console.log(analytics.data);
101
+ ```typescript
102
+ analytics.data.screen;
103
+ // width, height, availWidth, availHeight
104
+ // pixelRatio, colorDepth, orientation, touchPoints
105
+ ```
44
106
 
45
- // When done (SPA unmount, etc.)
46
- analytics.destroy();
107
+ Useful for responsive decisions, screenshot tooling, or debugging layout issues reported by users.
108
+
109
+ ### Network
110
+
111
+ ```typescript
112
+ analytics.data.network;
113
+ // online, effectiveType ("4g", "3g", "slow-2g"…)
114
+ // downlink (Mbps), rtt (ms), saveData
47
115
  ```
48
116
 
49
- Despite the name, `useSoloAnalytics` is framework-agnostic. The `use` prefix matches Vue conventions, but it works the same in React, Nuxt, or plain JS—initialize on the client, call `destroy()` on teardown.
117
+ Also exposed as `analytics.isOnline`. Enable `autoRefresh: true` if you need live updates when connection quality changes.
50
118
 
51
- ### Options
119
+ ### Performance
120
+
121
+ ```typescript
122
+ analytics.data.performance;
123
+ // navigation type, load timing, first paint / FCP
124
+ // memory (Chrome only — null elsewhere)
125
+ ```
126
+
127
+ ### Environment and privacy signals
128
+
129
+ ```typescript
130
+ analytics.data.location;
131
+ // timeZone, language, languages
132
+ // cookiesEnabled, localStorage, sessionStorage
133
+ // doNotTrack, isRestricted (iframe detection)
134
+ ```
135
+
136
+ ### Optional async fields
137
+
138
+ Populated after idle-time probes (or immediately after `await analytics.refresh()`):
139
+
140
+ ```typescript
141
+ analytics.data.isIncognito; // best-effort hint — see Limitations
142
+ analytics.data.hasCamera; // boolean | null
143
+ analytics.data.hasMicrophone; // boolean | null
144
+ analytics.data.hasBattery;
145
+ analytics.data.batteryLevel;
146
+ analytics.data.batteryCharging;
147
+ analytics.data.permissions; // Record<string, string>
148
+ ```
149
+
150
+ Set `detectFeatures: false` to skip async probes entirely.
151
+
152
+ ---
153
+
154
+ ## Configuration
155
+
156
+ All options are optional. Defaults are sensible for most apps.
52
157
 
53
158
  ```typescript
54
159
  const analytics = useSoloAnalytics({
55
- autoRefresh: false, // poll network + performance (default: false)
56
- refreshInterval: 30000, // ms between polls when autoRefresh is true
57
- trackVisibility: true, // listen for visibilitychange
58
- detectFeatures: true, // run async probes (incognito, battery, media, permissions)
59
- lazyFeatures: true, // defer async probes via requestIdleCallback (default: true)
160
+ autoRefresh: false, // poll network + performance on an interval
161
+ refreshInterval: 30000, // ms between polls (only when autoRefresh: true)
162
+ trackVisibility: true, // update pageVisibility on visibilitychange
163
+ detectFeatures: true, // run async probes (incognito, battery, media)
164
+ lazyFeatures: true, // defer async probes to idle time
60
165
  });
61
166
  ```
62
167
 
63
- Set `detectFeatures: false` if you only need UA, screen, and network data. That skips the async work entirely.
168
+ | Option | Default | When to change it |
169
+ | ----------------- | ------- | -------------------------------------------------------------------------------------- |
170
+ | `autoRefresh` | `false` | Set `true` if network or performance data must stay current (e.g. connection-aware UI) |
171
+ | `refreshInterval` | `30000` | Lower for faster updates; higher to reduce work |
172
+ | `trackVisibility` | `true` | Set `false` if you never read `pageVisibility` or `isVisible` |
173
+ | `detectFeatures` | `true` | Set `false` for minimal bundle work — UA, screen, and network only |
174
+ | `lazyFeatures` | `true` | Set `false` if you need camera/battery/incognito data on first paint |
64
175
 
65
- ### API
176
+ ### Minimal setup (fastest)
66
177
 
67
- | Member | Description |
68
- | ----------------------------------- | ------------------------------------------------------------------ |
69
- | `data` | Full `SoloAnalyticsInfo` object (see `src/types/soloAnalytics.ts`) |
70
- | `refresh()` | Re-collect sync, dynamic, and async data |
71
- | `destroy()` | Clear timers and event listeners |
72
- | `isMobile`, `isTablet`, `isDesktop` | Device class shortcuts |
73
- | `isOnline` | `navigator.onLine` |
74
- | `isVisible` | Page visibility state |
75
- | `browserName`, `osName` | Parsed names |
178
+ Only sync data — no idle callbacks, no async probes:
76
179
 
77
- Types are exported: `SoloAnalyticsInfo`, `SoloAnalyticsOptions`, `SoloAnalyticsReturn`, and per-section interfaces.
180
+ ```typescript
181
+ const analytics = useSoloAnalytics({
182
+ detectFeatures: false,
183
+ trackVisibility: false,
184
+ });
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Framework integration
78
190
 
79
- ### SSR
191
+ Initialize **on the client only**. The library guards missing `window` / `navigator`; on the server, `data` stays at its initial empty state.
80
192
 
81
- The library guards against missing `window` / `navigator`. On the server, `data` stays at its initial empty state. Initialize in `onMounted` (Vue/Nuxt) or `useEffect` (React), not during SSR.
193
+ ### Vue / Nuxt
82
194
 
83
- ### Limitations
195
+ ```vue
196
+ <script setup lang="ts">
197
+ import { onMounted, onUnmounted, ref } from "vue";
198
+ import { useSoloAnalytics } from "solo-analytics";
84
199
 
85
- Be aware of what this library can and cannot guarantee:
200
+ const isMobile = ref(false);
201
+ let analytics: ReturnType<typeof useSoloAnalytics> | null = null;
86
202
 
87
- | Topic | Reality |
88
- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
89
- | UA parsing | Regex-based, not a full UA database. Edge cases exist (iPad desktop mode, new browsers, reduced UA strings). |
90
- | Client Hints | `navigator.userAgentData` is not used yet. |
91
- | Performance timing | Uses Navigation Timing where available; some fields are Chrome-only (`performance.memory`). |
92
- | Incognito detection | Best-effort hint via IndexedDB — unreliable across browsers. |
93
- | Camera / microphone | `enumerateDevices` may return empty until the user grants permission. `false` does not always mean absent. |
94
- | Mutable `data` | The `data` object updates in place. There is no reactive subscription — call `refresh()` or read getters after you know data changed. |
203
+ onMounted(() => {
204
+ analytics = useSoloAnalytics();
205
+ isMobile.value = analytics.isMobile;
206
+ });
207
+
208
+ onUnmounted(() => {
209
+ analytics?.destroy();
210
+ });
211
+ </script>
212
+ ```
213
+
214
+ In Nuxt, keep initialization inside `onMounted` — not in `<script setup>` top level during SSR.
215
+
216
+ ### React
217
+
218
+ ```tsx
219
+ import { useEffect, useState } from "react";
220
+ import { useSoloAnalytics } from "solo-analytics";
221
+
222
+ function App() {
223
+ const [isMobile, setIsMobile] = useState(false);
224
+
225
+ useEffect(() => {
226
+ const analytics = useSoloAnalytics();
227
+ setIsMobile(analytics.isMobile);
228
+ return () => analytics.destroy();
229
+ }, []);
230
+
231
+ return <div>{isMobile ? "Mobile" : "Desktop"}</div>;
232
+ }
233
+ ```
234
+
235
+ ### Vanilla JS
236
+
237
+ See the [demo playground](src/demo/main.ts): call `useSoloAnalytics()`, read `analytics.data`, call `analytics.refresh()` on demand, and `analytics.destroy()` on `beforeunload`.
238
+
239
+ ---
240
+
241
+ ## API reference
242
+
243
+ | Member | Type | Description |
244
+ | ------------- | ------------------- | ---------------------------------------- |
245
+ | `data` | `SoloAnalyticsInfo` | Full context object (updates in place) |
246
+ | `refresh()` | `Promise<void>` | Re-collect sync, dynamic, and async data |
247
+ | `destroy()` | `void` | Clear timers and event listeners |
248
+ | `isMobile` | `boolean` | Device class shortcut |
249
+ | `isTablet` | `boolean` | Device class shortcut |
250
+ | `isDesktop` | `boolean` | Device class shortcut |
251
+ | `isOnline` | `boolean` | `navigator.onLine` |
252
+ | `isVisible` | `boolean` | Page is currently visible |
253
+ | `browserName` | `string` | Parsed browser name |
254
+ | `osName` | `string` | Parsed OS name |
255
+
256
+ ### Exported types
257
+
258
+ ```typescript
259
+ import type {
260
+ SoloAnalyticsInfo,
261
+ SoloAnalyticsOptions,
262
+ SoloAnalyticsReturn,
263
+ BrowserInfo,
264
+ DeviceInfo,
265
+ NetworkInfo,
266
+ ScreenInfo,
267
+ PerformanceInfo,
268
+ LocationInfo,
269
+ OSInfo,
270
+ } from "solo-analytics";
271
+ ```
272
+
273
+ ### Reactivity note
274
+
275
+ `data` is a **mutable object** updated in place. There is no subscription API — after async probes finish, call `refresh()` or read getters again. In Vue/React, copy values into reactive state if you need re-renders.
276
+
277
+ ---
278
+
279
+ ## Limitations (read before relying on edge cases)
280
+
281
+ | Topic | What to expect |
282
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------- |
283
+ | **User-agent parsing** | Regex-based, not a full UA database. Edge cases exist (iPad desktop mode, new browsers, reduced UA strings). |
284
+ | **Client Hints** | `navigator.userAgentData` is not used yet. |
285
+ | **Performance memory** | `performance.memory` is Chrome-only; other browsers return `null`. |
286
+ | **Incognito detection** | Best-effort via IndexedDB — unreliable across browsers. Treat as a hint, not proof. |
287
+ | **Camera / microphone** | `enumerateDevices` may return empty until the user grants permission. `false` does not always mean hardware is absent. |
288
+ | **SSR** | Server-side calls produce empty defaults. Always init on the client. |
289
+
290
+ ---
95
291
 
96
292
  ## Bundle size
97
293
 
98
- Roughly 3–4 KB gzipped. `sideEffects: false` in `package.json` so bundlers can tree-shake unused exports.
294
+ Roughly **3–4 KB gzipped**. `sideEffects: false` in `package.json` bundlers can tree-shake unused exports.
295
+
296
+ ---
99
297
 
100
298
  ## Development
101
299
 
102
- This project uses [Vite+](https://viteplus.dev/) (Vite, Vitest, Oxlint, Oxfmt, tsdown) via the `vp` CLI.
300
+ This project uses [Vite+](https://viteplus.dev/) (`vp` CLI: Vite, Vitest, Oxlint, Oxfmt, tsdown).
103
301
 
104
302
  ```bash
105
303
  npm install
106
304
  vp dev # local demo playground
107
- vp pack # build library (ESM + CJS + bundled types, publint + attw)
305
+ vp pack # build library (ESM + CJS + types)
108
306
  vp test # unit tests
109
307
  vp test run --coverage
110
- vp check # format, lint, and type-check
111
- vp check --fix # auto-fix format/lint issues
308
+ vp check # format, lint, type-check
309
+ vp check --fix # auto-fix format/lint
112
310
  ```
113
311
 
312
+ ---
313
+
114
314
  ## Contributing
115
315
 
116
- Issues and pull requests are welcome on [GitHub](https://github.com/cesswhite/solo-analytics).
316
+ Issues and pull requests welcome on [GitHub](https://github.com/cesswhite/solo-analytics).
117
317
 
118
318
  ## License
119
319
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solo-analytics",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Collect structured browser, device, and network context on the client. Typed, tree-shakeable, no vendor lock-in.",
5
5
  "keywords": [
6
6
  "analytics",