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

v1.1.0 2025-11-08 09:08 UTC

This package is auto-updated.

Last update: 2025-11-09 09:34:13 UTC


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 settings
  • 2024_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.json for 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 .env values are correct

Table of Contents

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 device
  • POST /api/devices/push-token - Update push token
  • POST /api/devices/deactivate - Deactivate device
  • GET /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 request
  • all_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:

  1. Accept-Language header
  2. 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

  • CaptchaVerifier
  • CaptchaDriverInterface
  • DeviceManager
  • LocaleResolver
  • PlatformDetector
  • PublicIdGenerator
  • PushManager
  • PushDriver
  • AppRedirector
  • TraceIdGenerator

Events

Device Events

  • DeviceRegistered - Device registered
  • DeviceRemoved - Device deactivated

Push Notification Events

  • PushNotificationSent - Notification sent successfully
  • PushNotificationFailed - Notification failed

Captcha Events

  • CaptchaVerifySuccess - Captcha verification succeeded
  • CaptchaVerifyFailed - 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