WebSocketHandler::onMessage()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 11
c 2
b 0
f 0
nc 3
nop 2
dl 0
loc 18
rs 9.9
1
<?php
2
3
namespace BeyondCode\LaravelWebSockets\Server;
4
5
use BeyondCode\LaravelWebSockets\Apps\App;
6
use BeyondCode\LaravelWebSockets\Contracts\ChannelManager;
7
use BeyondCode\LaravelWebSockets\DashboardLogger;
8
use BeyondCode\LaravelWebSockets\Events\ConnectionClosed;
9
use BeyondCode\LaravelWebSockets\Events\NewConnection;
10
use BeyondCode\LaravelWebSockets\Events\WebSocketMessageReceived;
11
use BeyondCode\LaravelWebSockets\Facades\StatisticsCollector;
12
use Exception;
13
use Ratchet\ConnectionInterface;
14
use Ratchet\RFC6455\Messaging\MessageInterface;
15
use Ratchet\WebSocket\MessageComponentInterface;
16
17
class WebSocketHandler implements MessageComponentInterface
18
{
19
    /**
20
     * The channel manager.
21
     *
22
     * @var ChannelManager
23
     */
24
    protected $channelManager;
25
26
    /**
27
     * Initialize a new handler.
28
     *
29
     * @param  \BeyondCode\LaravelWebSockets\Contracts\ChannelManager  $channelManager
30
     * @return void
31
     */
32
    public function __construct(ChannelManager $channelManager)
33
    {
34
        $this->channelManager = $channelManager;
35
    }
36
37
    /**
38
     * Handle the socket opening.
39
     *
40
     * @param  \Ratchet\ConnectionInterface  $connection
41
     * @return void
42
     */
43
    public function onOpen(ConnectionInterface $connection)
44
    {
45
        if (! $this->connectionCanBeMade($connection)) {
46
            return $connection->close();
47
        }
48
49
        $this->verifyAppKey($connection)
50
            ->verifyOrigin($connection)
51
            ->limitConcurrentConnections($connection)
52
            ->generateSocketId($connection)
53
            ->establishConnection($connection);
54
55
        if (isset($connection->app)) {
0 ignored issues
show
Bug introduced by
Accessing app on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
56
            /** @var \GuzzleHttp\Psr7\Request $request */
57
            $request = $connection->httpRequest;
0 ignored issues
show
Bug introduced by
Accessing httpRequest on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
58
59
            if ($connection->app->statisticsEnabled) {
60
                StatisticsCollector::connection($connection->app->id);
61
            }
62
63
            $this->channelManager->subscribeToApp($connection->app->id);
64
65
            $this->channelManager->connectionPonged($connection);
66
67
            DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_CONNECTED, [
68
                'origin' => "{$request->getUri()->getScheme()}://{$request->getUri()->getHost()}",
69
                'socketId' => $connection->socketId,
0 ignored issues
show
Bug introduced by
Accessing socketId on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
70
            ]);
71
72
            NewConnection::dispatch($connection->app->id, $connection->socketId);
73
        }
74
    }
75
76
    /**
77
     * Handle the incoming message.
78
     *
79
     * @param  \Ratchet\ConnectionInterface  $connection
80
     * @param  \Ratchet\RFC6455\Messaging\MessageInterface  $message
81
     * @return void
82
     */
83
    public function onMessage(ConnectionInterface $connection, MessageInterface $message)
84
    {
85
        if (! isset($connection->app)) {
0 ignored issues
show
Bug introduced by
Accessing app on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
86
            return;
87
        }
88
89
        Messages\PusherMessageFactory::createForMessage(
90
            $message, $connection, $this->channelManager
91
        )->respond();
92
93
        if ($connection->app->statisticsEnabled) {
94
            StatisticsCollector::webSocketMessage($connection->app->id);
95
        }
96
97
        WebSocketMessageReceived::dispatch(
98
            $connection->app->id,
99
            $connection->socketId,
0 ignored issues
show
Bug introduced by
Accessing socketId on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
100
            $message
101
        );
102
    }
103
104
    /**
105
     * Handle the websocket close.
106
     *
107
     * @param  \Ratchet\ConnectionInterface  $connection
108
     * @return void
109
     */
110
    public function onClose(ConnectionInterface $connection)
111
    {
112
        $this->channelManager
113
            ->unsubscribeFromAllChannels($connection)
114
            ->then(function (bool $unsubscribed) use ($connection) {
115
                if (isset($connection->app)) {
0 ignored issues
show
Bug introduced by
Accessing app on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
116
                    if ($connection->app->statisticsEnabled) {
117
                        StatisticsCollector::disconnection($connection->app->id);
118
                    }
119
120
                    $this->channelManager->unsubscribeFromApp($connection->app->id);
121
122
                    DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_DISCONNECTED, [
123
                        'socketId' => $connection->socketId,
0 ignored issues
show
Bug introduced by
Accessing socketId on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
124
                    ]);
125
126
                    ConnectionClosed::dispatch($connection->app->id, $connection->socketId);
127
                }
128
            });
