You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+32-2Lines changed: 32 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,36 @@
1
1
# Changelog
2
2
3
-
## Unreleased
3
+
## 1.0.0 (2026-03-04)
4
+
5
+
### Breaking Changes
6
+
7
+
-**Normalize `ConnectionThresholds` field names** — Renamed `downlink2gUpperBound` → `lowUpperBound`, `downlink3gUpperBound` → `midUpperBound`, `downlink4gUpperBound` → `highUpperBound` to match the `CpuThresholds`/`MemoryThresholds` naming pattern. Update any custom `connection` threshold objects in your `thresholds` config
8
+
-**Rename `ConnectionTier` value `'fast'` → `'high'`** — Aligns connection tier vocabulary with CPU, memory, and GPU tiers which all use `'low' | 'mid' | 'high'`. Update any code comparing `tiers.connection === 'fast'` to use `'high'` instead
9
+
-**`classify()` and `deriveHints()` now accept `StoredSignals` instead of `RawSignals`** — These functions never used `userAgent` or `viewport` (which are stripped before storage). The narrower type makes the API honest. Existing call sites are unaffected — `RawSignals` is structurally assignable to `StoredSignals`
10
+
-**`profile:store` event now carries `StoredSignals` instead of `RawSignals`** — The event previously emitted the raw probe payload (including `userAgent`/`viewport`), not what was actually stored. The `signals` field now matches the persisted data. `bot:reject` still carries `RawSignals` (it fires before stripping)
11
+
-**Rename default cookie `dr_session` → `device-router-session`** — Self-documenting name before 1.0 locks the cookie in. If you hardcode `cookieName: 'dr_session'` in your options you are unaffected; if you rely on the default, existing sessions will reset once on deploy
12
+
-**Remove `disableAutoplay` rendering hint** — `disableAutoplay` triggered on identical conditions to `deferHeavyComponents` (`isLowEnd || isSlowConnection || isBatteryConstrained`). Use `deferHeavyComponents` instead
13
+
-**Remove `has()` from `StorageAdapter`** — `has()` was a redundant alias for `exists()`. Custom adapters must remove their `has()` implementation
14
+
-**middleware-fastify: normalized return shape** — `createDeviceRouter()` now returns raw Fastify hooks instead of a `fastify-plugin` wrapped plugin. Migrate `await app.register(middleware)` → `app.addHook('preHandler', middleware)`. When using `injectProbe: true`, register the injection hook separately: `app.addHook('onSend', injectionMiddleware)`. Removed `fastify-plugin` dependency
15
+
16
+
### Migration Guide
17
+
18
+
If you have a custom `StorageAdapter` implementation:
19
+
20
+
- Remove the `has()` method — use `exists()` instead
21
+
- Replace any `adapter.has(token)` calls with `adapter.exists(token)`
22
+
23
+
### Features
24
+
25
+
-**Composable middleware** — `createMiddleware()`, `createProbeEndpoint()`, and `createInjectionMiddleware()` are now first-class exports with full threshold validation and documentation. Use them independently for fine-grained control without the `createDeviceRouter()` factory
26
+
-**`loadProbeScript()` utility** — New helper exported from all middleware packages that reads the minified probe bundle and optionally rewrites the endpoint URL. Pairs with `createInjectionMiddleware()` for standalone probe injection
27
+
-**`clear()` on StorageAdapter** — `clear()` is now part of the `StorageAdapter` interface. `MemoryStorageAdapter` and `RedisStorageAdapter` both implement it. Redis implementation uses SCAN (when available) or KEYS + `DEL` with graceful error handling
28
+
-**StorageAdapter introspection** — New `count()` and `keys()` methods on `StorageAdapter` for inspecting stored profiles. `keys()` returns session tokens (not prefixed keys). All methods handle errors gracefully on Redis
29
+
-**Redis SCAN support** — `RedisStorageAdapter` uses the optional `scan()` method on the client for `clear()`, `count()`, and `keys()` operations, avoiding the blocking `KEYS` command. Falls back to `KEYS` when `scan` is not available
30
+
-**`durationMs` on `bot:reject` event** — The `bot:reject` event now includes `durationMs` measuring validation and bot detection time, matching the pattern used by `profile:store`
31
+
-**`errorMessage` on error events** — Error events now include a pre-extracted `errorMessage: string` field, avoiding the need to narrow the `error: unknown` field. New `extractErrorMessage()` helper exported from `@device-router/types`
32
+
-**New rendering hints** — Three new hints: `limitVideoQuality` (slow connection or low battery), `useSystemFonts` (low-end device or slow connection), `disablePrefetch` (slow connection or low battery)
33
+
-**Probe retry logic** — New `runProbeWithRetry()` function with exponential backoff and jitter. Exported from `@device-router/probe` as an ESM-only export (does not affect the IIFE bundle size)
-**Battery API signal** — Collect battery level and charging status via `navigator.getBattery()` (Chromium-only, silently skipped elsewhere). When unplugged and below 15%, `deferHeavyComponents`, `reduceAnimations`, and `disableAutoplay` are forced on
80
+
-**Battery API signal** — Collect battery level and charging status via `navigator.getBattery()` (Chromium-only, silently skipped elsewhere). When unplugged and below 15%, `deferHeavyComponents`and `reduceAnimations` are forced on
51
81
-**Signal validation** — New `isValidSignals()` type guard for validating incoming probe payloads
52
82
-**Custom GPU thresholds** — `softwarePattern` and `highEndPattern` are configurable via `GpuThresholds`
Copy file name to clipboardExpand all lines: README.md
+24-4Lines changed: 24 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -18,7 +18,7 @@ A **~1 KB** client probe. One middleware call. Full device awareness on every re
18
18
Responsive design adapts layout. DeviceRouter adapts **what you serve**.
19
19
20
20
- A budget phone on 2G? Skip the heavy animations, defer non-critical components, prefer server-side rendering.
21
-
- A flagship on fiber? Go all out — autoplay, full interactivity, rich visuals.
21
+
- A flagship on fiber? Go all out — full interactivity, rich visuals.
22
22
23
23
No user-agent sniffing. No guesswork. Real signals from real devices, classified into actionable tiers and rendering hints your server can act on immediately.
24
24
@@ -120,7 +120,6 @@ Based on tiers and user preferences, DeviceRouter derives actionable booleans:
120
120
|`serveMinimalCSS`| Low-end device |
121
121
|`reduceAnimations`| Low-end device, prefers reduced motion, or low battery |
0 commit comments