Channel   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 247
Duplicated Lines 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
eloc 57
c 4
b 1
f 0
dl 0
loc 247
rs 10
wmc 21

14 Methods

Rating   Name   Duplication   Size   Complexity  
A broadcastToEveryoneExcept() 0 18 4
A getName() 0 3 1
A hasConnection() 0 3 1
A saveConnection() 0 3 1
A broadcast() 0 13 2
A unsubscribe() 0 15 2
A broadcastLocally() 0 3 1
A subscribe() 0 21 1
A getConnection() 0 3 1
A getConnections() 0 3 1
A broadcastLocallyToEveryoneExcept() 0 4 1
A __construct() 0 4 1
A hasConnections() 0 3 1
A verifySignature() 0 13 3
1
<?php
2
3
namespace BeyondCode\LaravelWebSockets\Channels;
4
5
use BeyondCode\LaravelWebSockets\Contracts\ChannelManager;
6
use BeyondCode\LaravelWebSockets\DashboardLogger;
7
use BeyondCode\LaravelWebSockets\Events\SubscribedToChannel;
8
use BeyondCode\LaravelWebSockets\Events\UnsubscribedFromChannel;
9
use BeyondCode\LaravelWebSockets\Server\Exceptions\InvalidSignature;
10
use Illuminate\Support\Str;
11
use Ratchet\ConnectionInterface;
12
use stdClass;
13
14
class Channel
15
{
16
    /**
17
     * The channel name.
18
     *
19
     * @var string
20
     */
21
    protected $name;
22
23
    /**
24
     * The connections that got subscribed to this channel.
25
     *
26
     * @var array
27
     */
28
    protected $connections = [];
29
30
    /**
31
     * Create a new instance.
32
     *
33
     * @param  string  $name
34
     * @return void
35
     */
36
    public function __construct(string $name)
37
    {
38
        $this->name = $name;
39
        $this->channelManager = app(ChannelManager::class);
0 ignored issues
show
Bug Best Practice introduced by
The property channelManager does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
40
    }
41
42
    /**
43
     * Get channel name.
44
     *
45
     * @return string
46
     */
47
    public function getName()
48
    {
49
        return $this->name;
50
    }
51
52
    /**
53
     * Get the list of subscribed connections.
54
     *
55
     * @return array
56
     */
57
    public function getConnections()
58
    {
59
        return $this->connections;
60
    }
61
62
    /**
63
     * Get connection by socketId.
64
     *
65
     * @param  string socketId
0 ignored issues
show
Bug introduced by
The type BeyondCode\LaravelWebSockets\Channels\socketId 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...
66
     * @return ?ConnectionInterface
67
     */
68
    public function getConnection(string $socketId): ?ConnectionInterface
69
    {
70
        return $this->connections[$socketId] ?? null;
71
    }
72
73
    /**
74
     * Check if the channel has connections.
75
     *
76
     * @return bool
77
     */
78
    public function hasConnections(): bool
79
    {
80
        return count($this->getConnections()) > 0;
81
    }
82
83
    /**
84
     * Add a new connection to the channel.
85
     *
86
     * @see    https://pusher.com/docs/pusher_protocol#presence-channel-events
87
     *
88
     * @param  \Ratchet\ConnectionInterface  $connection
89
     * @param  \stdClass  $payload
90
     * @return bool
91
     */
92
    public function subscribe(ConnectionInterface $connection, stdClass $payload): bool
93
    {
94
        $this->saveConnection($connection);
95
96
        $connection->send(json_encode([
97
            'event' => 'pusher_internal:subscription_succeeded',
98
            'channel' => $this->getName(),
99
        ]));
100
101
        DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_SUBSCRIBED, [
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...
102
            '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...
103
            'channel' => $this->getName(),
104
        ]);
105
106
        SubscribedToChannel::dispatch(
107
            $connection->app->id,
108
            $connection->socketId,
109
            $this->getName(),
110
        );
111
112
        return true;
113
    }
114
115
    /**
116
     * Unsubscribe connection from the channel.
117
     *
118
     * @param  \Ratchet\ConnectionInterface  $connection
119
     * @return bool
120
     */
121
    public function unsubscribe(ConnectionInterface $connection): bool