129
    }
130
131
    /**
132
     * Handle the websocket errors.
133
     *
134
     * @param  \Ratchet\ConnectionInterface  $connection
135
     * @param  WebSocketException  $exception
0 ignored issues
show
Bug introduced by
The type BeyondCode\LaravelWebSoc...rver\WebSocketException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
136
     * @return void
137
     */
138
    public function onError(ConnectionInterface $connection, Exception $exception)
139
    {
140
        if ($exception instanceof Exceptions\WebSocketException) {
141
            $connection->send(json_encode(
142
                $exception->getPayload()
143
            ));
144
        }
145
    }
146
147
    /**
148
     * Check if the connection can be made for the
149
     * current server instance.
150
     *
151
     * @param  \Ratchet\ConnectionInterface  $connection
152
     * @return bool
153
     */
154
    protected function connectionCanBeMade(ConnectionInterface $connection): bool
155
    {
156
        return $this->channelManager->acceptsNewConnections();
0 ignored issues
show
Bug introduced by
The method acceptsNewConnections() does not exist on BeyondCode\LaravelWebSoc...ontracts\ChannelManager. Since it exists in all sub-types, consider adding an abstract or default implementation to BeyondCode\LaravelWebSoc...ontracts\ChannelManager. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

156
        return $this->channelManager->/** @scrutinizer ignore-call */ acceptsNewConnections();
Loading history...
157
    }
158
159
    /**
160
     * Verify the app key validity.
161
     *
162
     * @param  \Ratchet\ConnectionInterface  $connection
163
     * @return $this
164
     */
165
    protected function verifyAppKey(ConnectionInterface $connection)
166
    {
167
        $query = QueryParameters::create($connection->httpRequest);
0 ignored issues
show
Bug introduced by
Accessing httpRequest on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
168
169
        $appKey = $query->get('appKey');
170
171
        if (! $app = App::findByKey($appKey)) {
172
            throw new Exceptions\UnknownAppKey($appKey);
173
        }
174
175
        $connection->app = $app;
0 ignored issues
show
Bug introduced by
Accessing app on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
176
177
        return $this;
178
    }
179
180
    /**
181
     * Verify the origin.
182
     *
183
     * @param  \Ratchet\ConnectionInterface  $connection
184
     * @return $this
185
     */
186
    protected function verifyOrigin(ConnectionInterface $connection)
187
    {
188
        if (! $connection->app->allowedOrigins) {
0 ignored issues
show
Bug introduced by
Accessing app on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
189
            return $this;
190
        }
191
192
        $header = (string) ($connection->httpRequest->getHeader('Origin')[0] ?? null);
0 ignored issues
show
Bug introduced by
Accessing httpRequest on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
193
194
        $origin = parse_url($header, PHP_URL_HOST) ?: $header;
195
196
        if (! $header || ! in_array($origin, $connection->app->allowedOrigins)) {
197
            throw new Exceptions\OriginNotAllowed($connection->app->key);
198
        }
199
200
        return $this;
201
    }
202
203
    /**
204
     * Limit the connections count by the app.
205
     *
206
     * @param  \Ratchet\ConnectionInterface  $connection
207
     * @return $this
208
     */
209
    protected function limitConcurrentConnections(ConnectionInterface $connection)
210
    {
211
        if (! is_null($capacity = $connection->app->capacity)) {
0 ignored issues
show
Bug introduced by
Accessing app on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
212
            $this->channelManager
213
                ->getGlobalConnectionsCount($connection->app->id)
214
                ->then(function ($connectionsCount) use ($capacity, $connection) {
215
                    if ($connectionsCount >= $capacity) {
216
                        $exception = new Exceptions\ConnectionsOverCapacity;
217
218
                        $payload = json_encode($exception->getPayload());
219
220
                        tap($connection)->send($payload)->close();
221
                    }
222
                });
223
        }
224
225
        return $this;
226
    }
227
228
    /**
229
     * Create a socket id.
230
     *
231
     * @param  \Ratchet\ConnectionInterface  $connection
232
     * @return $this
233
     */
234
    protected function generateSocketId(ConnectionInterface $connection)
235
    {
236
        $socketId = sprintf('%d.%d', random_int(1, 1000000000), random_int(1, 1000000000));
237
238
        $connection->socketId = $socketId;
0 ignored issues
show
Bug introduced by
Accessing socketId on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
239
240
        return $this;
241
    }
242
243
    /**
244
     * Establish connection with the client.
245
     *
246
     * @param  \Ratchet\ConnectionInterface  $connection
247
     * @return $this
248
     */
249
    protected function establishConnection(ConnectionInterface $connection)
250
    {
251
        $connection->send(json_encode([
252
            'event' => 'pusher:connection_established',
253
            'data' => json_encode([
254
                'socket_id' => $connection->socketId,
0 ignored issues
show
Bug introduced by
Accessing socketId on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
255
                'activity_timeout' => 30,
256
            ]),
257
        ]));
258
259
        return $this;
260
    }
261
}
262