nowo-tech/device-intelligence-bundle
A Symfony bundle for device intelligence: detect device type, browser, OS, and capabilities from user-agent data, with configurable detection rules and Twig/PHP integration for responsive, device-aware applications.
Install and register
composer require nowo-tech/device-intelligence-bundle
This is a Symfony bundle, not a Laravel package — there is no service provider or publishable Laravel config. In a Symfony app, add it to config/bundles.php:
return [
// ...
NowoTech\DeviceIntelligenceBundle\DeviceIntelligenceBundle::class => ['all' => true],
];
In a Laravel app you do not get first-class support. Realistically you have three options before adopting:
Because there are 0 stars and 0 dependents, treat this as an early-stage bundle: read README.md, src/, and config/ in the repo before wiring it into anything user-facing.
Where to look first
src/DependencyInjection/ — what config keys are exposed and what services are registered.src/Controller/ — the collect endpoint route and its payload shape.config/routes.yaml (or a route attribute) — how the collect endpoint is mounted.Minimal config
Create config/packages/device_intelligence.yaml matching the options the bundle actually declares. If config keys are not documented in the README, inspect the Configuration class in src/DependencyInjection/Configuration.php — that is the source of truth.
First use case
The natural first integration is login and checkout risk scoring:
Start by hitting the collect endpoint manually (curl or Insomnia) and inspecting the response before writing any JavaScript.
1. Collect endpoint wiring
The bundle exposes a collect endpoint. Front-load it with a small JS snippet on pages where you need signals (login form, checkout, registration). Keep the snippet isolated in a single entry point rather than scattering calls — consistent with the bundle's frontend rules (src/Resources/assets, src/Resources/public/build).
Pattern:
// one module, one init
export function initDeviceIntelligence() {
window.addEventListener('load', async () => {
await fetch(collectUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'same-origin',
body: JSON.stringify({ /* token + payload per the bundle spec */ }),
});
});
}
Call initDeviceIntelligence() from the layout or form pages that matter — not from every page.
2. Risk scoring on sensitive actions
Wrap the call in your own service so the bundle stays a dependency, not a public API:
final class RiskGate
{
public function __construct(private readonly RiskScoringService $risk) {}
public function requiresChallenge(string $deviceId, string $userAgent): bool
{
$score = $this->risk->score($deviceId, $userAgent);
return $score >= $this->threshold;
}
}
Then use it in a listener, form handler, or middleware-style check. Keep the threshold in your config, not in the bundle config, so you can tune per environment.
3. Probabilistic matching
Use matching when you need to associate the current request with previously seen devices — e.g., "is this the same user agent + profile combination seen last week?". Store the returned device ID (and a match confidence, if the bundle exposes one) in your own table:
$deviceId = $matcher->match($signals); // returns null or a candidate ID
// persist the match + confidence for auditability
DeviceHistory::create([
'user_id' => $userId,
'device_id' => $deviceId,
'confidence' => $confidence,
'ip' => request()->ip(),
]);
This gives you an audit trail and lets you change thresholds later without rewriting the bundle integration.
4. Integration with authentication flows
Typical insertion points:
Don't hard-block based on a single probabilistic match — the value is in adding friction at the right moment.
5. Testing
Mock the bundle services at the boundary (the risk scoring service and the matcher). Do not test against the collect endpoint in unit tests; that endpoint depends on real client signals. Add an integration test against a fixture payload instead.
Device IDs are not credentials. The package description says this explicitly. Treat every device ID as a hint, never as proof. Never authorize a user, reset a session, or grant access based on a device ID match alone. Use it to add friction (2FA, CAPTCHA) not to remove it.
You are trusting client input. The collect endpoint receives data the client controls. Assume a hostile client:
Probabilistic matching has false positives and negatives. The same user across two browsers may not match; a new device on the same network may match. Design your flow so a "no match" is a soft signal (challenge) rather than a hard failure.
No Laravel integration exists. There is no DeviceIntelligence::class facade, no config/device-intelligence.php, no published migrations. If you are on Laravel, don't expect php artisan to help. Use a Symfony sidecar or copy the logic.
Compatibility window. The bundle targets PHP ≥ 8.1 and Symfony 6/7/8. A Laravel app that is still on PHP 8.0, or a legacy Symfony 5 app, will not install cleanly.
Config caching. In Symfony, remember bin/console cache:clear after changing the bundle's config. In a Laravel port, config is cached separately and needs php artisan config:clear.
Version pinning. Zero stars, zero dependents, no release history to speak of. Pin the version in composer.json and re-check the changelog on upgrades; a ^1.0 constraint may drift.
Debugging the collect endpoint. First confirm the route is reachable (bin/console debug:router | grep -i device). If responses are empty or 4xx, check:
Look at the bundle's own controller — it will show you what it expects.
Extension points. Prefer wrapping bundle services in your own thin services (RiskGate, DeviceHistoryWriter) rather than calling bundle services from controllers. This makes replacement easy if the bundle is abandoned and prevents your public API from depending on a zero-star dependency.
Privacy. You are collecting device and user-agent data for scoring. Check your privacy notice and retention policy before you ship. If a GDPR-style consent flow applies, gate collection on consent.
Release hygiene. The bundle's own rules mention CHANGELOG.md and UPGRADING.md. Read them before upgrading; the bundle explicitly preserves backward compatibility but is early enough that behavior may still change.
How can I help you explore Laravel packages today?