Zum Hauptinhalt springen

Broadcasting (Reverb)

Postbox nutzt Laravel Reverb als First-Party WebSocket-Server fuer Echtzeit-Kommunikation. Events werden server-seitig dispatcht und im Browser ueber Laravel Echo empfangen.

Architektur

Browser (wss://app.postbox.so:443/app)
└─> Nginx (Reverse Proxy)
└─> Reverb (127.0.0.1:8081)

Laravel (Server-seitig)
└─> http://127.0.0.1:8081 (direkt, ohne Cloudflare)

Server-seitiges Broadcasting verbindet direkt zu Reverb auf localhost, um Cloudflare und andere Proxies zu umgehen.

.env Konfiguration

Server-seitig (Laravel zu Reverb)

BROADCAST_CONNECTION=reverb

REVERB_APP_ID=postbox
REVERB_APP_KEY=your-random-key
REVERB_APP_SECRET=your-random-secret
REVERB_SERVER_HOST=127.0.0.1
REVERB_SERVER_PORT=8081

Client-seitig (Browser zu Nginx zu Reverb)

REVERB_HOST=app.postbox.so
REVERB_PORT=443
REVERB_SCHEME=https

Frontend (Vite)

VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

Nginx Konfiguration

Das /app-Prefix muss auf den Reverb-Server proxied werden:

location /app {
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header Scheme $scheme;
proxy_set_header SERVER_PORT $server_port;
proxy_set_header REMOTE_ADDR $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_pass http://127.0.0.1:8081;
}

Forge Daemon

Reverb muss als Daemon-Prozess laufen. In Laravel Forge unter Server - Daemons:

FeldWert
Commandphp8.4 artisan reverb:start --host=0.0.0.0 --port=8081
Directory/home/forge/app.postbox.so/current
Userforge
Processes1

Broadcast Channels

Location: routes/channels.php

ChannelTypAutorisierungEvents
user.{userId}Private(int) $user->id === (int) $userIdYouTube Sync, Daily Scrape, Profile Sanitized/Unsanitized/Reactivated
workspace.{workspaceId}Private$user->workspaces()->where('workspaces.id', $workspaceId)->exists()Import Progress
profile.{profileId}PrivateUser hat Watcher mit diesem Profil (ueber WatcherSource + Workspace)Related Profiles Calculated
adminPrivate$user->isAdmin()User Registered, Server Alerts, Server Metrics
test-channelPublicKeineTest Broadcast (Admin-Seite)

Channel Authorization Code

// routes/channels.php
Broadcast::channel('user.{userId}', fn($user, $userId) =>
(int) $user->id === (int) $userId
);

Broadcast::channel('workspace.{workspaceId}', fn($user, $workspaceId) =>
$user->workspaces()->where('workspaces.id', $workspaceId)->exists()
);

Broadcast::channel('profile.{profileId}', fn($user, $profileId) =>
WatcherSource::query()
->where('social_profile_id', $profileId)
->whereHas('watcher', fn($q) => $q
->whereIn('workspace_id', $user->workspaces->pluck('id')))
->exists()
);

Broadcast::channel('admin', fn($user) => $user->isAdmin());

Events

Location: app/Events/

Event-Uebersicht

EventChannel(s)broadcastAsInterfaceBeschreibung
WatcherImportProgressworkspace.{id}import.progressShouldBroadcastImport-Fortschritt pro URL (status, counters)
YouTubeVideoSyncCompleteduser.{userId}youtube.sync.completedShouldBroadcastNowYouTube Video-Stats Sync fertig
DailyScrapeCompleteduser.{userId}daily.scrape.completedShouldBroadcastTaegliche Aktualisierung abgeschlossen
RelatedProfilesCalculatedprofile.{id} + user.{id}related.profiles.calculatedShouldBroadcastRelated-Berechnung abgeschlossen
ProfileReactivateduser.{userId} (alle betroffenen)profile.reactivatedShouldBroadcastDeaktiviertes Profil reaktiviert
ProfileSanitizeduser.{userId} (alle betroffenen)profile.sanitizedShouldBroadcastProfil durch Sanitizer deaktiviert
ProfileUnsanitizeduser.{userId} (alle betroffenen)profile.unsanitizedShouldBroadcastSanitized Profil wieder aktiviert
UserRegisteredadminuser.registeredShouldBroadcastNeuer User registriert
ServerAlertTriggeredadminserver.alert.triggeredShouldBroadcastNowServer-Metrik ueber Schwellwert
ServerMetricsUpdatedadminserver.metrics.updatedShouldBroadcastNowServer-Metriken Update (~15s)
TestBroadcasttest-channeltoast.showShouldBroadcastNowTest-Event fuer Reverb-Konnektivitaet

SuppressesBroadcastFailures Pattern

Broadcasts sind "nice to have" -- wenn Reverb temporaer nicht erreichbar ist, soll die App weiter funktionieren. Alle ShouldBroadcast-Events (ausser TestBroadcast und ShouldBroadcastNow-Events) nutzen das Trait SuppressesBroadcastFailures.

Location: app/Events/Concerns/SuppressesBroadcastFailures.php, app/Queue/Middleware/SuppressBroadcastFailures.php

Funktionsweise

// app/Events/Concerns/SuppressesBroadcastFailures.php
trait SuppressesBroadcastFailures
{
public function middleware(): array
{
return [
new \App\Queue\Middleware\SuppressBroadcastFailures,
];
}
}

// app/Queue/Middleware/SuppressBroadcastFailures.php
class SuppressBroadcastFailures
{
public function handle(object $job, Closure $next): void
{
try {
$next($job);
} catch (BroadcastException $e) {
Log::warning('Broadcast failed (Reverb unavailable)', [
'job' => get_class($job),
'message' => $e->getMessage(),
]);
}
}
}

Verwendung in Events

class WatcherImportProgress implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels, SuppressesBroadcastFailures;
// ...
}

