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.
- package/README.md +263 -63
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,119 +1,319 @@
|
|
|
1
1
|
# Solo Analytics
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
71
|
+
Despite the `use` prefix (Vue convention), the function is **framework-agnostic**. Same API in React, Nuxt, Svelte, or plain JavaScript.
|
|
12
72
|
|
|
13
|
-
|
|
73
|
+
---
|
|
14
74
|
|
|
15
|
-
|
|
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
|
-
|
|
77
|
+
`analytics.data` is a `SoloAnalyticsInfo` object. Here is what each section contains and when you might use it.
|
|
20
78
|
|
|
21
|
-
|
|
79
|
+
### Browser, OS, and device
|
|
22
80
|
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
+
Shortcuts on the return value:
|
|
35
90
|
|
|
36
91
|
```typescript
|
|
37
|
-
|
|
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
|
-
|
|
99
|
+
### Screen
|
|
40
100
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
101
|
+
```typescript
|
|
102
|
+
analytics.data.screen;
|
|
103
|
+
// width, height, availWidth, availHeight
|
|
104
|
+
// pixelRatio, colorDepth, orientation, touchPoints
|
|
105
|
+
```
|
|
44
106
|
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
117
|
+
Also exposed as `analytics.isOnline`. Enable `autoRefresh: true` if you need live updates when connection quality changes.
|
|
50
118
|
|
|
51
|
-
###
|
|
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
|
|
56
|
-
refreshInterval: 30000, // ms between polls when autoRefresh
|
|
57
|
-
trackVisibility: true, //
|
|
58
|
-
detectFeatures: true, // run async probes (incognito, battery, media
|
|
59
|
-
lazyFeatures: true, // defer async probes
|
|
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
|
-
|
|
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
|
-
###
|
|
176
|
+
### Minimal setup (fastest)
|
|
66
177
|
|
|
67
|
-
|
|
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
|
-
|
|
180
|
+
```typescript
|
|
181
|
+
const analytics = useSoloAnalytics({
|
|
182
|
+
detectFeatures: false,
|
|
183
|
+
trackVisibility: false,
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Framework integration
|
|
78
190
|
|
|
79
|
-
|
|
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
|
-
|
|
193
|
+
### Vue / Nuxt
|
|
82
194
|
|
|
83
|
-
|
|
195
|
+
```vue
|
|
196
|
+
<script setup lang="ts">
|
|
197
|
+
import { onMounted, onUnmounted, ref } from "vue";
|
|
198
|
+
import { useSoloAnalytics } from "solo-analytics";
|
|
84
199
|
|
|
85
|
-
|
|
200
|
+
const isMobile = ref(false);
|
|
201
|
+
let analytics: ReturnType<typeof useSoloAnalytics> | null = null;
|
|
86
202
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
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)
|
|
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 +
|
|
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,
|
|
111
|
-
vp check --fix # auto-fix format/lint
|
|
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
|
|
316
|
+
Issues and pull requests welcome on [GitHub](https://github.com/cesswhite/solo-analytics).
|
|
117
317
|
|
|
118
318
|
## License
|
|
119
319
|
|