Completed
Push — master ( 331a12...805fd5 )
by Alexandru
18s queued 10s
created

PresenceChannel::getHash()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
namespace BeyondCode\LaravelWebSockets\WebSockets\Channels;
4
5
use Ratchet\ConnectionInterface;
6
use stdClass;
7
8
/**
9
 * @link https://pusher.com/docs/pusher_protocol#presence-channel-events
10
 */
11
class PresenceChannel extends Channel
12
{
13
    /**
14
     * List of users in the channel keyed by their user ID with their info as value.
15
     *
16
     * @var array<string, array>
17
     */
18
    protected $users = [];
19
20
    /**
21
     * List of sockets keyed by their ID with the value pointing to a user ID.
22
     *
23
     * @var array<string, string>
24
     */
25
    protected $sockets = [];
26
27
    public function subscribe(ConnectionInterface $connection, stdClass $payload)
28
    {
29
        $this->verifySignature($connection, $payload);
30
31
        $this->saveConnection($connection);
32
33
        $channelData = json_decode($payload->channel_data, true);
34
35
        // The ID of the user connecting
36
        $userId = (string) $channelData['user_id'];
37
38
        // Check if the user was already connected to the channel before storing the connection in the state
39
        $userFirstConnection = ! isset($this->users[$userId]);
40
41
        // Add or replace the user info in the state
42
        $this->users[$userId] = $channelData['user_info'] ?? [];
43
44
        // Add the socket ID to user ID map in the state
45
        $this->sockets[$connection->socketId] = $userId;
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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
46
47
        // Send the success event
48
        $connection->send(json_encode([
49
            'event' => 'pusher_internal:subscription_succeeded',
50
            'channel' => $this->channelName,
51
            'data' => json_encode($this->getChannelData()),
52
        ]));
53
54
        // The `pusher_internal:member_added` event is triggered when a user joins a channel.
55
        // It's quite possible that a user can have multiple connections to the same channel
56
        // (for example by having multiple browser tabs open)
57
        // and in this case the events will only be triggered when the first tab is opened.
58
        if ($userFirstConnection) {
59
            $this->broadcastToOthers($connection, [
60
                'event' => 'pusher_internal:member_added',
61
                'channel' => $this->channelName,
62
                'data' => json_encode($channelData),
63
            ]);
64
        }
65
    }
66
67
    public function unsubscribe(ConnectionInterface $connection)
68
    {
69
        parent::unsubscribe($connection);
70
71
        if (! isset($this->sockets[$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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
72
            return;
73
        }
74
75
        // Find the user ID belonging to this socket
76
        $userId = $this->sockets[$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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
77
78
        // Remove the socket from the state
79
        unset($this->sockets[$connection->socketId]);
80
81
        // Test if the user still has open sockets to this channel
82
        $userHasOpenConnections = (array_flip($this->sockets)[$userId] ?? null) !== null;
83
84
        // The `pusher_internal:member_removed` is triggered when a user leaves a channel.
85
        // It's quite possible that a user can have multiple connections to the same channel
86
        // (for example by having multiple browser tabs open)
87
        // and in this case the events will only be triggered when the last one is closed.
88
        if (! $userHasOpenConnections) {
89
            $this->broadcastToOthers($connection, [
90
                'event' => 'pusher_internal:member_removed',
91
                'channel' => $this->channelName,
92
                'data' => json_encode([
93
                    'user_id' => $userId,
94
                ]),
95
            ]);
96
97
            // Remove the user info from the state
98
            unset($this->users[$userId]);
99
        }
100
    }
101
102
    protected function getChannelData(): array
103
    {
104
        return [
105
            'presence' => [
106
                'ids' => array_keys($this->users),
107
                'hash' => $this->users,
108
                'count' => count($this->users),
109
            ],
110
        ];
111
    }
112
113
    public function getUsers(): array
114
    {
115
        return $this->users;
116
    }
117
118
    public function toArray(): array
119
    {
120
        return array_merge(parent::toArray(), [
121
            'user_count' => count($this->users),
122
        ]);
123
    }
124
}
125