Factory that creates the Hono middleware and probe endpoint.
import { Hono } from 'hono';
import { createDeviceRouter } from '@device-router/middleware-hono';
import type { DeviceRouterEnv } from '@device-router/middleware-hono';
import { MemoryStorageAdapter } from '@device-router/storage';
const app = new Hono<DeviceRouterEnv>();
const { middleware, probeEndpoint } = createDeviceRouter({
storage: new MemoryStorageAdapter(),
});
app.post('/device-router/probe', probeEndpoint);
app.use('*', middleware);| Option | Type | Default | Description |
|---|---|---|---|
storage |
StorageAdapter |
required | Storage backend |
cookieName |
string |
'device-router-session' |
Session cookie name |
cookiePath |
string |
'/' |
Cookie path |
cookieSecure |
boolean |
false |
Set Secure flag on the session cookie |
ttl |
number |
86400 |
Profile TTL in seconds |
rejectBots |
boolean |
true |
Reject bot/crawler probe submissions (returns 403) |
thresholds |
TierThresholds |
built-in defaults | Custom tier classification thresholds (validated at startup) |
injectProbe |
boolean |
false |
Auto-inject probe script into HTML responses |
probePath |
string |
'/device-router/probe' |
Custom probe endpoint path for injected script |
probeNonce |
string | ((c: Context) => string) |
— | CSP nonce for the injected script tag |
fallbackProfile |
FallbackProfile |
— | Fallback profile for first requests without probe data |
classifyFromHeaders |
boolean |
false |
Classify from UA/Client Hints on first request |
onEvent |
OnEventCallback |
— | Observability callback for logging/metrics (guide) |
| Property | Type | Description |
|---|---|---|
middleware |
Hono middleware | Reads session, classifies device, sets deviceProfile variable |
probeEndpoint |
Hono handler | Handles POST from probe, validates and stores signals |
injectionMiddleware |
Hono middleware or undefined |
Only present when injectProbe: true |
The middleware sets a ClassifiedProfile | null on the Hono context:
app.get('/', (c) => {
const profile = c.get('deviceProfile');
// ...
});Type your Hono app with DeviceRouterEnv for type-safe access to deviceProfile:
import type { DeviceRouterEnv } from '@device-router/middleware-hono';
const app = new Hono<DeviceRouterEnv>();When injectProbe: true, a middleware is returned that intercepts HTML responses after next() and injects the probe <script>. Uses Web API Response replacement — edge-safe, no Node.js-specific APIs at runtime.
const { middleware, probeEndpoint, injectionMiddleware } = createDeviceRouter({
storage,
injectProbe: true,
});
// Register injection middleware before other middleware
if (injectionMiddleware) {
app.use('*', injectionMiddleware);
}
app.use('*', middleware);Note: fs.readFileSync is called once at initialization to load the probe bundle. At runtime, only Web APIs are used.
The individual pieces can be used independently for more granular control.
Reads and returns the minified probe script. Use this with createInjectionMiddleware() when you need probe injection without the full factory.
import { loadProbeScript } from '@device-router/middleware-hono';
const probeScript = loadProbeScript();
// or with a custom endpoint path:
const probeScript = loadProbeScript({ probePath: '/custom/probe' });| Option | Type | Default | Description |
|---|---|---|---|
probePath |
string |
— | Custom probe endpoint path (rewrites the URL in the script) |
Creates the Hono middleware independently. Thresholds are validated at creation time.
import { createMiddleware } from '@device-router/middleware-hono';
const middleware = createMiddleware({
storage,
thresholds: { cpu: { lowUpperBound: 4, midUpperBound: 8 } },
});
app.use('*', middleware);| Option | Type | Default | Description |
|---|---|---|---|
storage |
StorageAdapter |
(required) | Storage backend for profiles |
cookieName |
string |
'device-router-session' |
Session cookie name |
thresholds |
TierThresholds |
Built-in | Custom tier thresholds (validated at creation) |
fallbackProfile |
FallbackProfile |
— | Fallback profile for first requests |
classifyFromHeaders |
boolean |
false |
Classify from UA/Client Hints on first request |
onEvent |
OnEventCallback |
— | Observability callback |
Creates the probe POST handler independently.
import { createProbeEndpoint } from '@device-router/middleware-hono';
const endpoint = createProbeEndpoint({
storage,
ttl: 3600,
rejectBots: true,
});
app.post('/device-router/probe', endpoint);| Option | Type | Default | Description |
|---|---|---|---|
storage |
StorageAdapter |
(required) | Storage backend for profiles |
cookieName |
string |
'device-router-session' |
Session cookie name |
cookiePath |
string |
'/' |
Cookie path |
cookieSecure |
boolean |
false |
Set Secure flag on the cookie |
ttl |
number |
86400 |
Profile TTL in seconds |
rejectBots |
boolean |
true |
Reject bot/crawler probe submissions |
onEvent |
OnEventCallback |
— | Observability callback |
Creates the probe script injection middleware independently. Pair with loadProbeScript() to load the probe bundle.
import { createInjectionMiddleware, loadProbeScript } from '@device-router/middleware-hono';
const injection = createInjectionMiddleware({
probeScript: loadProbeScript({ probePath: '/api/probe' }),
nonce: 'my-csp-nonce',
});
app.use('*', injection);| Option | Type | Default | Description |
|---|---|---|---|
probeScript |
string |
(required) | The minified probe script source |
nonce |
string | ((c: Context) => string) |
— | CSP nonce for the injected script |
Use the pieces independently when you need fine-grained control:
import { Hono } from 'hono';
import {
createMiddleware,
createProbeEndpoint,
createInjectionMiddleware,
loadProbeScript,
} from '@device-router/middleware-hono';
import type { DeviceRouterEnv } from '@device-router/middleware-hono';
import { MemoryStorageAdapter } from '@device-router/storage';
const app = new Hono<DeviceRouterEnv>();
const storage = new MemoryStorageAdapter();
// Each piece is configured independently
const middleware = createMiddleware({ storage });
const endpoint = createProbeEndpoint({ storage, ttl: 3600 });
const injection = createInjectionMiddleware({
probeScript: loadProbeScript(),
});
app.use('*', injection);
app.use('*', middleware);
app.post('/device-router/probe', endpoint);
app.get('/', (c) => {
const profile = c.get('deviceProfile');
return c.json({ tiers: profile?.tiers });
});
export default app;- Hono uses built-in cookie helpers (
hono/cookie) — no additional cookie package needed @device-router/probemust be installed when usinginjectProbe: true