122
    {
123
        if (! $this->hasConnection($connection)) {
124
            return false;
125
        }
126
127
        unset($this->connections[$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...
128
129
        UnsubscribedFromChannel::dispatch(
130
            $connection->app->id,
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...
131
            $connection->socketId,
132
            $this->getName()
133
        );
134
135
        return true;
136
    }
137
138
    /**
139
     * Check if the given connection exists.
140
     *
141
     * @param  \Ratchet\ConnectionInterface  $connection
142
     * @return bool
143
     */
144
    public function hasConnection(ConnectionInterface $connection): bool
145
    {
146
        return isset($this->connections[$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...
147
    }
148
149
    /**
150
     * Store the connection to the subscribers list.
151
     *
152
     * @param  \Ratchet\ConnectionInterface  $connection
153
     * @return void
154
     */
155
    public function saveConnection(ConnectionInterface $connection)
156
    {
157
        $this->connections[$connection->socketId] = $connection;
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...
158
    }
159
160
    /**
161
     * Broadcast a payload to the subscribed connections.
162
     *
163
     * @param  string|int  $appId
164
     * @param  \stdClass  $payload
165
     * @param  bool  $replicate
166
     * @return bool
167
     */
168
    public function broadcast($appId, stdClass $payload, bool $replicate = true): bool
169
    {
170
        collect($this->getConnections())
171
            ->each(function ($connection) use ($payload) {
172
                $connection->send(json_encode($payload));
173
                $this->channelManager->connectionPonged($connection);
174
            });
175
176
        if ($replicate) {
177
            $this->channelManager->broadcastAcrossServers($appId, null, $this->getName(), $payload);
178
        }
179
180
        return true;
181
    }
182
183
    /**
184
     * Broadcast a payload to the locally-subscribed connections.
185
     *
186
     * @param  string|int  $appId
187
     * @param  \stdClass  $payload
188
     * @return bool
189
     */
190
    public function broadcastLocally($appId, stdClass $payload): bool
191
    {
192
        return $this->broadcast($appId, $payload, false);
193
    }
194
195
    /**
196
     * Broadcast the payload, but exclude a specific socket id.
197
     *
198
     * @param  \stdClass  $payload
199
     * @param  string|null  $socketId
200
     * @param  string|int  $appId
201
     * @param  bool  $replicate
202
     * @return bool
203
     */
204
    public function broadcastToEveryoneExcept(stdClass $payload, ?string $socketId, $appId, bool $replicate = true)
205
    {
206
        if ($replicate) {
207
            $this->channelManager->broadcastAcrossServers($appId, $socketId, $this->getName(), $payload);
208
        }
209
210
        if (is_null($socketId)) {
211
            return $this->broadcast($appId, $payload, false);
212
        }
213
214
        collect($this->getConnections())->each(function (ConnectionInterface $connection) use ($socketId, $payload) {
215
            if ($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...
216
                $connection->send(json_encode($payload));
217
                $this->channelManager->connectionPonged($connection);
218
            }
219
        });
220
221
        return true;
222
    }
223
224
    /**
225
     * Broadcast the payload, but exclude a specific socket id.
226
     *
227
     * @param  \stdClass  $payload
228
     * @param  string|null  $socketId
229
     * @param  string|int  $appId
230
     * @return bool
231
     */
232
    public function broadcastLocallyToEveryoneExcept(stdClass $payload, ?string $socketId, $appId)
233
    {
234
        return $this->broadcastToEveryoneExcept(
235
            $payload, $socketId, $appId, false
236
        );
237
    }
238
239
    /**
240
     * Check if the signature for the payload is valid.
241
     *
242
     * @param  \Ratchet\ConnectionInterface  $connection
243
     * @param  \stdClass  $payload
244
     * @return void
245
     *
246
     * @throws InvalidSignature
247
     */
248
    protected function verifySignature(ConnectionInterface $connection, stdClass $payload)
249
    {
250
        $signature = "{$connection->socketId}:{$this->getName()}";
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...
251
252
        if (isset($payload->channel_data)) {
253
            $signature .= ":{$payload->channel_data}";
254
        }
255
256
        if (! hash_equals(
257
            hash_hmac('sha256', $signature, $connection->app->secret),
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...
258
            Str::after($payload->auth, ':'))
259
        ) {
260
            throw new InvalidSignature;
261
        }
262
    }
263
}
264