Repository: laravel/reverb (php)

Issue:
Incorrect condition in `CliLogger::message` method
### Reverb Version

1.4

### Laravel Version

11.36

### PHP Version

8.3

### Description

There is an incorrect condition in the `CliLogger::message` method in the `vendor/laravel/reverb/src/Loggers/CliLogger.php` file. The condition should check if `message['data']['channel_data']` is a string, not `message['data']`.

### Steps To Reproduce

1. Log a message with `channel_data` as a string.
2. Observe that the `channel_data` is not decoded correctly.


Discussion:
Can you share a screenshot of the issue you are seeing on the CLI?
> Can you share a screenshot of the issue you are seeing on the CLI?

<img width="1278" alt="Screenshot 1403-10-22 at 14 17 45" src="https://github.com/user-attachments/assets/13d60062-1aa5-4584-991e-82eab7998073" />

As you can see in the image, when the channel is not in string format, the socket drops and restarts. This issue also exists on its main site https://reverb.laravel.com/.


`{"event":"pusher:subscribe","data":{"auth":"","channel":{}}}`


[video](https://www.youtube.com/watch?v=Lu4VA_26wAE)
>  Can you share a screenshot of the issue you are seeing on the CLI?

```
[2025-01-09 07:17:24] local.ERROR: json_decode(): Argument #1 ($json) must be of type string, array given {"exception":"[object] (TypeError(code: 0): json_decode(): Argument #1 ($json) must be of type string, array given at /vendor/laravel/reverb/src/Loggers/CliLogger.php:56)
[stacktrace]
```

اول از همه من مجبور شدم برای اینکه مشکل را توضیح بدم اینکار بکنم :)