Bei Reverb-Ausfall

  • Queue-Jobs failen nicht mehr wegen BroadcastException
  • Stattdessen wird eine Warning geloggt
  • Die eigentliche Funktionalitaet (Job) laeuft weiter
  • Nur die Echtzeit-Benachrichtigung entfaellt
  • Zusaetzlich: BroadcastException in dontReport (Backup in bootstrap/app.php)

NotificationCenter Integration

Location: app/Livewire/NotificationCenter.php, resources/views/livewire/notification-center.blade.php

Event-Flow

1. Laravel Event wird dispatched
└─> Reverb broadcastet an Channel

2. Laravel Echo empfaengt Event im Browser
└─> Ruft $wire.dispatch('reverb-*') auf

3. Livewire NotificationCenter Handler verarbeitet Event
└─> Persistenz in Nachrichtenzentrale (falls konfiguriert)

4. Handler dispatcht 'show-toast' Event
└─> JavaScript zeigt window.Flux.toast() an

5. 'notification-received' Event wird dispatcht
└─> NotificationBell aktualisiert Unread Count

Abonnierte Channels pro User

user.{userId}           // YouTube Sync, Daily Scrape, Profile Events
workspace.{id} // Import Progress (fuer jeden Workspace des Users)
admin // User Registrations, Server Alerts (nur Admins)
profile.{id} // Related Profiles (dynamisch)

Rate-Limiting

Der NotificationCenter verwendet safeServerDispatch() mit maximal 40 Calls pro Burst, um den Server bei vielen gleichzeitigen Events nicht zu ueberlasten.

Toast-Typen

TypHeadingVerwendung
successErfolgErfolgreiche Aktionen (Speichern, Import fertig)
warningWarnungTeilweiser Erfolg, Warnungen
danger / errorFehlerFehlgeschlagene Aktionen
infoInfoInformationsmeldungen (Default)

Toast in Livewire dispatchen

// Erfolg (gruen)
$this->dispatch('show-toast', ['message' => 'Gespeichert', 'type' => 'success']);

// Fehler (rot)
$this->dispatch('show-toast', ['message' => 'Fehler aufgetreten', 'type' => 'error']);

// Info (blau)
$this->dispatch('show-toast', ['message' => 'Wird verarbeitet...', 'type' => 'info']);

// Warnung (gelb)
$this->dispatch('show-toast', ['message' => 'Limit erreicht', 'type' => 'warning']);

Manuell testen

Admin-Seite

Die Route /admin/reverb-test bietet Verbindungsstatus, Test-Broadcast und Event-Log.

CLI

# Test Broadcast (oeffentlicher Channel)
php artisan tinker --execute='App\Events\TestBroadcast::dispatch("Hello!", "success");'

# Import Progress (Workspace 1)
php artisan tinker --execute='event(new \App\Events\WatcherImportProgress(1, 1, "finished", "Test!", 5, 10, 2));'

# YouTube Sync (User 1)
php artisan tinker --execute='event(new \App\Events\YouTubeVideoSyncCompleted(1, "testchannel", true, 25));'

# Daily Scrape (User 1)
php artisan tinker --execute='event(new \App\Events\DailyScrapeCompleted(1, 10, 10));'

Relevante Dateien

DateiBeschreibung
config/broadcasting.phpLaravel Broadcasting Konfiguration
config/reverb.phpReverb Server Konfiguration
routes/channels.phpChannel Authorization
resources/js/echo.jsLaravel Echo Frontend Setup
app/Events/Alle Broadcast Events
app/Events/Concerns/SuppressesBroadcastFailures.phpGraceful Degradation Trait
app/Queue/Middleware/SuppressBroadcastFailures.phpQueue Middleware
app/Livewire/NotificationCenter.phpNotification Center (Server)
app/Livewire/NotificationBell.phpBell Component (Unread Count)
resources/views/livewire/notification-center.blade.phpNotification Center (Client, Echo Subscriptions)