laravel-websockets/src/Channels/PresenceChannel.php

160 lines
6.9 KiB
PHP
Raw Normal View History

2020-09-10 19:59:26 +00:00
<?php
namespace BeyondCode\LaravelWebSockets\Channels;
2020-09-11 05:34:21 +00:00
use BeyondCode\LaravelWebSockets\DashboardLogger;
use BeyondCode\LaravelWebSockets\Events\SubscribedToChannel;
use BeyondCode\LaravelWebSockets\Events\UnsubscribedFromChannel;
use BeyondCode\LaravelWebSockets\Helpers;
2020-09-10 19:59:26 +00:00
use BeyondCode\LaravelWebSockets\Server\Exceptions\InvalidSignature;
use Ratchet\ConnectionInterface;
use React\Promise\PromiseInterface;
2020-09-10 19:59:26 +00:00
use stdClass;
class PresenceChannel extends PrivateChannel
{
/**
* Subscribe to the channel.
*
* @see https://pusher.com/docs/pusher_protocol#presence-channel-events
2021-11-26 12:32:47 +00:00
*
2020-09-10 19:59:26 +00:00
* @param \Ratchet\ConnectionInterface $connection
* @param \stdClass $payload
2020-09-19 11:16:26 +00:00
* @return bool
2021-11-26 12:32:47 +00:00
*
2020-09-10 19:59:26 +00:00
* @throws InvalidSignature
*/
2020-09-19 11:16:26 +00:00
public function subscribe(ConnectionInterface $connection, stdClass $payload): bool
2020-09-10 19:59:26 +00:00
{
2020-09-11 05:34:21 +00:00
$this->verifySignature($connection, $payload);
$this->saveConnection($connection);
2020-09-10 19:59:26 +00:00
2020-09-19 11:16:26 +00:00
$user = json_decode($payload->channel_data);
2020-09-10 19:59:26 +00:00
$this->channelManager
2020-09-19 11:16:26 +00:00
->userJoinedPresenceChannel($connection, $user, $this->getName(), $payload)
2020-09-19 11:16:46 +00:00
->then(function () use ($connection) {
2020-09-19 11:16:26 +00:00
$this->channelManager
->getChannelMembers($connection->app->id, $this->getName())
->then(function ($users) use ($connection) {
$hash = [];
2020-09-11 05:46:02 +00:00
2020-09-19 11:16:26 +00:00
foreach ($users as $socketId => $user) {
$hash[$user->user_id] = $user->user_info ?? [];
}
2020-09-11 05:46:02 +00:00
2020-09-19 11:16:26 +00:00
$connection->send(json_encode([
'event' => 'pusher_internal:subscription_succeeded',
'channel' => $this->getName(),
'data' => json_encode([
'presence' => [
'ids' => collect($users)->map(function ($user) {
return (string) $user->user_id;
})->values(),
'hash' => $hash,
'count' => count($users),
],
]),
]));
});
})
->then(function () use ($connection, $user, $payload) {
2020-09-19 11:16:26 +00:00
// The `pusher_internal:member_added` event is triggered when a user joins a channel.
// It's quite possible that a user can have multiple connections to the same channel
// (for example by having multiple browser tabs open)
// and in this case the events will only be triggered when the first tab is opened.
$this->channelManager
->getMemberSockets($user->user_id, $connection->app->id, $this->getName())
->then(function ($sockets) use ($payload, $connection, $user) {
2020-09-19 11:16:26 +00:00
if (count($sockets) === 1) {
$memberAddedPayload = [
'event' => 'pusher_internal:member_added',
'channel' => $this->getName(),
'data' => $payload->channel_data,
];
2020-09-10 19:59:26 +00:00
2020-09-19 11:16:26 +00:00
$this->broadcastToEveryoneExcept(
(object) $memberAddedPayload, $connection->socketId,
$connection->app->id
);
SubscribedToChannel::dispatch(
$connection->app->id,
$connection->socketId,
$this->getName(),
$user
);
2020-09-19 11:16:26 +00:00
}
2020-09-11 05:34:21 +00:00
2020-09-19 11:16:26 +00:00
DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_SUBSCRIBED, [
'socketId' => $connection->socketId,
'channel' => $this->getName(),
'duplicate-connection' => count($sockets) > 1,
]);
});
2020-09-16 08:09:14 +00:00
});
2020-09-19 11:16:26 +00:00
return true;
2020-09-10 19:59:26 +00:00
}
/**
* Unsubscribe connection from the channel.
*
* @param \Ratchet\ConnectionInterface $connection
* @return PromiseInterface
2020-09-10 19:59:26 +00:00
*/
public function unsubscribe(ConnectionInterface $connection): PromiseInterface
2020-09-10 19:59:26 +00:00
{
2020-09-19 11:16:26 +00:00
$truth = parent::unsubscribe($connection);
2020-09-10 19:59:26 +00:00
return $this->channelManager
->getChannelMember($connection, $this->getName())
->then(function ($user) {
return @json_decode($user);
})
->then(function ($user) use ($connection) {
if (! $user) {
return Helpers::createFulfilledPromise(true);
}
return $this->channelManager
->userLeftPresenceChannel($connection, $user, $this->getName())
->then(function () use ($connection, $user) {
// The `pusher_internal:member_removed` is triggered when a user leaves a channel.
// It's quite possible that a user can have multiple connections to the same channel
// (for example by having multiple browser tabs open)
// and in this case the events will only be triggered when the last one is closed.
return $this->channelManager
->getMemberSockets($user->user_id, $connection->app->id, $this->getName())
->then(function ($sockets) use ($connection, $user) {
if (count($sockets) === 0) {
$memberRemovedPayload = [
'event' => 'pusher_internal:member_removed',
'channel' => $this->getName(),
'data' => json_encode([
'user_id' => $user->user_id,
]),
];
$this->broadcastToEveryoneExcept(
(object) $memberRemovedPayload, $connection->socketId,
$connection->app->id
);
UnsubscribedFromChannel::dispatch(
$connection->app->id,
$connection->socketId,
$this->getName(),
$user
);
}
});
});
})
->then(function () use ($truth) {
return $truth;
});
2020-09-10 19:59:26 +00:00
}
}