[php code](https://limewire.com/d/8d16e125-0adc-4579-89a0-20521ab0de76#1MmqMFUt90L9Q6HhwCS8_B_UQHophxxpm1W7CHx3N5s)

[link sample](http://fm.freehost.io/websocketReverb.php?duration=15)

https://reverb.laravel.com/

کافیه کنسول سایت باز کنید و اسکریپت اجرا کنید و متوجه خطا شوید این کد باعث میش سوکت هر ۵ ثانیه سوکت که توسط پکیج reverb بالا میاد بیافته 

![Image](https://github.com/user-attachments/assets/ac996378-e8a7-4788-a812-ad16fce5c807)

حالا دوست داره لاراول این مشکل حل کنه یا خیر واقعا من نمیدونم !!


First of all, I had to do this to explain the issue :)

[php code](https://limewire.com/d/8d16e125-0adc-4579-89a0-20521ab0de76#1MmqMFUt90L9Q6HhwCS8_B_UQHophxxpm1W7CHx3N5s)

[link sample](http://fm.freehost.io/websocketReverb.php?duration=15)

https://reverb.laravel.com/

Just open the site console, run the script, and you’ll notice the error. This code causes the socket, which gets raised by the reverb package, to drop every 5 seconds.

![Image](https://github.com/user-attachments/assets/ac996378-e8a7-4788-a812-ad16fce5c807)

Now it’s up to Laravel to decide whether they want to resolve this issue or not; honestly, I don’t know!!


Patch:
diff --git a/src/Loggers/CliLogger.php b/src/Loggers/CliLogger.php
--- a/src/Loggers/CliLogger.php
+++ b/src/Loggers/CliLogger.php
@@ -52,7 +52,7 @@ public function message(string $message): void
             $message['data'] = json_decode($message['data'], true);
         }
 
-        if (isset($message['data']['channel_data'])) {
+        if (isset($message['data']['channel_data']) && is_string($message['data']['channel_data'])) {
             $message['data']['channel_data'] = json_decode($message['data']['channel_data'], true);
         }
 
diff --git a/src/Protocols/Pusher/ClientEvent.php b/src/Protocols/Pusher/ClientEvent.php
--- a/src/Protocols/Pusher/ClientEvent.php
+++ b/src/Protocols/Pusher/ClientEvent.php
@@ -2,6 +2,7 @@
 
 namespace Laravel\Reverb\Protocols\Pusher;
 
+use Illuminate\Support\Facades\Validator;
 use Illuminate\Support\Str;
 use Laravel\Reverb\Contracts\Connection;
 
@@ -12,6 +13,12 @@ class ClientEvent
      */
     public static function handle(Connection $connection, array $event): ?ClientEvent
     {
+        Validator::make($event, [
+            'event' => ['required', 'string'],
+            'channel' => ['required', 'string'],
+            'data' => ['required', 'array'],
+        ])->validate();
+
         if (! Str::startsWith($event['event'], 'client-')) {
             return null;
         }
diff --git a/src/Protocols/Pusher/EventHandler.php b/src/Protocols/Pusher/EventHandler.php
--- a/src/Protocols/Pusher/EventHandler.php
+++ b/src/Protocols/Pusher/EventHandler.php
@@ -3,6 +3,7 @@
 namespace Laravel\Reverb\Protocols\Pusher;
 
 use Exception;
+use Illuminate\Support\Facades\Validator;
 use Illuminate\Support\Str;
 use Laravel\Reverb\Contracts\Connection;
 use Laravel\Reverb\Protocols\Pusher\Channels\CacheChannel;
@@ -24,7 +25,9 @@ public function __construct(protected ChannelManager $channels)
      */
     public function handle(Connection $connection, string $event, array $payload = []): void
     {
-        match (Str::after($event, 'pusher:')) {
+        $event = Str::after($event, 'pusher:');
+
+        match ($event) {
             'connection_established' => $this->acknowledge($connection),
             'subscribe' => $this->subscribe(
                 $connection,
@@ -55,6 +58,16 @@ public function acknowledge(Connection $connection): void
      */
     public function subscribe(Connection $connection, string $channel, ?string $auth = null, ?string $data = null): void
     {
+        Validator::make([
+            'channel' => $channel,
+            'auth' => $auth,
+            'channel_data' => $data,
+        ], [
+            'channel' => ['nullable', 'string'],
+            'auth' => ['nullable', 'string'],
+            'channel_data' => ['nullable', 'json'],
+        ])->validate();
+
         $channel = $this->channels
             ->for($connection->app())
             ->findOrCreate($channel);
diff --git a/src/Protocols/Pusher/Server.php b/src/Protocols/Pusher/Server.php
--- a/src/Protocols/Pusher/Server.php
+++ b/src/Protocols/Pusher/Server.php
@@ -3,6 +3,7 @@
 namespace Laravel\Reverb\Protocols\Pusher;
 
 use Exception;
+use Illuminate\Support\Facades\Validator;
 use Illuminate\Support\Str;
 use Laravel\Reverb\Contracts\Connection;
 use Laravel\Reverb\Events\MessageReceived;
@@ -12,6 +13,7 @@
 use Laravel\Reverb\Protocols\Pusher\Exceptions\PusherException;
 use Ratchet\RFC6455\Messaging\Frame;
 use Ratchet\RFC6455\Messaging\FrameInterface;
+use Throwable;
 
 class Server
 {
@@ -54,12 +56,13 @@ public function message(Connection $from, string $message): void
         try {
             $event = json_decode($message, associative: true, flags: JSON_THROW_ON_ERROR);
 
+            Validator::make($event, ['event' => ['required', 'string']])->validate();
+
             match (Str::startsWith($event['event'], 'pusher:')) {
                 true => $this->handler->handle(
                     $from,
                     $event['event'],
                     empty($event['data']) ? [] : $event['data'],
-                    $event['channel'] ?? null
                 ),
                 default => ClientEvent::handle($from, $event)
             };
@@ -67,7 +70,7 @@ public function message(Connection $from, string $message): void
             Log::info('Message Handled', $from->id());
 
             MessageReceived::dispatch($from, $message);
-        } catch (Exception $e) {
+        } catch (Throwable $e) {
             $this->error($from, $e);
         }
     }
@@ -104,7 +107,7 @@ public function close(Connection $connection): void
     /**
      * Handle an error.
      */
-    public function error(Connection $connection, Exception $exception): void
+    public function error(Connection $connection, Throwable $exception): void
     {
         if ($exception instanceof PusherException) {
             $connection->send(json_encode($exception->payload()));


Test patch:
diff --git a/tests/Unit/Protocols/Pusher/ServerTest.php b/tests/Unit/Protocols/Pusher/ServerTest.php
--- a/tests/Unit/Protocols/Pusher/ServerTest.php
+++ b/tests/Unit/Protocols/Pusher/ServerTest.php
@@ -329,3 +329,157 @@
         ['localhost', '*.localhost'],
     ],
 ]);
+
+it('sends an error if something fails for event type', function () {
+    $this->server->message(
+        $connection = new FakeConnection,
+        json_encode([
+            'event' => [],
+        ])
+    );
+
+    $connection->assertReceived([
+        'event' => 'pusher:error',
+        'data' => json_encode([
+            'code' => 4200,
+            'message' => 'Invalid message format',
+        ]),
+    ]);
+});
+
+it('sends an error if something fails for data type', function () {
+    $this->server->message(
+        $connection = new FakeConnection,
+        json_encode([
+            'event' => 'pusher:subscribe',
+            'data' => 'sfsfsfs',
+        ])
+    );
+
+    $connection->assertReceived([
+        'event' => 'pusher:error',
+        'data' => json_encode([
+            'code' => 4200,
+            'message' => 'Invalid message format',
+        ]),
+    ]);
+});
+
+it('sends an error if something fails for data channel type', function () {
+    $this->server->message(
+        $connection = new FakeConnection,
+        json_encode([
+            'event' => 'pusher:subscribe',
+            'data' => [
+                'channel' => [],
+            ],
+        ])
+    );
+
+    $connection->assertReceived([
+        'event' => 'pusher:error',
+        'data' => json_encode([
+            'code' => 4200,
+            'message' => 'Invalid message format',
+        ]),
+    ]);
+
+    $this->server->message(
+        $connection = new FakeConnection,
+        json_encode([
+            'event' => 'pusher:subscribe',
+            'data' => [
+                'channel' => null,
+            ],
+        ])
+    );
+
+    $connection->assertReceived([
+        'event' => 'pusher:error',
+        'data' => json_encode([
+            'code' => 4200,
+            'message' => 'Invalid message format',
+        ]),
+    ]);
+});
+
+it('sends an error if something fails for data auth type', function () {
+    $this->server->message(
+        $connection = new FakeConnection,
+        json_encode([
+            'event' => 'pusher:subscribe',
+            'data' => [
+                'channel' => 'presence-test-channel',
+                'auth' => [],
+            ],
+        ])
+    );
+
+    $connection->assertReceived([
+        'event' => 'pusher:error',
+        'data' => json_encode([
+            'code' => 4200,
+            'message' => 'Invalid message format',
+        ]),
+    ]);
+});
+
+it('sends an error if something fails for data channel_data type', function () {
+    $this->server->message(
+        $connection = new FakeConnection,
+        json_encode([
+            'event' => 'pusher:subscribe',
+            'data' => [
+                'channel' => 'presence-test-channel',
+                'auth' => '',
+                'channel_data' => [],
+            ],
+        ])
+    );
+
+    $connection->assertReceived([
+        'event' => 'pusher:error',
+        'data' => json_encode([
+            'code' => 4200,
+            'message' => 'Invalid message format',
+        ]),
+    ]);
+
+    $this->server->message(
+        $connection = new FakeConnection,
+        json_encode([
+            'event' => 'pusher:subscribe',
+            'data' => [
+                'channel' => 'presence-test-channel',
+                'auth' => '',
+                'channel_data' => 'Hello',
+            ],
+        ])
+    );
+
+    $connection->assertReceived([
+        'event' => 'pusher:error',
+        'data' => json_encode([
+            'code' => 4200,
+            'message' => 'Invalid message format',
+        ]),
+    ]);
+});
+
+it('sends an error if something fails for channel type', function () {
+    $this->server->message(
+        $connection = new FakeConnection,
+        json_encode([
+            'event' => 'client-start-typing',
+            'channel' => [],
+        ])
+    );
+
+    $connection->assertReceived([
+        'event' => 'pusher:error',
+        'data' => json_encode([
+            'code' => 4200,
+            'message' => 'Invalid message format',
+        ]),
+    ]);
+});