diff --git a/.env.example b/.env.example index 2457c8c..f9a30e9 100644 --- a/.env.example +++ b/.env.example @@ -33,7 +33,7 @@ SESSION_ENCRYPT=false SESSION_PATH=/ SESSION_DOMAIN=null -BROADCAST_CONNECTION=log +BROADCAST_CONNECTION=reverb FILESYSTEM_DISK=local QUEUE_CONNECTION=redis @@ -47,6 +47,13 @@ REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 +REVERB_APP_ID=1 +REVERB_APP_KEY=local +REVERB_APP_SECRET=secret +REVERB_HOST=127.0.0.1 +REVERB_PORT=8080 +REVERB_SCHEME=http + MAIL_MAILER=log MAIL_SCHEME=null MAIL_HOST=127.0.0.1 diff --git a/app/Livewire/Common/NotificationBell.php b/app/Livewire/Common/NotificationBell.php index bd9a87e..9c76fc1 100644 --- a/app/Livewire/Common/NotificationBell.php +++ b/app/Livewire/Common/NotificationBell.php @@ -3,6 +3,7 @@ namespace App\Livewire\Common; use Illuminate\Support\Facades\Auth; +use Livewire\Attributes\On; use Livewire\Component; class NotificationBell extends Component @@ -37,6 +38,13 @@ class NotificationBell extends Component $this->loadNotifications(); } + #[On('notification-received')] + public function handleNotification($message = 'Nueva notificación') + { + $this->loadNotifications(); + $this->dispatch('notify', $message); + } + public function render() { return view('livewire.common.notification-bell'); diff --git a/app/Notifications/FeatureCompletedNotification.php b/app/Notifications/FeatureCompletedNotification.php index c7608c2..70c6679 100644 --- a/app/Notifications/FeatureCompletedNotification.php +++ b/app/Notifications/FeatureCompletedNotification.php @@ -4,9 +4,10 @@ namespace App\Notifications; use App\Models\Feature; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Notifications\Notification; -class FeatureCompletedNotification extends Notification +class FeatureCompletedNotification extends Notification implements ShouldBroadcast { use Queueable; @@ -14,7 +15,7 @@ class FeatureCompletedNotification extends Notification public function via($notifiable): array { - return ['database']; + return ['database', 'broadcast']; } public function toArray($notifiable): array @@ -28,4 +29,14 @@ class FeatureCompletedNotification extends Notification 'message' => "Elemento '{$this->feature->name}' marcado como completado", ]; } -} + + public function broadcastOn(): array + { + $projectId = $this->feature->layer?->phase?->project_id; + if (! $projectId) { + return []; + } + $userIds = \App\Models\Project::find($projectId)?->users->pluck('id')->toArray() ?? []; + return array_map(fn ($id) => new \Illuminate\Broadcasting\PrivateChannel("user.{$id}"), $userIds); + } +} \ No newline at end of file diff --git a/app/Notifications/InspectionCompletedNotification.php b/app/Notifications/InspectionCompletedNotification.php index 52b1e67..c93460a 100644 --- a/app/Notifications/InspectionCompletedNotification.php +++ b/app/Notifications/InspectionCompletedNotification.php @@ -4,9 +4,10 @@ namespace App\Notifications; use App\Models\Inspection; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Notifications\Notification; -class InspectionCompletedNotification extends Notification +class InspectionCompletedNotification extends Notification implements ShouldBroadcast { use Queueable; @@ -14,7 +15,7 @@ class InspectionCompletedNotification extends Notification public function via($notifiable): array { - return ['database']; + return ['database', 'broadcast']; } public function toArray($notifiable): array @@ -29,4 +30,10 @@ class InspectionCompletedNotification extends Notification 'message' => "Inspección completada en '{$this->inspection->feature?->name}': ".($this->inspection->result ?? 'sin resultado'), ]; } -} + + public function broadcastOn(): array + { + $userIds = $this->inspection->project->users->pluck('id')->toArray(); + return array_map(fn ($id) => new \Illuminate\Broadcasting\PrivateChannel("user.{$id}"), $userIds); + } +} \ No newline at end of file diff --git a/app/Notifications/InspectionDeletedNotification.php b/app/Notifications/InspectionDeletedNotification.php index 0515a2c..b2a7f1f 100644 --- a/app/Notifications/InspectionDeletedNotification.php +++ b/app/Notifications/InspectionDeletedNotification.php @@ -4,9 +4,10 @@ namespace App\Notifications; use App\Models\Inspection; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Notifications\Notification; -class InspectionDeletedNotification extends Notification +class InspectionDeletedNotification extends Notification implements ShouldBroadcast { use Queueable; @@ -14,7 +15,7 @@ class InspectionDeletedNotification extends Notification public function via($notifiable): array { - return ['database']; + return ['database', 'broadcast']; } public function toArray($notifiable): array @@ -29,4 +30,10 @@ class InspectionDeletedNotification extends Notification 'message' => "Inspección eliminada en '{$this->inspection->feature?->name}' por {$this->deletedBy}", ]; } -} + + public function broadcastOn(): array + { + $userIds = $this->inspection->project->users->pluck('id')->toArray(); + return array_map(fn ($id) => new \Illuminate\Broadcasting\PrivateChannel("user.{$id}"), $userIds); + } +} \ No newline at end of file diff --git a/app/Notifications/InspectionUpdatedNotification.php b/app/Notifications/InspectionUpdatedNotification.php index e3b9637..cb65e73 100644 --- a/app/Notifications/InspectionUpdatedNotification.php +++ b/app/Notifications/InspectionUpdatedNotification.php @@ -4,9 +4,10 @@ namespace App\Notifications; use App\Models\Inspection; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Notifications\Notification; -class InspectionUpdatedNotification extends Notification +class InspectionUpdatedNotification extends Notification implements ShouldBroadcast { use Queueable; @@ -14,7 +15,7 @@ class InspectionUpdatedNotification extends Notification public function via($notifiable): array { - return ['database']; + return ['database', 'broadcast']; } public function toArray($notifiable): array @@ -30,4 +31,10 @@ class InspectionUpdatedNotification extends Notification 'message' => "Inspección actualizada en '{$this->inspection->feature?->name}'", ]; } -} + + public function broadcastOn(): array + { + $userIds = $this->inspection->project->users->pluck('id')->toArray(); + return array_map(fn ($id) => new \Illuminate\Broadcasting\PrivateChannel("user.{$id}"), $userIds); + } +} \ No newline at end of file diff --git a/app/Notifications/IssueAssignedNotification.php b/app/Notifications/IssueAssignedNotification.php index 9a6577a..fb84f9d 100644 --- a/app/Notifications/IssueAssignedNotification.php +++ b/app/Notifications/IssueAssignedNotification.php @@ -4,9 +4,10 @@ namespace App\Notifications; use App\Models\Issue; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Notifications\Notification; -class IssueAssignedNotification extends Notification +class IssueAssignedNotification extends Notification implements ShouldBroadcast { use Queueable; @@ -14,7 +15,7 @@ class IssueAssignedNotification extends Notification public function via($notifiable): array { - return ['database']; + return ['database', 'broadcast']; } public function toArray($notifiable): array @@ -27,4 +28,9 @@ class IssueAssignedNotification extends Notification 'message' => "Se te ha asignado la incidencia '{$this->issue->title}'", ]; } -} + + public function broadcastOn(): array + { + return [new \Illuminate\Broadcasting\PrivateChannel('user.'.$this->issue->assigned_to)]; + } +} \ No newline at end of file diff --git a/app/Notifications/IssueCommentedNotification.php b/app/Notifications/IssueCommentedNotification.php index f72fd0d..8b78bcb 100644 --- a/app/Notifications/IssueCommentedNotification.php +++ b/app/Notifications/IssueCommentedNotification.php @@ -4,10 +4,11 @@ namespace App\Notifications; use App\Models\IssueComment; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Notifications\Notification; use Illuminate\Support\Str; -class IssueCommentedNotification extends Notification +class IssueCommentedNotification extends Notification implements ShouldBroadcast { use Queueable; @@ -15,7 +16,7 @@ class IssueCommentedNotification extends Notification public function via($notifiable): array { - return ['database']; + return ['database', 'broadcast']; } public function toArray($notifiable): array @@ -30,4 +31,13 @@ class IssueCommentedNotification extends Notification 'message' => "{$this->comment->user?->name} comentó en '{$issue?->title}': ".Str::limit($this->comment->body, 60), ]; } -} + + public function broadcastOn(): array + { + $issue = $this->comment->issue; + $userIds = []; + if ($issue->assigned_to) $userIds[] = $issue->assigned_to; + if ($issue->reported_by) $userIds[] = $issue->reported_by; + return array_map(fn ($id) => new \Illuminate\Broadcasting\PrivateChannel("user.{$id}"), array_unique($userIds)); + } +} \ No newline at end of file diff --git a/app/Notifications/IssueReportedNotification.php b/app/Notifications/IssueReportedNotification.php index 1fdc208..6832f4c 100644 --- a/app/Notifications/IssueReportedNotification.php +++ b/app/Notifications/IssueReportedNotification.php @@ -4,9 +4,10 @@ namespace App\Notifications; use App\Models\Issue; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Notifications\Notification; -class IssueReportedNotification extends Notification +class IssueReportedNotification extends Notification implements ShouldBroadcast { use Queueable; @@ -14,7 +15,7 @@ class IssueReportedNotification extends Notification public function via($notifiable): array { - return ['database']; + return ['database', 'broadcast']; } public function toArray($notifiable): array @@ -28,4 +29,10 @@ class IssueReportedNotification extends Notification 'message' => "Nuevo issue '{$this->issue->title}' (prioridad: {$this->issue->priority})", ]; } -} + + public function broadcastOn(): array + { + $userIds = $this->issue->project->users->pluck('id')->toArray(); + return array_map(fn ($id) => new \Illuminate\Broadcasting\PrivateChannel("user.{$id}"), $userIds); + } +} \ No newline at end of file diff --git a/app/Notifications/IssueStatusChangedNotification.php b/app/Notifications/IssueStatusChangedNotification.php index 74d9fd5..c130e42 100644 --- a/app/Notifications/IssueStatusChangedNotification.php +++ b/app/Notifications/IssueStatusChangedNotification.php @@ -4,9 +4,10 @@ namespace App\Notifications; use App\Models\Issue; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Notifications\Notification; -class IssueStatusChangedNotification extends Notification +class IssueStatusChangedNotification extends Notification implements ShouldBroadcast { use Queueable; @@ -14,7 +15,7 @@ class IssueStatusChangedNotification extends Notification public function via($notifiable): array { - return ['database']; + return ['database', 'broadcast']; } public function toArray($notifiable): array @@ -34,4 +35,12 @@ class IssueStatusChangedNotification extends Notification 'message' => "La incidencia '{$this->issue->title}' ha sido {$label}", ]; } -} + + public function broadcastOn(): array + { + $userIds = []; + if ($this->issue->assigned_to) $userIds[] = $this->issue->assigned_to; + if ($this->issue->reported_by) $userIds[] = $this->issue->reported_by; + return array_map(fn ($id) => new \Illuminate\Broadcasting\PrivateChannel("user.{$id}"), array_unique($userIds)); + } +} \ No newline at end of file diff --git a/app/Notifications/IssueTaskAssignedNotification.php b/app/Notifications/IssueTaskAssignedNotification.php index 43fb98d..1f331c5 100644 --- a/app/Notifications/IssueTaskAssignedNotification.php +++ b/app/Notifications/IssueTaskAssignedNotification.php @@ -4,9 +4,10 @@ namespace App\Notifications; use App\Models\IssueTask; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Notifications\Notification; -class IssueTaskAssignedNotification extends Notification +class IssueTaskAssignedNotification extends Notification implements ShouldBroadcast { use Queueable; @@ -14,7 +15,7 @@ class IssueTaskAssignedNotification extends Notification public function via($notifiable): array { - return ['database']; + return ['database', 'broadcast']; } public function toArray($notifiable): array @@ -28,4 +29,9 @@ class IssueTaskAssignedNotification extends Notification 'message' => "Se te ha asignado la tarea '{$this->task->title}'", ]; } -} + + public function broadcastOn(): array + { + return [new \Illuminate\Broadcasting\PrivateChannel('user.'.$this->task->assigned_to)]; + } +} \ No newline at end of file diff --git a/app/Notifications/IssueTaskOverdueNotification.php b/app/Notifications/IssueTaskOverdueNotification.php index e2f39b1..f2af882 100644 --- a/app/Notifications/IssueTaskOverdueNotification.php +++ b/app/Notifications/IssueTaskOverdueNotification.php @@ -4,9 +4,10 @@ namespace App\Notifications; use App\Models\IssueTask; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Notifications\Notification; -class IssueTaskOverdueNotification extends Notification +class IssueTaskOverdueNotification extends Notification implements ShouldBroadcast { use Queueable; @@ -14,7 +15,7 @@ class IssueTaskOverdueNotification extends Notification public function via($notifiable): array { - return ['database']; + return ['database', 'broadcast']; } public function toArray($notifiable): array @@ -28,4 +29,9 @@ class IssueTaskOverdueNotification extends Notification 'message' => "Tarea vencida: '{$this->task->title}' (venció el {$this->task->due_date?->format('d/m/Y')})", ]; } -} + + public function broadcastOn(): array + { + return [new \Illuminate\Broadcasting\PrivateChannel('user.'.$this->task->assigned_to)]; + } +} \ No newline at end of file diff --git a/app/Notifications/TaskAssignedNotification.php b/app/Notifications/TaskAssignedNotification.php index a363ef9..3a06ee1 100644 --- a/app/Notifications/TaskAssignedNotification.php +++ b/app/Notifications/TaskAssignedNotification.php @@ -4,9 +4,10 @@ namespace App\Notifications; use App\Models\Task; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Notifications\Notification; -class TaskAssignedNotification extends Notification +class TaskAssignedNotification extends Notification implements ShouldBroadcast { use Queueable; @@ -14,7 +15,7 @@ class TaskAssignedNotification extends Notification public function via($notifiable): array { - return ['database']; + return ['database', 'broadcast']; } public function toArray($notifiable): array @@ -27,4 +28,9 @@ class TaskAssignedNotification extends Notification 'message' => "Se te ha asignado la tarea '{$this->task->title}'", ]; } -} + + public function broadcastOn(): array + { + return [new \Illuminate\Broadcasting\PrivateChannel('user.'.$this->task->assigned_to)]; + } +} \ No newline at end of file diff --git a/app/Notifications/TaskCommentNotification.php b/app/Notifications/TaskCommentNotification.php index d0574ed..22c540e 100644 --- a/app/Notifications/TaskCommentNotification.php +++ b/app/Notifications/TaskCommentNotification.php @@ -5,9 +5,10 @@ namespace App\Notifications; use App\Models\Comment; use App\Models\Task; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Notifications\Notification; -class TaskCommentNotification extends Notification +class TaskCommentNotification extends Notification implements ShouldBroadcast { use Queueable; @@ -15,7 +16,7 @@ class TaskCommentNotification extends Notification public function via($notifiable): array { - return ['database']; + return ['database', 'broadcast']; } public function toArray($notifiable): array @@ -28,4 +29,12 @@ class TaskCommentNotification extends Notification 'message' => "Nuevo comentario en '{$this->task->title}' por {$this->comment->user->name}: ".substr($this->comment->body, 0, 100), ]; } -} + + public function broadcastOn(): array + { + $userIds = []; + if ($this->task->assigned_to) $userIds[] = $this->task->assigned_to; + if ($this->task->created_by) $userIds[] = $this->task->created_by; + return array_map(fn ($id) => new \Illuminate\Broadcasting\PrivateChannel("user.{$id}"), array_unique($userIds)); + } +} \ No newline at end of file diff --git a/app/Notifications/TaskOverdueNotification.php b/app/Notifications/TaskOverdueNotification.php index c6178ae..41b5a27 100644 --- a/app/Notifications/TaskOverdueNotification.php +++ b/app/Notifications/TaskOverdueNotification.php @@ -4,9 +4,10 @@ namespace App\Notifications; use App\Models\Task; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Notifications\Notification; -class TaskOverdueNotification extends Notification +class TaskOverdueNotification extends Notification implements ShouldBroadcast { use Queueable; @@ -14,7 +15,7 @@ class TaskOverdueNotification extends Notification public function via($notifiable): array { - return ['database']; + return ['database', 'broadcast']; } public function toArray($notifiable): array @@ -27,4 +28,9 @@ class TaskOverdueNotification extends Notification 'message' => "Tarea vencida: '{$this->task->title}' (venció el {$this->task->due_date?->format('d/m/Y')})", ]; } -} + + public function broadcastOn(): array + { + return [new \Illuminate\Broadcasting\PrivateChannel('user.'.$this->task->assigned_to)]; + } +} \ No newline at end of file diff --git a/app/Notifications/TaskStatusChangedNotification.php b/app/Notifications/TaskStatusChangedNotification.php index a690ac5..7a96750 100644 --- a/app/Notifications/TaskStatusChangedNotification.php +++ b/app/Notifications/TaskStatusChangedNotification.php @@ -4,9 +4,10 @@ namespace App\Notifications; use App\Models\Task; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Notifications\Notification; -class TaskStatusChangedNotification extends Notification +class TaskStatusChangedNotification extends Notification implements ShouldBroadcast { use Queueable; @@ -14,7 +15,7 @@ class TaskStatusChangedNotification extends Notification public function via($notifiable): array { - return ['database']; + return ['database', 'broadcast']; } public function toArray($notifiable): array @@ -42,4 +43,12 @@ class TaskStatusChangedNotification extends Notification 'message' => "La tarea '{$this->task->title}' cambió de {$oldLabel} a {$label}", ]; } -} + + public function broadcastOn(): array + { + $userIds = []; + if ($this->task->assigned_to) $userIds[] = $this->task->assigned_to; + if ($this->task->created_by) $userIds[] = $this->task->created_by; + return array_map(fn ($id) => new \Illuminate\Broadcasting\PrivateChannel("user.{$id}"), array_unique($userIds)); + } +} \ No newline at end of file diff --git a/bootstrap/app.php b/bootstrap/app.php index 8f63d08..c268d5f 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -17,6 +17,7 @@ return Application::configure(basePath: dirname(__DIR__)) web: __DIR__.'/../routes/web.php', api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', + channels: __DIR__.'/../routes/channels.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { diff --git a/composer.json b/composer.json index 9cdc160..c2cd3bb 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "blade-ui-kit/blade-heroicons": "^2.7", "gasparesganga/php-shapefile": "^3.4", "laravel/framework": "^12.0", + "laravel/reverb": "^1.11", "laravel/sanctum": "^4.3", "laravel/tinker": "^2.10.1", "league/geotools": "^1.3", diff --git a/composer.lock b/composer.lock index 8177505..ea0ac1d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "59f27ca027f68b2cd10cb3d30bae901b", + "content-hash": "043b5e79681c66887e3615a82073d335", "packages": [ { "name": "blade-ui-kit/blade-heroicons", @@ -285,6 +285,136 @@ ], "time": "2024-02-09T16:56:22+00:00" }, + { + "name": "clue/redis-protocol", + "version": "v0.3.2", + "source": { + "type": "git", + "url": "https://github.com/clue/redis-protocol.git", + "reference": "6f565332f5531b7722d1e9c445314b91862f6d6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/redis-protocol/zipball/6f565332f5531b7722d1e9c445314b91862f6d6c", + "reference": "6f565332f5531b7722d1e9c445314b91862f6d6c", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\Redis\\Protocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@lueck.tv" + } + ], + "description": "A streaming Redis protocol (RESP) parser and serializer written in pure PHP.", + "homepage": "https://github.com/clue/redis-protocol", + "keywords": [ + "parser", + "protocol", + "redis", + "resp", + "serializer", + "streaming" + ], + "support": { + "issues": "https://github.com/clue/redis-protocol/issues", + "source": "https://github.com/clue/redis-protocol/tree/v0.3.2" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2024-08-07T11:06:28+00:00" + }, + { + "name": "clue/redis-react", + "version": "v2.8.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-redis.git", + "reference": "84569198dfd5564977d2ae6a32de4beb5a24bdca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-redis/zipball/84569198dfd5564977d2ae6a32de4beb5a24bdca", + "reference": "84569198dfd5564977d2ae6a32de4beb5a24bdca", + "shasum": "" + }, + "require": { + "clue/redis-protocol": "^0.3.2", + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.0 || ^1.1", + "react/promise-timer": "^1.11", + "react/socket": "^1.16" + }, + "require-dev": { + "clue/block-react": "^1.5", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\Redis\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Async Redis client implementation, built on top of ReactPHP.", + "homepage": "https://github.com/clue/reactphp-redis", + "keywords": [ + "async", + "client", + "database", + "reactphp", + "redis" + ], + "support": { + "issues": "https://github.com/clue/reactphp-redis/issues", + "source": "https://github.com/clue/reactphp-redis/tree/v2.8.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2025-01-03T16:18:33+00:00" + }, { "name": "composer/pcre", "version": "3.3.2", @@ -814,6 +944,53 @@ ], "time": "2025-03-06T22:45:56+00:00" }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, { "name": "ezyang/htmlpurifier", "version": "v4.19.0", @@ -1753,6 +1930,85 @@ }, "time": "2026-04-20T16:07:33+00:00" }, + { + "name": "laravel/reverb", + "version": "v1.11.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/reverb.git", + "reference": "52ce5fd88cd1d7eacfdcf6b91cea4704cc546f27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/reverb/zipball/52ce5fd88cd1d7eacfdcf6b91cea4704cc546f27", + "reference": "52ce5fd88cd1d7eacfdcf6b91cea4704cc546f27", + "shasum": "" + }, + "require": { + "clue/redis-react": "^2.6", + "guzzlehttp/psr7": "^2.6", + "illuminate/console": "^10.47|^11.0|^12.0|^13.0", + "illuminate/contracts": "^10.47|^11.0|^12.0|^13.0", + "illuminate/http": "^10.47|^11.0|^12.0|^13.0", + "illuminate/support": "^10.47|^11.0|^12.0|^13.0", + "laravel/prompts": "^0.1.15|^0.2.0|^0.3.0", + "php": "^8.2", + "pusher/pusher-php-server": "^7.2", + "ratchet/rfc6455": "^0.4", + "react/promise-timer": "^1.10", + "react/socket": "^1.14", + "symfony/console": "^6.0|^7.0|^8.0", + "symfony/http-foundation": "^6.3|^7.0|^8.0" + }, + "require-dev": { + "orchestra/testbench": "^8.36|^9.15|^10.8|^11.0", + "pestphp/pest": "^2.0|^3.0|^4.0", + "phpstan/phpstan": "^1.10", + "ratchet/pawl": "^0.4.1", + "react/async": "^4.2", + "react/http": "^1.9" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Reverb\\ApplicationManagerServiceProvider", + "Laravel\\Reverb\\ReverbServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Reverb\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Joe Dixon", + "email": "joe@laravel.com" + } + ], + "description": "Laravel Reverb provides a real-time WebSocket communication backend for Laravel applications.", + "keywords": [ + "WebSockets", + "laravel", + "real-time", + "websocket" + ], + "support": { + "issues": "https://github.com/laravel/reverb/issues", + "source": "https://github.com/laravel/reverb/tree/v1.11.1" + }, + "time": "2026-08-06T14:48:58+00:00" + }, { "name": "laravel/sanctum", "version": "v4.3.2", @@ -4476,6 +4732,69 @@ }, "time": "2026-03-22T23:03:24+00:00" }, + { + "name": "pusher/pusher-php-server", + "version": "7.3.0", + "source": { + "type": "git", + "url": "https://github.com/pusher/pusher-http-php.git", + "reference": "058d8464246118110a341fc2e6e70c7c8b6a7f2c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/058d8464246118110a341fc2e6e70c7c8b6a7f2c", + "reference": "058d8464246118110a341fc2e6e70c7c8b6a7f2c", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "guzzlehttp/guzzle": "^7.8.2 || ^8.0", + "guzzlehttp/promises": "^2.0.3 || ^3.0", + "guzzlehttp/psr7": "^2.6.3 || ^3.0", + "php": "^7.3|^8.0", + "psr/http-client": "^1.0", + "psr/log": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "overtrue/phplint": "^2.3", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "Pusher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Library for interacting with the Pusher REST API", + "keywords": [ + "events", + "messaging", + "php-pusher-server", + "publish", + "push", + "pusher", + "real time", + "real-time", + "realtime", + "rest", + "trigger" + ], + "support": { + "issues": "https://github.com/pusher/pusher-http-php/issues", + "source": "https://github.com/pusher/pusher-http-php/tree/7.3.0" + }, + "time": "2026-08-07T12:47:11+00:00" + }, { "name": "ralouphie/getallheaders", "version": "3.0.3", @@ -4755,6 +5074,213 @@ ], "time": "2025-05-03T02:24:46+00:00" }, + { + "name": "ratchet/rfc6455", + "version": "v0.4.1", + "source": { + "type": "git", + "url": "https://github.com/ratchetphp/RFC6455.git", + "reference": "9b05f371219cbaf9748b505f139617dd0715592b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/9b05f371219cbaf9748b505f139617dd0715592b", + "reference": "9b05f371219cbaf9748b505f139617dd0715592b", + "shasum": "" + }, + "require": { + "php": ">=7.4", + "psr/http-factory-implementation": "^1.0", + "symfony/polyfill-php80": "^1.15" + }, + "require-dev": { + "guzzlehttp/psr7": "^2.7", + "phpunit/phpunit": "^9.5", + "react/socket": "^1.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ratchet\\RFC6455\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "role": "Developer" + }, + { + "name": "Matt Bonneau", + "role": "Developer" + } + ], + "description": "RFC6455 WebSocket protocol handler", + "homepage": "http://socketo.me", + "keywords": [ + "WebSockets", + "rfc6455", + "websocket" + ], + "support": { + "chat": "https://gitter.im/reactphp/reactphp", + "issues": "https://github.com/ratchetphp/RFC6455/issues", + "source": "https://github.com/ratchetphp/RFC6455/tree/v0.4.1" + }, + "time": "2026-06-06T14:34:23+00:00" + }, + { + "name": "react/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/dns", + "version": "v1.14.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.14.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-18T19:34:28+00:00" + }, { "name": "react/event-loop", "version": "v1.6.0", @@ -4900,6 +5426,243 @@ ], "time": "2025-08-19T18:57:03+00:00" }, + { + "name": "react/promise-timer", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise-timer.git", + "reference": "4f70306ed66b8b44768941ca7f142092600fafc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise-timer/zipball/4f70306ed66b8b44768941ca7f142092600fafc1", + "reference": "4f70306ed66b8b44768941ca7f142092600fafc1", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7.0 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\Timer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.", + "homepage": "https://github.com/reactphp/promise-timer", + "keywords": [ + "async", + "event-loop", + "promise", + "reactphp", + "timeout", + "timer" + ], + "support": { + "issues": "https://github.com/reactphp/promise-timer/issues", + "source": "https://github.com/reactphp/promise-timer/tree/v1.11.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-04T14:27:45+00:00" + }, + { + "name": "react/socket", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.17.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-19T20:47:34+00:00" + }, + { + "name": "react/stream", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" + }, { "name": "spatie/laravel-permission", "version": "6.25.0", diff --git a/config/broadcasting.php b/config/broadcasting.php new file mode 100644 index 0000000..ebc3fb9 --- /dev/null +++ b/config/broadcasting.php @@ -0,0 +1,82 @@ + env('BROADCAST_CONNECTION', 'null'), + + /* + |-------------------------------------------------------------------------- + | Broadcast Connections + |-------------------------------------------------------------------------- + | + | Here you may define all of the broadcast connections that will be used + | to broadcast events to other systems or over WebSockets. Samples of + | each available type of connection are provided inside this array. + | + */ + + 'connections' => [ + + 'reverb' => [ + 'driver' => 'reverb', + 'key' => env('REVERB_APP_KEY'), + 'secret' => env('REVERB_APP_SECRET'), + 'app_id' => env('REVERB_APP_ID'), + 'options' => [ + 'host' => env('REVERB_HOST'), + 'port' => env('REVERB_PORT', 443), + 'scheme' => env('REVERB_SCHEME', 'https'), + 'useTLS' => env('REVERB_SCHEME', 'https') === 'https', + ], + 'client_options' => [ + // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html + ], + ], + + 'pusher' => [ + 'driver' => 'pusher', + 'key' => env('PUSHER_APP_KEY'), + 'secret' => env('PUSHER_APP_SECRET'), + 'app_id' => env('PUSHER_APP_ID'), + 'options' => [ + 'cluster' => env('PUSHER_APP_CLUSTER'), + 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com', + 'port' => env('PUSHER_PORT', 443), + 'scheme' => env('PUSHER_SCHEME', 'https'), + 'encrypted' => true, + 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https', + ], + 'client_options' => [ + // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html + ], + ], + + 'ably' => [ + 'driver' => 'ably', + 'key' => env('ABLY_KEY'), + ], + + 'log' => [ + 'driver' => 'log', + ], + + 'null' => [ + 'driver' => 'null', + ], + + ], + +]; diff --git a/config/reverb.php b/config/reverb.php new file mode 100644 index 0000000..91f3880 --- /dev/null +++ b/config/reverb.php @@ -0,0 +1,102 @@ + env('REVERB_SERVER', 'reverb'), + + /* + |-------------------------------------------------------------------------- + | Reverb Servers + |-------------------------------------------------------------------------- + | + | Here you may define details for each of the supported Reverb servers. + | Each server has its own configuration options that are defined in + | the array below. You should ensure all the options are present. + | + */ + + 'servers' => [ + + 'reverb' => [ + 'host' => env('REVERB_SERVER_HOST', '0.0.0.0'), + 'port' => env('REVERB_SERVER_PORT', 8080), + 'path' => env('REVERB_SERVER_PATH', ''), + 'hostname' => env('REVERB_HOST'), + 'options' => [ + 'tls' => [], + ], + 'max_request_size' => env('REVERB_MAX_REQUEST_SIZE', 10_000), + 'scaling' => [ + 'enabled' => env('REVERB_SCALING_ENABLED', false), + 'channel' => env('REVERB_SCALING_CHANNEL', 'reverb'), + 'server' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'port' => env('REDIS_PORT', '6379'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'database' => env('REDIS_DB', '0'), + 'timeout' => env('REDIS_TIMEOUT', 60), + ], + ], + 'pulse_ingest_interval' => env('REVERB_PULSE_INGEST_INTERVAL', 15), + 'telescope_ingest_interval' => env('REVERB_TELESCOPE_INGEST_INTERVAL', 15), + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Reverb Applications + |-------------------------------------------------------------------------- + | + | Here you may define how Reverb applications are managed. If you choose + | to use the "config" provider, you may define an array of apps which + | your server will support, including their connection credentials. + | + */ + + 'apps' => [ + + 'provider' => 'config', + + 'apps' => [ + [ + 'key' => env('REVERB_APP_KEY'), + 'secret' => env('REVERB_APP_SECRET'), + 'app_id' => env('REVERB_APP_ID'), + 'options' => [ + 'host' => env('REVERB_HOST'), + 'port' => env('REVERB_PORT', 443), + 'scheme' => env('REVERB_SCHEME', 'https'), + 'useTLS' => env('REVERB_SCHEME', 'https') === 'https', + ], + 'allowed_origins' => ['*'], + 'ping_interval' => env('REVERB_APP_PING_INTERVAL', 60), + 'activity_timeout' => env('REVERB_APP_ACTIVITY_TIMEOUT', 30), + 'max_connections' => env('REVERB_APP_MAX_CONNECTIONS'), + 'max_message_size' => env('REVERB_APP_MAX_MESSAGE_SIZE', 10_000), + 'accept_client_events_from' => env('REVERB_APP_ACCEPT_CLIENT_EVENTS_FROM', 'members'), + 'rate_limiting' => [ + 'enabled' => env('REVERB_APP_RATE_LIMITING_ENABLED', false), + 'max_attempts' => env('REVERB_APP_RATE_LIMIT_MAX_ATTEMPTS', 60), + 'decay_seconds' => env('REVERB_APP_RATE_LIMIT_DECAY_SECONDS', 60), + 'terminate_on_limit' => env('REVERB_APP_RATE_LIMIT_TERMINATE', false), + ], + ], + ], + + ], + +]; diff --git a/package-lock.json b/package-lock.json index af220a0..ed56ad3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,8 +17,10 @@ "axios": "^1.11.0", "concurrently": "^9.0.1", "daisyui": "^5.5.19", + "laravel-echo": "^2.4.0", "laravel-vite-plugin": "^2.0.0", "postcss": "^8.5.12", + "pusher-js": "^8.6.0", "tailwindcss": "^3.4.19", "vite": "^7.0.7" } @@ -2156,6 +2158,28 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/laravel-echo": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-2.4.0.tgz", + "integrity": "sha512-8w0fAGSNt6THfbNyqdKc29bhfeNpJg13CGx2fcLgoX0/f0mTJm/AIkYTTakmcr9pc42ZB68cSoE00j4/xNaFGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "pusher-js": "*", + "socket.io-client": "*" + }, + "peerDependenciesMeta": { + "pusher-js": { + "optional": true + }, + "socket.io-client": { + "optional": true + } + } + }, "node_modules/laravel-vite-plugin": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.1.0.tgz", @@ -2832,6 +2856,16 @@ "node": ">=10" } }, + "node_modules/pusher-js": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.6.0.tgz", + "integrity": "sha512-wShJPfCS/kYkCBVzVW67wa9cnQIgHTszEK2XHNrFkOgGruuGw081aERAxfRjfdFU+WcIt8x6dvbwkTW4iZuQ8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tweetnacl": "^1.0.3" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3266,6 +3300,13 @@ "dev": true, "license": "0BSD" }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "dev": true, + "license": "Unlicense" + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", diff --git a/package.json b/package.json index bed5ad2..0000f47 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,10 @@ "axios": "^1.11.0", "concurrently": "^9.0.1", "daisyui": "^5.5.19", + "laravel-echo": "^2.4.0", "laravel-vite-plugin": "^2.0.0", "postcss": "^8.5.12", + "pusher-js": "^8.6.0", "tailwindcss": "^3.4.19", "vite": "^7.0.7" }, diff --git a/resources/js/app.js b/resources/js/app.js index 5c20874..2dc4527 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,5 +1,19 @@ import './bootstrap'; +import Echo from 'laravel-echo'; +import Pusher from 'pusher-js'; + +window.Pusher = Pusher; +window.Echo = new Echo({ + broadcaster: 'reverb', + key: import.meta.env.VITE_REVERB_APP_KEY, + wsHost: import.meta.env.VITE_REVERB_HOST, + wsPort: import.meta.env.VITE_REVERB_PORT, + wssPort: import.meta.env.VITE_REVERB_PORT, + forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https', + enabledTransports: ['ws', 'wss'], +}); + import localforage from 'localforage'; // Generic function to queue any offline action diff --git a/resources/views/livewire/common/notification-bell.blade.php b/resources/views/livewire/common/notification-bell.blade.php index efffeb4..1956af9 100644 --- a/resources/views/livewire/common/notification-bell.blade.php +++ b/resources/views/livewire/common/notification-bell.blade.php @@ -84,4 +84,18 @@ @endif + + @push('scripts') + + @endpush diff --git a/routes/channels.php b/routes/channels.php new file mode 100644 index 0000000..df2ad28 --- /dev/null +++ b/routes/channels.php @@ -0,0 +1,7 @@ +id === (int) $id; +});