quvel / core
Core foundation package for Quvel framework - Device management, push notifications, platform detection, distributed tracing, and more for Laravel applications
Installs: 30
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/quvel/core
Requires
- php: ^8.4
- ext-curl: *
- ext-openssl: *
- illuminate/broadcasting: ^12.0
- illuminate/contracts: ^12.0
- illuminate/database: ^12.0
- illuminate/events: ^12.0
- illuminate/http: ^12.0
- illuminate/log: ^12.0
- illuminate/queue: ^12.0
- illuminate/support: ^12.0
- monolog/monolog: ^3.9
- psr/log: ^3.0
Requires (Dev)
- larastan/larastan: ^3.8
- laravel/pint: ^1.25
- orchestra/testbench: ^10.6
- phpmd/phpmd: ^2.15
- phpunit/phpunit: ^12.4
- psalm/plugin-laravel: ^3.0
- rector/rector: ^2.2
- squizlabs/php_codesniffer: ^4.0
- vimeo/psalm: ^6.13
README
A Laravel package providing essential utilities for full-stack applications including device management, push notifications, platform detection, locale handling, distributed tracing, and more.
Installation
Step 1: Add the Package
Install via composer:
composer require quvel/core
Step 2: Publish Configuration
Publish the configuration file to config/quvel.php:
php artisan vendor:publish --tag=quvel-config
This creates config/quvel.php with all available options.
Step 3: Publish and Run Migrations
Publish the migrations:
php artisan vendor:publish --tag=quvel-migrations
This publishes:
2024_01_01_000000_create_platform_settings_table- For platform-specific settings2024_01_01_000001_create_user_devices_table- For device management
Note: Migration filenames include timestamps (YYYY_MM_DD_HHMMSS format) to ensure they run in the correct order. The timestamps are part of Laravel's convention for managing database schema versions.
Run the migrations:
php artisan migrate
Step 4: Configure Environment Variables
Add the following to your .env file (optional, depending on features you use):
# Captcha (Google reCAPTCHA) CAPTCHA_ENABLED=true RECAPTCHA_SECRET_KEY=your-secret-key RECAPTCHA_SITE_KEY=your-site-key RECAPTCHA_SCORE_THRESHOLD=0.5 # Push Notifications PUSH_ENABLED=true PUSH_DRIVERS=fcm,apns,web # FCM (Firebase Cloud Messaging) FCM_SERVER_KEY=your-fcm-server-key FCM_PROJECT_ID=your-fcm-project-id # APNS (Apple Push Notification Service) APNS_KEY_PATH=/path/to/apns/key.p8 APNS_KEY_ID=your-key-id APNS_TEAM_ID=your-team-id APNS_BUNDLE_ID=com.yourapp.bundle APNS_ENVIRONMENT=sandbox # or 'production' # Web Push (VAPID) VAPID_SUBJECT=mailto:your@email.com VAPID_PUBLIC_KEY=your-public-key VAPID_PRIVATE_KEY=your-private-key # Device Management DEVICES_ENABLED=true DEVICES_ALLOW_ANONYMOUS=false DEVICES_CLEANUP_DAYS=90 DEVICES_MAX_PER_USER=10 # Frontend/Redirects FRONTEND_URL=http://localhost:3000 FRONTEND_REDIRECT_MODE=universal_links # or 'custom_scheme', 'landing_page', 'web_only' FRONTEND_CUSTOM_SCHEME=myapp FRONTEND_LANDING_PAGE_TIMEOUT=5 # Security SECURITY_TRUSTED_IPS=127.0.0.1,::1 SECURITY_API_KEY=your-internal-api-key SECURITY_DISABLE_IP_CHECK=false SECURITY_DISABLE_KEY_CHECK=false # Tracing TRACING_ENABLED=true TRACING_ACCEPT_EXTERNAL=true # Locale LOCALE_ALLOWED=en,es,fr LOCALE_FALLBACK=en LOCALE_NORMALIZE=true # Platform Detection HEADER_PLATFORM=X-Platform HEADER_TRACE_ID=X-Trace-ID # Public IDs PUBLIC_ID_DRIVER=ulid # or 'uuid' PUBLIC_ID_COLUMN=public_id
Step 5: Verify Installation
Check that the package is discovered:
php artisan package:discover
You should see quvel/core in the list.
Check available commands:
php artisan list
Optional: Publish Additional Assets
# Publish API routes for device management (optional - only if you want to customize them) php artisan vendor:publish --tag=quvel-routes # Publish language files php artisan vendor:publish --tag=quvel-lang # Publish views (landing pages, etc.) php artisan vendor:publish --tag=quvel-views
Important: Routes are disabled by default. To enable device management routes, add to your .env:
# Enable device management API routes QUVEL_DEVICE_ROUTES_ENABLED=true # Enable platform settings API routes QUVEL_PLATFORM_SETTINGS_ROUTES_ENABLED=true
You do not need to publish the routes file unless you want to customize the route definitions. The routes will work from the package once enabled via environment variables.
Troubleshooting
Package not discovered:
- Clear bootstrap cache:
rm bootstrap/cache/*.php - Run:
php artisan package:discover - Check
composer.jsonfor correct repository path
Migrations already exist:
- Migrations are automatically loaded from the package
- Only publish if you need to customize them
Config changes not taking effect:
- Run:
php artisan config:clear - Check that
.envvalues are correct
Table of Contents
- Captcha
- Device Management
- Push Notifications
- Platform Detection
- Locale Management
- Distributed Tracing
- Public IDs
- Redirects
- Security
- Middleware
- Publishing Assets
Captcha
Protect your endpoints from bots and automated attacks using Google reCAPTCHA.
Configuration
// config/quvel.php 'captcha' => [ 'enabled' => env('CAPTCHA_ENABLED', true), 'driver' => env('CAPTCHA_DRIVER', \Quvel\Core\Captcha\GoogleRecaptchaDriver::class), 'score_threshold' => env('RECAPTCHA_SCORE_THRESHOLD', 0.5), 'timeout' => env('CAPTCHA_TIMEOUT', 30), ],
CAPTCHA_ENABLED=true RECAPTCHA_SECRET_KEY=your-secret-key RECAPTCHA_SITE_KEY=your-site-key
Usage
Verify captcha:
use Quvel\Core\Facades\Captcha; $result = Captcha::verify($token, $request->ip()); if ($result->isSuccessful()) { // Continue with request } // Check reCAPTCHA v3 score if ($result->hasScore() && $result->score >= 0.5) { // High confidence user }
Protect routes with middleware:
Route::post('/register', function () { // Protected by captcha })->middleware('captcha'); // Custom input field Route::post('/login', function () { // ... })->middleware('captcha:recaptcha_response');
Custom Captcha Driver
use Quvel\Core\Contracts\CaptchaDriverInterface; use Quvel\Core\Captcha\CaptchaVerificationResult; class HCaptchaDriver implements CaptchaDriverInterface { public function verify(string $token, ?string $ip = null, ?string $action = null): CaptchaVerificationResult { // Your verification logic return CaptchaVerificationResult::success(); } public function supportsScoring(): bool { return false; } public function getDefaultScoreThreshold(): ?float { return null; } }
Set in config:
'captcha' => [ 'driver' => \App\Captcha\HCaptchaDriver::class, ],
Events
use Quvel\Core\Events\CaptchaVerifySuccess; use Quvel\Core\Events\CaptchaVerifyFailed; Event::listen(CaptchaVerifyFailed::class, function ($event) { Log::warning('Captcha failed', [ 'ip' => $event->ipAddress, 'reason' => $event->reason, ]); });
Device Management
Track user devices across web, mobile, and desktop platforms. Register devices, manage push tokens, and maintain device lifecycle.
Configuration
// config/quvel.php 'devices' => [ 'enabled' => env('DEVICES_ENABLED', true), 'allow_anonymous' => env('DEVICES_ALLOW_ANONYMOUS', false), 'cleanup_inactive_after_days' => env('DEVICES_CLEANUP_DAYS', 90), 'max_devices_per_user' => env('DEVICES_MAX_PER_USER', 10), ],
Usage
Register a device:
use Quvel\Core\Facades\Device; $device = Device::registerDevice([ 'device_id' => 'device-123', 'platform' => 'ios', 'device_name' => 'John's iPhone', 'push_token' => 'fcm-token', 'push_provider' => 'fcm', ]);
Manage devices:
// Update push token DeviceManager::updatePushToken('device-123', 'new-token', 'fcm'); // Deactivate device DeviceManager::deactivateDevice('device-123', 'User logged out'); // Get user's devices $devices = DeviceManager::getUserDevices(auth()->id());
Device Model
use Quvel\Core\Models\UserDevice; // Get active devices for a user $devices = UserDevice::forUser($userId)->active()->get(); // Find by device ID $device = UserDevice::where('device_id', 'device-123')->first(); // Check if device has valid push token if ($device->hasValidPushToken()) { // Send notification } // Platform filtering $iosDevices = UserDevice::forPlatform('ios')->get();
API Routes
Publish and enable device routes:
php artisan vendor:publish --tag=quvel-routes
// config/quvel.php 'routes' => [ 'devices' => [ 'enabled' => env('QUVEL_DEVICE_ROUTES_ENABLED', false), 'prefix' => 'api/devices', 'name' => 'devices.', 'middleware' => ['api', 'auth:sanctum'], ], ],
Available endpoints:
POST /api/devices/register- Register a devicePOST /api/devices/push-token- Update push tokenPOST /api/devices/deactivate- Deactivate deviceGET /api/devices/list- List user's devices
Middleware
Automatically detect and track devices:
$device = $request->attributes->get('device'); $deviceId = $request->attributes->get('device_id');
Events
use Quvel\Core\Events\DeviceRegistered; use Quvel\Core\Events\DeviceRemoved; Event::listen(DeviceRegistered::class, function ($event) { Log::info('Device registered', [ 'device_id' => $event->deviceId, 'platform' => $event->platform, ]); });
Push Notifications
Send push notifications to user devices across multiple platforms (FCM, APNS, Web Push). Supports device targeting and batch processing.
Configuration
// config/quvel.php 'push' => [ 'enabled' => env('PUSH_ENABLED', true), 'drivers' => explode(',', env('PUSH_DRIVERS', 'fcm,apns,web')), 'fcm' => [ 'server_key' => env('FCM_SERVER_KEY'), 'project_id' => env('FCM_PROJECT_ID'), ], 'apns' => [ 'key_path' => env('APNS_KEY_PATH'), 'key_id' => env('APNS_KEY_ID'), 'team_id' => env('APNS_TEAM_ID'), 'bundle_id' => env('APNS_BUNDLE_ID'), 'environment' => env('APNS_ENVIRONMENT', 'sandbox'), ], 'web_push' => [ 'vapid_subject' => env('VAPID_SUBJECT'), 'vapid_public_key' => env('VAPID_PUBLIC_KEY'), 'vapid_private_key' => env('VAPID_PRIVATE_KEY'), ], 'batch_size' => env('PUSH_BATCH_SIZE', 1000), ], 'targeting' => [ 'default_scope' => env('TARGETING_DEFAULT_SCOPE', 'requesting_device'), ],
Usage
Send to device:
use Quvel\Core\Facades\PushNotification; $success = PushNotification::sendToDevice( device: $device, title: 'New Message', body: 'You have a new message', data: ['message_id' => 123] );
Send to multiple devices:
$results = PushNotification::sendToDevices( devices: $devices, title: 'Update Available', body: 'A new version is available' ); // Returns: ['device-123' => true, 'device-456' => false, ...]
Device Targeting
Get target devices:
use Quvel\Core\Facades\Targeting; // Get devices for targeting scope $devices = Targeting::getTargetDevices( requestingDevice: $device, userId: auth()->id(), scope: 'all_user_devices' // or 'requesting_device' ); // Then send to those devices PushNotification::sendToDevices($devices, $title, $body);
Targeting scopes:
requesting_device- Only the device making the requestall_user_devices- All of the user's active devices
Custom Push Driver
use Quvel\Core\Contracts\PushDriver; use Quvel\Core\Models\UserDevice; class CustomPushDriver implements PushDriver { public function getName(): string { return 'custom'; } public function supports(string $platform): bool { return $platform === 'my-platform'; } public function isConfigured(): bool { return !empty(config('services.custom_push.api_key')); } public function send(UserDevice $device, string $title, string $body, array $data = []): bool { // Your sending logic return true; } }
Register in service provider:
app(PushManager::class)->extend('custom', function () { return new CustomPushDriver(); });
Events
use Quvel\Core\Events\PushNotificationSent; use Quvel\Core\Events\PushNotificationFailed; Event::listen(PushNotificationSent::class, function ($event) { Log::info('Push sent', [ 'devices' => $event->deviceIds, 'title' => $event->title, ]); });
Platform Detection
Detect the platform (web, mobile, desktop) from which requests originate.
Configuration
// config/quvel.php 'headers' => [ 'platform' => env('HEADER_PLATFORM'), // Defaults to 'X-Platform' ],
Usage
use Quvel\Core\Facades\PlatformDetector; $platform = Platform::getPlatform(); // 'web', 'mobile', or 'desktop' if (Platform::isPlatform('mobile')) { // Mobile-specific logic }
Platform tags:
use Quvel\Core\Platform\PlatformTag; PlatformTag::IOS->value; // 'ios' PlatformTag::ANDROID->value; // 'android' PlatformTag::ELECTRON->value; // 'electron' PlatformTag::TABLET->value; // 'tablet' PlatformTag::SCREEN_LG->value; // 'screen:lg' $tag = PlatformTag::tryFrom('ios'); $mode = $tag->getMainMode(); // 'mobile' $category = $tag->getCategory(); // 'os'
Available platform tags:
- Runtime:
web,capacitor,cordova,electron,tauri - OS:
ios,android,macos,windows,linux - Form Factor:
mobile,tablet,desktop - Screen Sizes:
screen:xs,screen:sm,screen:md,screen:lg,screen:xl
Frontend Integration
// Multi-tag platform detection (comma-separated) // iPhone in Capacitor fetch('/api/endpoint', { headers: { 'X-Platform': 'capacitor,ios,mobile,screen:sm' } }); // iPad in Safari fetch('/api/endpoint', { headers: { 'X-Platform': 'web,ios,tablet,screen:lg' } }); // Electron on macOS fetch('/api/endpoint', { headers: { 'X-Platform': 'electron,macos,desktop,screen:xl' } });
Locale Management
Automatic locale detection and management from request headers.
Configuration
// config/quvel.php 'locale' => [ 'allowed_locales' => explode(',', env('LOCALE_ALLOWED', 'en')), 'fallback_locale' => env('LOCALE_FALLBACK', 'en'), 'normalize_locales' => env('LOCALE_NORMALIZE', true), // en-US -> en ],
Usage
use Quvel\Core\Facades\Locale; // Detect locale from request $locale = $request->header('Accept-Language'); // Middleware automatically detects and sets locale // Access via Laravel's app()->getLocale()
Locale is detected from:
Accept-Languageheader- Falls back to configured default
Distributed Tracing
Track requests across your distributed system with automatic trace ID generation and propagation.
Configuration
// config/quvel.php 'tracing' => [ 'enabled' => env('TRACING_ENABLED', true), 'accept_external_trace_ids' => env('TRACING_ACCEPT_EXTERNAL', true), ], 'headers' => [ 'trace_id' => env('HEADER_TRACE_ID'), // Defaults to 'X-Trace-ID' ],
Usage
use Quvel\Core\Facades\Trace; // Middleware automatically generates trace IDs // Access from Laravel's Context use Illuminate\Support\Facades\Context; $traceId = Context::get('trace_id');
Frontend Integration
const traceId = generateUUID(); fetch('/api/endpoint', { headers: { 'X-Trace-ID': traceId } });
Public IDs
Generate user-facing IDs (ULIDs or UUIDs) for models instead of exposing database IDs.
Configuration
// config/quvel.php 'public_id' => [ 'driver' => env('PUBLIC_ID_DRIVER', 'ulid'), // 'ulid' or 'uuid' 'column' => env('PUBLIC_ID_COLUMN', 'public_id'), ],
Usage
Add trait to your model:
use Quvel\Core\Concerns\HasPublicId; class Order extends Model { use HasPublicId; }
Auto-generates public ID on creation:
$order = Order::create([...]); echo $order->public_id; // '01HQ...' (ULID) or 'uuid-here' // Find by public ID $order = Order::wherePublicId('01HQ...')->first();
Route Model Binding
Route::get('/orders/{order:public_id}', function (Order $order) { return $order; });
Redirects
Smart redirects that work across web, mobile, and desktop platforms using universal links, custom schemes, or landing pages.
Configuration
// config/quvel.php 'frontend' => [ 'url' => env('FRONTEND_URL', 'http://localhost:3000'), 'custom_scheme' => env('FRONTEND_CUSTOM_SCHEME'), 'redirect_mode' => env('FRONTEND_REDIRECT_MODE', 'universal_links'), // Options: 'universal_links', 'custom_scheme', 'landing_page', 'web_only' 'landing_page_timeout' => env('FRONTEND_LANDING_PAGE_TIMEOUT', 5), 'allowed_redirect_domains' => explode(',', env('FRONTEND_ALLOWED_DOMAINS', '')), ],
Usage
use Quvel\Core\Facades\Redirect; // Redirect to frontend path return Redirect::redirect('/dashboard'); // With query params return Redirect::redirect('/orders/123', ['status' => 'new']); // With message return Redirect::redirectWithMessage('/login', 'Please sign in'); // Get URL without redirecting $url = Redirect::getUrl('/profile');
Redirect Modes
Universal Links (recommended): Uses HTTPS URLs that open the app if installed, otherwise open in browser.
FRONTEND_REDIRECT_MODE=universal_links FRONTEND_URL=https://app.example.com
Custom Scheme: Uses custom URL scheme (myapp://).
FRONTEND_REDIRECT_MODE=custom_scheme FRONTEND_CUSTOM_SCHEME=myapp
Landing Page: Shows countdown page before redirect.
FRONTEND_REDIRECT_MODE=landing_page FRONTEND_LANDING_PAGE_TIMEOUT=5
Web Only: Always redirects to web URL.
FRONTEND_REDIRECT_MODE=web_only
Security
Internal Request Validation
Protect internal endpoints from external access:
// config/quvel.php 'security' => [ 'internal_requests' => [ 'trusted_ips' => explode(',', env('SECURITY_TRUSTED_IPS', '127.0.0.1,::1')), 'api_key' => env('SECURITY_API_KEY'), 'disable_ip_check' => env('SECURITY_DISABLE_IP_CHECK', false), 'disable_key_check' => env('SECURITY_DISABLE_KEY_CHECK', false), ], ],
Protect routes:
Route::middleware('internal-only')->group(function () { Route::get('/internal/stats', [StatsController::class, 'index']); });
Frontend SSR integration:
// In your SSR server fetch('http://api.internal/endpoint', { headers: { 'X-SSR-Key': process.env.SECURITY_API_KEY } });
Middleware
Available Middleware
'captcha' // Verify captcha token 'config-gate' // Gate access based on config 'device-detection' // Detect and track devices 'internal-only' // Restrict to internal requests 'locale' // Auto-detect and set locale 'platform-detection' // Detect platform (web/mobile/desktop) 'trace' // Generate/propagate trace IDs
Global Configuration
// config/quvel.php 'middleware' => [ 'aliases' => [ 'captcha' => \Quvel\Core\Http\Middleware\VerifyCaptcha::class, 'config-gate' => \Quvel\Core\Http\Middleware\ConfigGate::class, 'device-detection' => \Quvel\Core\Http\Middleware\DeviceDetection::class, 'internal-only' => \Quvel\Core\Http\Middleware\InternalOnly::class, 'locale' => \Quvel\Core\Http\Middleware\LocaleMiddleware::class, 'platform-detection' => \Quvel\Core\Http\Middleware\PlatformDetection::class, 'trace' => \Quvel\Core\Http\Middleware\TraceMiddleware::class, ], 'groups' => [ 'web' => [ 'platform-detection', 'device-detection', 'locale', 'trace', ], 'api' => [ 'platform-detection', 'device-detection', 'locale', 'trace', ], ], ],
Extending the Package
Custom Implementations
All core services use contracts and can be extended or replaced:
use Quvel\Core\Device\Device as BaseManager; class CustomDeviceManager extends BaseManager { public function registerDevice(array $deviceData): UserDevice { $device = parent::registerDevice($deviceData); // Add custom logic (webhooks, etc.) return $device; } } // Bind in service provider $this->app->bind( \Quvel\Core\Contracts\Device::class, \App\Services\CustomDeviceManager::class );
Available Contracts
CaptchaVerifierCaptchaDriverInterfaceDeviceManagerLocaleResolverPlatformDetectorPublicIdGeneratorPushManagerPushDriverAppRedirectorTraceIdGenerator
Events
Device Events
DeviceRegistered- Device registeredDeviceRemoved- Device deactivated
Push Notification Events
PushNotificationSent- Notification sent successfullyPushNotificationFailed- Notification failed
Captcha Events
CaptchaVerifySuccess- Captcha verification succeededCaptchaVerifyFailed- Captcha verification failed
Trace Events
PublicTraceAccepted- External trace ID accepted
Publishing Assets
# Publish everything php artisan vendor:publish --provider="Quvel\Core\Providers\CoreServiceProvider" # Publish specific assets php artisan vendor:publish --tag=quvel-config php artisan vendor:publish --tag=quvel-migrations php artisan vendor:publish --tag=quvel-routes php artisan vendor:publish --tag=quvel-lang php artisan vendor:publish --tag=quvel-views
License
MIT