2018-11-21 11:13:40 +00:00
|
|
|
<?php
|
|
|
|
|
|
2018-11-27 15:21:31 +00:00
|
|
|
namespace BeyondCode\LaravelWebSockets\WebSockets\Channels;
|
2018-11-21 11:13:40 +00:00
|
|
|
|
2018-11-29 10:59:17 +00:00
|
|
|
use BeyondCode\LaravelWebSockets\Dashboard\DashboardLogger;
|
2019-03-25 22:37:14 +00:00
|
|
|
use BeyondCode\LaravelWebSockets\PubSub\ReplicationInterface;
|
2018-11-27 15:35:28 +00:00
|
|
|
use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\InvalidSignature;
|
2020-03-04 09:58:39 +00:00
|
|
|
use Illuminate\Support\Str;
|
|
|
|
|
use Ratchet\ConnectionInterface;
|
|
|
|
|
use stdClass;
|
2018-11-21 11:13:40 +00:00
|
|
|
|
|
|
|
|
class Channel
|
|
|
|
|
{
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* The channel name.
|
|
|
|
|
*
|
|
|
|
|
* @var string
|
|
|
|
|
*/
|
2018-12-01 11:26:08 +00:00
|
|
|
protected $channelName;
|
2018-11-21 11:13:40 +00:00
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* The replicator client.
|
|
|
|
|
*
|
|
|
|
|
* @var ReplicationInterface
|
|
|
|
|
*/
|
2020-08-13 16:27:24 +00:00
|
|
|
protected $replicator;
|
2019-07-28 18:50:10 +00:00
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* The connections that got subscribed.
|
|
|
|
|
*
|
|
|
|
|
* @var array
|
|
|
|
|
*/
|
2018-12-01 14:21:31 +00:00
|
|
|
protected $subscribedConnections = [];
|
2018-11-21 11:13:40 +00:00
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Create a new instance.
|
|
|
|
|
*
|
|
|
|
|
* @param string $channelName
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2018-12-01 11:26:08 +00:00
|
|
|
public function __construct(string $channelName)
|
2018-11-21 11:13:40 +00:00
|
|
|
{
|
2018-12-01 11:41:58 +00:00
|
|
|
$this->channelName = $channelName;
|
2020-08-13 16:27:24 +00:00
|
|
|
$this->replicator = app(ReplicationInterface::class);
|
2018-11-21 11:13:40 +00:00
|
|
|
}
|
|
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Get the channel name.
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
2019-03-29 19:33:46 +00:00
|
|
|
public function getChannelName(): string
|
|
|
|
|
{
|
|
|
|
|
return $this->channelName;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Check if the channel has connections.
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2018-11-21 21:36:50 +00:00
|
|
|
public function hasConnections(): bool
|
|
|
|
|
{
|
2018-12-01 14:21:31 +00:00
|
|
|
return count($this->subscribedConnections) > 0;
|
2018-11-21 21:36:50 +00:00
|
|
|
}
|
|
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Get all subscribed connections.
|
|
|
|
|
*
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
2018-12-01 14:21:31 +00:00
|
|
|
public function getSubscribedConnections(): array
|
2018-11-28 23:22:01 +00:00
|
|
|
{
|
2018-12-01 14:21:31 +00:00
|
|
|
return $this->subscribedConnections;
|
2018-11-28 23:22:01 +00:00
|
|
|
}
|
|
|
|
|
|
2019-03-29 19:33:46 +00:00
|
|
|
/**
|
2020-08-18 17:21:22 +00:00
|
|
|
* Check if the signature for the payload is valid.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @param \stdClass $payload
|
|
|
|
|
* @return void
|
2019-03-29 19:33:46 +00:00
|
|
|
* @throws InvalidSignature
|
|
|
|
|
*/
|
2018-11-22 09:54:51 +00:00
|
|
|
protected function verifySignature(ConnectionInterface $connection, stdClass $payload)
|
|
|
|
|
{
|
2018-12-01 11:41:58 +00:00
|
|
|
$signature = "{$connection->socketId}:{$this->channelName}";
|
2018-11-22 09:54:51 +00:00
|
|
|
|
|
|
|
|
if (isset($payload->channel_data)) {
|
|
|
|
|
$signature .= ":{$payload->channel_data}";
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-28 19:29:16 +00:00
|
|
|
if (! hash_equals(
|
2019-03-29 19:33:46 +00:00
|
|
|
hash_hmac('sha256', $signature, $connection->app->secret),
|
|
|
|
|
Str::after($payload->auth, ':'))
|
|
|
|
|
) {
|
2018-11-26 23:13:22 +00:00
|
|
|
throw new InvalidSignature();
|
2018-11-22 09:54:51 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-29 19:33:46 +00:00
|
|
|
/**
|
2020-08-18 17:21:22 +00:00
|
|
|
* Subscribe to the channel.
|
|
|
|
|
*
|
|
|
|
|
* @see https://pusher.com/docs/pusher_protocol#presence-channel-events
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @param \stdClass $payload
|
|
|
|
|
* @return void
|
2018-11-21 14:42:04 +00:00
|
|
|
*/
|
2018-11-22 09:54:51 +00:00
|
|
|
public function subscribe(ConnectionInterface $connection, stdClass $payload)
|
2018-11-21 11:13:40 +00:00
|
|
|
{
|
2018-11-21 23:03:13 +00:00
|
|
|
$this->saveConnection($connection);
|
2018-11-21 11:13:40 +00:00
|
|
|
|
2018-11-21 23:03:13 +00:00
|
|
|
$connection->send(json_encode([
|
2018-11-21 14:42:04 +00:00
|
|
|
'event' => 'pusher_internal:subscription_succeeded',
|
2018-12-04 21:22:33 +00:00
|
|
|
'channel' => $this->channelName,
|
2018-11-21 14:42:04 +00:00
|
|
|
]));
|
2020-08-14 05:42:17 +00:00
|
|
|
|
|
|
|
|
$this->replicator->subscribe($connection->app->id, $this->channelName);
|
2018-11-21 14:42:04 +00:00
|
|
|
}
|
|
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Unsubscribe connection from the channel.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2018-11-21 21:36:50 +00:00
|
|
|
public function unsubscribe(ConnectionInterface $connection)
|
|
|
|
|
{
|
2018-12-01 14:21:31 +00:00
|
|
|
unset($this->subscribedConnections[$connection->socketId]);
|
2018-11-25 21:24:31 +00:00
|
|
|
|
2020-08-13 16:27:24 +00:00
|
|
|
$this->replicator->unsubscribe($connection->app->id, $this->channelName);
|
2019-03-25 22:00:54 +00:00
|
|
|
|
2019-07-28 19:29:16 +00:00
|
|
|
if (! $this->hasConnections()) {
|
2020-08-17 18:06:51 +00:00
|
|
|
DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_VACATED, [
|
|
|
|
|
'socketId' => $connection->socketId,
|
|
|
|
|
'channel' => $this->channelName,
|
|
|
|
|
]);
|
2018-11-25 21:24:31 +00:00
|
|
|
}
|
2018-11-21 21:36:50 +00:00
|
|
|
}
|
|
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Store the connection to the subscribers list.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2018-11-21 23:14:22 +00:00
|
|
|
protected function saveConnection(ConnectionInterface $connection)
|
2018-11-21 14:42:04 +00:00
|
|
|
{
|
2018-11-26 21:29:24 +00:00
|
|
|
$hadConnectionsPreviously = $this->hasConnections();
|
2018-11-25 21:24:31 +00:00
|
|
|
|
2018-12-01 14:21:31 +00:00
|
|
|
$this->subscribedConnections[$connection->socketId] = $connection;
|
2018-11-25 21:24:31 +00:00
|
|
|
|
2019-07-28 19:29:16 +00:00
|
|
|
if (! $hadConnectionsPreviously) {
|
2020-08-17 18:06:51 +00:00
|
|
|
DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_OCCUPIED, [
|
|
|
|
|
'channel' => $this->channelName,
|
|
|
|
|
]);
|
2018-11-26 21:29:24 +00:00
|
|
|
}
|
|
|
|
|
|
2020-08-17 18:06:51 +00:00
|
|
|
DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_SUBSCRIBED, [
|
|
|
|
|
'socketId' => $connection->socketId,
|
|
|
|
|
'channel' => $this->channelName,
|
|
|
|
|
]);
|
2018-11-21 14:42:04 +00:00
|
|
|
}
|
|
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Broadcast a payload to the subscribed connections.
|
|
|
|
|
*
|
|
|
|
|
* @param \stdClass $payload
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2018-11-21 14:42:04 +00:00
|
|
|
public function broadcast($payload)
|
|
|
|
|
{
|
2018-12-01 14:21:31 +00:00
|
|
|
foreach ($this->subscribedConnections as $connection) {
|
2018-11-21 14:42:04 +00:00
|
|
|
$connection->send(json_encode($payload));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Broadcast the payload, but exclude the current connection.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @param \stdClass $payload
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2018-12-04 20:12:10 +00:00
|
|
|
public function broadcastToOthers(ConnectionInterface $connection, $payload)
|
|
|
|
|
{
|
2020-08-18 17:21:22 +00:00
|
|
|
$this->broadcastToEveryoneExcept(
|
|
|
|
|
$payload, $connection->socketId, $connection->app->id
|
|
|
|
|
);
|
2018-12-04 20:12:10 +00:00
|
|
|
}
|
|
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Broadcast the payload, but exclude a specific socket id.
|
|
|
|
|
*
|
|
|
|
|
* @param \stdClass $payload
|
|
|
|
|
* @param string|null $socketId
|
|
|
|
|
* @param mixed $appId
|
|
|
|
|
* @param bool $publish
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public function broadcastToEveryoneExcept($payload, ?string $socketId, $appId, bool $publish = true)
|
2018-11-21 14:42:04 +00:00
|
|
|
{
|
2019-07-29 21:39:34 +00:00
|
|
|
// Also broadcast via the other websocket server instances.
|
|
|
|
|
// This is set false in the Redis client because we don't want to cause a loop
|
|
|
|
|
// in this case. If this came from TriggerEventController, then we still want
|
|
|
|
|
// to publish to get the message out to other server instances.
|
|
|
|
|
if ($publish) {
|
2020-08-13 16:27:24 +00:00
|
|
|
$this->replicator->publish($appId, $this->channelName, $payload);
|
2019-07-29 21:39:34 +00:00
|
|
|
}
|
|
|
|
|
|
2019-03-29 14:22:36 +00:00
|
|
|
// Performance optimization, if we don't have a socket ID,
|
|
|
|
|
// then we avoid running the if condition in the foreach loop below
|
|
|
|
|
// by calling broadcast() instead.
|
2018-11-29 15:41:27 +00:00
|
|
|
if (is_null($socketId)) {
|
2019-03-24 04:56:47 +00:00
|
|
|
$this->broadcast($payload);
|
2019-03-25 22:37:14 +00:00
|
|
|
|
2019-03-24 04:56:47 +00:00
|
|
|
return;
|
2018-11-29 15:41:27 +00:00
|
|
|
}
|
|
|
|
|
|
2018-12-01 14:21:31 +00:00
|
|
|
foreach ($this->subscribedConnections as $connection) {
|
2018-11-29 15:41:27 +00:00
|
|
|
if ($connection->socketId !== $socketId) {
|
|
|
|
|
$connection->send(json_encode($payload));
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-11-21 11:13:40 +00:00
|
|
|
}
|
2018-11-22 21:02:36 +00:00
|
|
|
|
2020-08-18 17:21:22 +00:00
|
|
|
/**
|
|
|
|
|
* Convert the channel to array.
|
|
|
|
|
*
|
|
|
|
|
* @param mixed $appId
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
|
|
|
|
public function toArray($appId = null)
|
2018-11-22 21:02:36 +00:00
|
|
|
{
|
|
|
|
|
return [
|
2018-12-01 14:21:31 +00:00
|
|
|
'occupied' => count($this->subscribedConnections) > 0,
|
2018-12-04 21:22:33 +00:00
|
|
|
'subscription_count' => count($this->subscribedConnections),
|
2018-11-22 21:02:36 +00:00
|
|
|
];
|
|
|
|
|
}
|
2018-12-04 21:22:33 +00:00
|
|
|
}
|