diff --git a/README.md b/README.md index 4abe2b2..b5ada8d 100644 --- a/README.md +++ b/README.md @@ -359,6 +359,24 @@ The `short_url_daily_stats` table stores pre-aggregated daily summaries per shor The `getCachedStats()` model method **automatically merges** data from both tables: historical days come from `short_url_daily_stats`, while today's data comes directly from `short_url_visits` — completely transparent to the dashboard. +### Queue Worker vs. Synchronous Execution + +The plugin is designed to be highly reliable and performant regardless of whether your hosting environment runs a background queue worker. + +#### 1. With a Queue Worker (Recommended) +If your application runs a queue worker (e.g. via supervisor running `php artisan queue:work`), you should configure the **Queue Connection** in the **Settings** panel (or via `SHORT_URL_QUEUE` env variable) to your primary queue driver (like `redis` or `database`). +- **Performance**: High-speed redirects are prioritized. Visitor clicks, Geo-IP lookups, GA4 hits, and Webhook dispatches are immediately deferred to background queue jobs (`TrackShortUrlVisitJob` and `SendWebhookJob`), keeping redirect response times under 20ms. +- **Worker Configuration**: Ensure your queue listener is running: + ```bash + php artisan queue:work --queue=default + ``` + *(If you customized the queue name in Settings, replace `default` with your custom queue name).* + +#### 2. Without a Queue Worker (Sync Driver Fallback) +If you do not run background queue workers, set the **Queue Connection** to `sync`. +- **Automatic Fallback**: The plugin dynamically executes all jobs synchronously on the request thread. The visitor is redirected as soon as the synchronous processes (log recording, webhook calls, etc.) complete. +- **Fault Tolerance (Safety Nets)**: Webhook failures (timeouts, 500 errors) or database tracking glitches can happen. The plugin wraps all synchronous execution points in strict error boundaries (`try/catch`). **A failed tracking job or a slow/offline webhook will NEVER crash the visitor's redirect or programmatic REST API responses.** They are gracefully logged in the background. + ### Queue-Based Counter Fallback When Redis is not available and counter buffering is enabled, the `IncrementVisitJob` is dispatched to the configured queue connection. This guarantees visit counts are not lost during cache evictions or restarts. @@ -367,7 +385,12 @@ When Redis is not available and counter buffering is enabled, the `IncrementVisi ## Social Retargeting Pixels (new in v1.5.0) -The **Marketing & API** tab in the short URL form lets you attach client-side tracking pixels to any link. When a visitor clicks a link that has pixels configured, instead of an instant 302 redirect the plugin serves a lightweight HTML interstitial page (styled with glassmorphism) for ~250 ms. During that time the browser executes the pixel scripts — capturing cookies and building ad audiences — then the visitor is forwarded to the destination URL. +The **Marketing & API** tab in the short URL form lets you attach client-side tracking pixels to any link. When a visitor clicks a link that has pixels configured, instead of an instant 302 redirect the plugin serves a lightweight, premium HTML interstitial page. + +### The Interstitial Experience +- **Premium Design**: Built using the exact same modern glassmorphic look as the password protection and warning interstitial pages. Supports dark mode automatically. +- **Brand Customization**: Automatically renders the application logo and the **Site Name Override** (configured globally in Settings, falling back to `config('app.name')`) to ensure the transition page looks professional and trust-instilling. +- **Micro-Animations & Smooth Redirect**: Displays a sleek animated loading spinner and a progress bar that smoothly fills from 0% to 100% in ~220-250ms. This short delay ensures browser execution time for the tracking scripts before performing a seamless `window.location.replace()` to the destination URL. This unlocks remarketing to people who clicked your links **even when redirecting to external domains** (e.g. booking.com, amazon.com) where you cannot install your own tracking code. @@ -539,9 +562,14 @@ Webhooks allow external systems to receive real-time HTTP POST notifications whe Webhooks can be configured at two levels: -**1. Per-link webhook** — Set a `Dedicated Webhook URL` in the **Marketing & API** tab of a specific short URL. Fires for every click on that link. +**1. Per-link Webhook (Dedicated Webhook URL)**: +* **Where to configure**: Set the `Dedicated Webhook URL` in the **Marketing & API** tab of a specific short URL (or pass it via the `webhook_url` parameter in the REST API). +* **How it works**: When a visitor clicks the short URL, or when the link is programmatically created via the REST API, a webhook notification is sent directly to this URL. +* **Bypassing Global Filters**: Dedicated webhooks **always fire** for these events and are completely independent of the global *Monitored Webhook Events* settings. This is useful for third-party landing pages or integrations that need to track a specific link's traffic without routing all package events. -**2. Global webhook** — Set a **Global Webhook URL** in **Settings → API & Webhooks → Global Webhook Configuration**. Fires for all links that don't have their own webhook URL, for the event types you select. +**2. Global Webhook**: +* **Where to configure**: Set a **Global Webhook URL** in **Settings → API & Webhooks → Global Webhook Configuration**. +* **How it works**: Fires for all links that *do not* have their own Dedicated Webhook URL configured. It will only fire for the specific event types selected in the **Monitored Webhook Events** setting. ### Monitored Events diff --git a/config/filament-short-url.php b/config/filament-short-url.php index 3be1103..3997e85 100644 --- a/config/filament-short-url.php +++ b/config/filament-short-url.php @@ -14,6 +14,16 @@ return [ */ 'route_prefix' => env('SHORT_URL_PREFIX', 's'), + /* + |-------------------------------------------------------------------------- + | Site Name Override + |-------------------------------------------------------------------------- + | The brand or site name displayed on the password prompt, redirect warnings, + | and pixel loading screens. Falls back to config('app.name') if empty. + | + */ + 'site_name' => env('SHORT_URL_SITE_NAME'), + /* |-------------------------------------------------------------------------- | Default Redirect Status Code diff --git a/resources/lang/en/default.php b/resources/lang/en/default.php index 01ddd1d..87e11ea 100644 --- a/resources/lang/en/default.php +++ b/resources/lang/en/default.php @@ -167,6 +167,8 @@ return [ 'settings_section_ga4' => 'GA4 Measurement Protocol', 'settings_ga4_description' => 'Configure server-side Google Analytics 4 event tracking via the Measurement Protocol. Events are sent in the background without blocking the redirect.', + 'settings_site_name' => 'Site Name Override', + 'settings_site_name_helper' => 'Custom brand/site name displayed on redirect prompt screens. If empty, falls back to config("app.name").', 'settings_route_prefix' => 'Route Prefix', 'settings_route_prefix_helper' => 'URL segment before the short key, e.g. "/s/abc123". Change requires a config:clear.', 'settings_key_length' => 'Auto-generated Key Length', @@ -338,6 +340,8 @@ return [ 'warning_description' => 'You are leaving this secure portal and being redirected to an external target link. Please ensure you trust the address below:', 'warning_btn_continue' => 'Continue to Destination', 'warning_btn_back' => 'Go Back', + 'pixel_loading_title' => 'Connecting...', + 'pixel_loading_description' => 'Preparing your connection and forwarding you now.', // Marketing & API Tab 'tab_marketing' => 'Marketing & API', diff --git a/resources/lang/pl/default.php b/resources/lang/pl/default.php index ddb1c76..8580b62 100644 --- a/resources/lang/pl/default.php +++ b/resources/lang/pl/default.php @@ -168,6 +168,8 @@ return [ 'settings_section_ga4' => 'GA4 Measurement Protocol', 'settings_ga4_description' => 'Skonfiguruj serwerowe śledzenie zdarzeń GA4 przez Measurement Protocol. Zdarzenia są wysyłane w tle bez blokowania przekierowania.', + 'settings_site_name' => 'Niestandardowa nazwa witryny', + 'settings_site_name_helper' => 'Własna nazwa witryny/marki wyświetlana na ekranach przekierowań (monit o hasło, ostrzeżenia, piksele). W przypadku braku wartości, zostanie użyta nazwa z konfiguracji config("app.name").', 'settings_route_prefix' => 'Prefiks trasy', 'settings_route_prefix_helper' => 'Segment URL przed kluczem, np. "/s/abc123". Zmiana wymaga php artisan config:clear.', 'settings_key_length' => 'Długość auto-generowanego klucza', @@ -339,6 +341,8 @@ return [ 'warning_description' => 'Opuszczasz ten bezpieczny portal i zostajesz przekierowany na zewnętrzny link docelowy. Upewnij się, że ufasz poniższemu adresowi:', 'warning_btn_continue' => 'Kontynuuj do celu', 'warning_btn_back' => 'Wróć', + 'pixel_loading_title' => 'Łączenie...', + 'pixel_loading_description' => 'Przygotowujemy połączenie i za chwilę zostaniesz przekierowany.', // Marketing & API Tab 'tab_marketing' => 'Marketing i API', diff --git a/resources/views/password-prompt.blade.php b/resources/views/password-prompt.blade.php index 650e8ae..02cd39b 100644 --- a/resources/views/password-prompt.blade.php +++ b/resources/views/password-prompt.blade.php @@ -37,7 +37,7 @@ @php $logoPath = function_exists('setting') ? setting('logo_path') : null; $logoUrl = $logoPath ? \Illuminate\Support\Facades\Storage::disk('public')->url($logoPath) : null; - $siteName = config('app.name', 'Laravel'); + $siteName = config('filament-short-url.site_name') ?: config('app.name', 'Laravel'); @endphp {{-- Main Sign-in Box --}} diff --git a/resources/views/pixel-loading.blade.php b/resources/views/pixel-loading.blade.php index 7d8561f..372cac1 100644 --- a/resources/views/pixel-loading.blade.php +++ b/resources/views/pixel-loading.blade.php @@ -1,14 +1,37 @@ - + - Connecting... - - + {{ __('filament-short-url::default.pixel_loading_title') ?? 'Connecting...' }} + + - + + + + + + @if(!empty($pixelMetaId)) @@ -55,103 +78,56 @@ @endif - - - - -
-
-
-
-
+ + @php + $logoPath = function_exists('setting') ? setting('logo_path') : null; + $logoUrl = $logoPath ? \Illuminate\Support\Facades\Storage::disk('public')->url($logoPath) : null; + $siteName = config('filament-short-url.site_name') ?: config('app.name', 'Laravel'); + @endphp + + {{-- Main card --}} +
+
+ @if ($logoUrl) + {{ $siteName }} + @else + {{ $siteName }} + @endif + + {{-- Animated spinner --}} + + +

+ {{ __('filament-short-url::default.pixel_loading_title') ?? 'Connecting...' }} +

+

+ {{ __('filament-short-url::default.pixel_loading_description') ?? 'Preparing your connection and forwarding you now.' }} +

+
+ + {{-- Progress bar --}} +
+
-

Connecting Safely

-

Securing connection & forwarding you now...

- + {{-- Footer --}} +
+ © {{ date('Y') }} {{ $siteName }} Inc. All rights reserved. +
+ + {{-- Redirect after pixels fire --}}