2020-09-10 19:59:26 +00:00
|
|
|
<?php
|
|
|
|
|
|
2025-01-16 07:54:02 +00:00
|
|
|
namespace BlaxSoftware\LaravelWebSockets\ChannelManagers;
|
|
|
|
|
|
|
|
|
|
use BlaxSoftware\LaravelWebSockets\Cache\ArrayLock;
|
|
|
|
|
use BlaxSoftware\LaravelWebSockets\Channels\Channel;
|
2025-09-18 15:43:40 +00:00
|
|
|
use BlaxSoftware\LaravelWebSockets\Channels\OpenPresenceChannel;
|
2025-01-16 07:54:02 +00:00
|
|
|
use BlaxSoftware\LaravelWebSockets\Channels\PresenceChannel;
|
|
|
|
|
use BlaxSoftware\LaravelWebSockets\Channels\PrivateChannel;
|
|
|
|
|
use BlaxSoftware\LaravelWebSockets\Contracts\ChannelManager;
|
|
|
|
|
use BlaxSoftware\LaravelWebSockets\Helpers;
|
2020-09-19 15:38:08 +00:00
|
|
|
use Carbon\Carbon;
|
|
|
|
|
use Illuminate\Cache\ArrayStore;
|
2020-09-10 19:59:49 +00:00
|
|
|
use Illuminate\Support\Str;
|
|
|
|
|
use Ratchet\ConnectionInterface;
|
|
|
|
|
use React\EventLoop\LoopInterface;
|
2020-09-10 19:59:26 +00:00
|
|
|
use React\Promise\PromiseInterface;
|
|
|
|
|
use stdClass;
|
|
|
|
|
|
2024-02-07 17:30:54 +00:00
|
|
|
use function React\Promise\all;
|
|
|
|
|
|
2020-09-10 19:59:26 +00:00
|
|
|
class LocalChannelManager implements ChannelManager
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* The list of stored channels.
|
|
|
|
|
*
|
|
|
|
|
* @var array
|
|
|
|
|
*/
|
|
|
|
|
protected $channels = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The list of users that joined the presence channel.
|
|
|
|
|
*
|
|
|
|
|
* @var array
|
|
|
|
|
*/
|
|
|
|
|
protected $users = [];
|
|
|
|
|
|
2020-09-16 08:02:58 +00:00
|
|
|
/**
|
|
|
|
|
* The list of users by socket and their attached id.
|
|
|
|
|
*
|
|
|
|
|
* @var array
|
|
|
|
|
*/
|
|
|
|
|
protected $userSockets = [];
|
|
|
|
|
|
2020-09-12 14:45:07 +00:00
|
|
|
/**
|
|
|
|
|
* Wether the current instance accepts new connections.
|
|
|
|
|
*
|
|
|
|
|
* @var bool
|
|
|
|
|
*/
|
|
|
|
|
protected $acceptsNewConnections = true;
|
|
|
|
|
|
2026-03-21 11:56:50 +00:00
|
|
|
/**
|
|
|
|
|
* The event loop instance.
|
|
|
|
|
*
|
|
|
|
|
* @var \React\EventLoop\LoopInterface
|
|
|
|
|
*/
|
|
|
|
|
public $loop;
|
|
|
|
|
|
2020-09-19 15:46:13 +00:00
|
|
|
/**
|
|
|
|
|
* The ArrayStore instance of locks.
|
|
|
|
|
*
|
|
|
|
|
* @var \Illuminate\Cache\ArrayStore
|
|
|
|
|
*/
|
|
|
|
|
protected $store;
|
|
|
|
|
|
2020-12-09 09:37:03 +00:00
|
|
|
/**
|
|
|
|
|
* The unique server identifier.
|
|
|
|
|
*
|
|
|
|
|
* @var string
|
|
|
|
|
*/
|
|
|
|
|
protected $serverId;
|
|
|
|
|
|
2020-09-19 15:38:08 +00:00
|
|
|
/**
|
|
|
|
|
* The lock name to use on Array to avoid multiple
|
|
|
|
|
* actions that might lead to multiple processings.
|
|
|
|
|
*
|
|
|
|
|
* @var string
|
|
|
|
|
*/
|
|
|
|
|
protected static $lockName = 'laravel-websockets:channel-manager:lock';
|
|
|
|
|
|
2020-09-10 19:59:26 +00:00
|
|
|
/**
|
|
|
|
|
* Create a new channel manager instance.
|
|
|
|
|
*
|
|
|
|
|
* @param LoopInterface $loop
|
|
|
|
|
* @param string|null $factoryClass
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public function __construct(LoopInterface $loop, $factoryClass = null)
|
|
|
|
|
{
|
2026-03-21 11:56:50 +00:00
|
|
|
$this->loop = $loop;
|
2020-09-19 15:46:13 +00:00
|
|
|
$this->store = new ArrayStore;
|
2020-12-09 09:37:03 +00:00
|
|
|
$this->serverId = Str::uuid()->toString();
|
2020-09-10 19:59:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Find the channel by app & name.
|
|
|
|
|
*
|
|
|
|
|
* @param string|int $appId
|
|
|
|
|
* @param string $channel
|
2025-01-16 07:54:02 +00:00
|
|
|
* @return null|BlaxSoftware\LaravelWebSockets\Channels\Channel
|
2020-09-10 19:59:26 +00:00
|
|
|
*/
|
|
|
|
|
public function find($appId, string $channel)
|
|
|
|
|
{
|
|
|
|
|
return $this->channels[$appId][$channel] ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Find a channel by app & name or create one.
|
|
|
|
|
*
|
|
|
|
|
* @param string|int $appId
|
|
|
|
|
* @param string $channel
|
2025-01-16 07:54:02 +00:00
|
|
|
* @return BlaxSoftware\LaravelWebSockets\Channels\Channel
|
2020-09-10 19:59:26 +00:00
|
|
|
*/
|
|
|
|
|
public function findOrCreate($appId, string $channel)
|
|
|
|
|
{
|
|
|
|
|
if (! $channelInstance = $this->find($appId, $channel)) {
|
|
|
|
|
$class = $this->getChannelClassName($channel);
|
|
|
|
|
|
|
|
|
|
$this->channels[$appId][$channel] = new $class($channel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->channels[$appId][$channel];
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-12 14:45:07 +00:00
|
|
|
/**
|
|
|
|
|
* Get the local connections, regardless of the channel
|
|
|
|
|
* they are connected to.
|
|
|
|
|
*
|
|
|
|
|
* @return \React\Promise\PromiseInterface
|
|
|
|
|
*/
|
|
|
|
|
public function getLocalConnections(): PromiseInterface
|
|
|
|
|
{
|
2020-12-07 21:30:12 +00:00
|
|
|
$connections = collect($this->channels)
|
|
|
|
|
->map(function ($channelsWithConnections, $appId) {
|
|
|
|
|
return collect($channelsWithConnections)->values();
|
|
|
|
|
})
|
|
|
|
|
->values()->collapse()
|
|
|
|
|
->map(function ($channel) {
|
|
|
|
|
return collect($channel->getConnections());
|
|
|
|
|
})
|
|
|
|
|
->values()->collapse()
|
|
|
|
|
->toArray();
|
2020-09-12 14:45:07 +00:00
|
|
|
|
2020-09-19 11:16:26 +00:00
|
|
|
return Helpers::createFulfilledPromise($connections);
|
2020-09-12 14:45:07 +00:00
|
|
|
}
|
|
|
|
|
|
2020-09-10 19:59:26 +00:00
|
|
|
/**
|
|
|
|
|
* Get all channels for a specific app
|
|
|
|
|
* for the current instance.
|
|
|
|
|
*
|
|
|
|
|
* @param string|int $appId
|
|
|
|
|
* @return \React\Promise\PromiseInterface[array]
|
|
|
|
|
*/
|
|
|
|
|
public function getLocalChannels($appId): PromiseInterface
|
|
|
|
|
{
|
2020-09-19 11:16:26 +00:00
|
|
|
return Helpers::createFulfilledPromise(
|
2020-09-10 19:59:26 +00:00
|
|
|
$this->channels[$appId] ?? []
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get all channels for a specific app
|
|
|
|
|
* across multiple servers.
|
|
|
|
|
*
|
|
|
|
|
* @param string|int $appId
|
|
|
|
|
* @return \React\Promise\PromiseInterface[array]
|
|
|
|
|
*/
|
|
|
|
|
public function getGlobalChannels($appId): PromiseInterface
|
|
|
|
|
{
|
|
|
|
|
return $this->getLocalChannels($appId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Remove connection from all channels.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
2020-09-19 11:16:26 +00:00
|
|
|
* @return PromiseInterface[bool]
|
2020-09-10 19:59:26 +00:00
|
|
|
*/
|
2020-09-19 11:16:26 +00:00
|
|
|
public function unsubscribeFromAllChannels(ConnectionInterface $connection): PromiseInterface
|
2020-09-10 19:59:26 +00:00
|
|
|
{
|
|
|
|
|
if (! isset($connection->app)) {
|
2020-11-20 13:11:03 +00:00
|
|
|
return Helpers::createFulfilledPromise(false);
|
2020-09-10 19:59:26 +00:00
|
|
|
}
|
|
|
|
|
|
2020-12-07 21:30:36 +00:00
|
|
|
$this->getLocalChannels($connection->app->id)
|
|
|
|
|
->then(function ($channels) use ($connection) {
|
2021-03-04 07:45:18 +00:00
|
|
|
collect($channels)
|
2021-03-04 08:05:48 +00:00
|
|
|
->each(function (Channel $channel) use ($connection) {
|
|
|
|
|
$channel->unsubscribe($connection);
|
|
|
|
|
});
|
2020-12-07 21:30:36 +00:00
|
|
|
|
|
|
|
|
collect($channels)
|
2021-03-30 15:11:58 +00:00
|
|
|
->reject(function ($channel) {
|
|
|
|
|
return $channel->hasConnections();
|
|
|
|
|
})
|
2020-12-07 21:30:36 +00:00
|
|
|
->each(function (Channel $channel, string $channelName) use ($connection) {
|
|
|
|
|
unset($this->channels[$connection->app->id][$channelName]);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$this->getLocalChannels($connection->app->id)
|
|
|
|
|
->then(function ($channels) use ($connection) {
|
|
|
|
|
if (count($channels) === 0) {
|
|
|
|
|
unset($this->channels[$connection->app->id]);
|
|
|
|
|
}
|
|
|
|
|
});
|
2020-09-19 11:16:26 +00:00
|
|
|
|
|
|
|
|
return Helpers::createFulfilledPromise(true);
|
2020-09-10 19:59:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Subscribe the connection to a specific channel.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @param string $channelName
|
|
|
|
|
* @param stdClass $payload
|
2020-09-19 11:16:26 +00:00
|
|
|
* @return PromiseInterface[bool]
|
2020-09-10 19:59:26 +00:00
|
|
|
*/
|
2020-09-19 11:16:26 +00:00
|
|
|
public function subscribeToChannel(ConnectionInterface $connection, string $channelName, stdClass $payload): PromiseInterface
|
2020-09-10 19:59:26 +00:00
|
|
|
{
|
|
|
|
|
$channel = $this->findOrCreate($connection->app->id, $channelName);
|
|
|
|
|
|
2020-09-19 11:16:26 +00:00
|
|
|
return Helpers::createFulfilledPromise(
|
|
|
|
|
$channel->subscribe($connection, $payload)
|
|
|
|
|
);
|
2020-09-10 19:59:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Unsubscribe the connection from the channel.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @param string $channelName
|
|
|
|
|
* @param stdClass $payload
|
2020-09-19 11:16:26 +00:00
|
|
|
* @return PromiseInterface[bool]
|
2020-09-10 19:59:26 +00:00
|
|
|
*/
|
2020-09-19 11:16:26 +00:00
|
|
|
public function unsubscribeFromChannel(ConnectionInterface $connection, string $channelName, stdClass $payload): PromiseInterface
|
2020-09-10 19:59:26 +00:00
|
|
|
{
|
|
|
|
|
$channel = $this->findOrCreate($connection->app->id, $channelName);
|
|
|
|
|
|
2022-10-06 11:46:54 +00:00
|
|
|
return $channel->unsubscribe($connection, $payload);
|
2020-09-10 19:59:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-09-19 11:16:26 +00:00
|
|
|
* Subscribe the connection to a specific channel, returning
|
|
|
|
|
* a promise containing the amount of connections.
|
2020-09-10 19:59:26 +00:00
|
|
|
*
|
|
|
|
|
* @param string|int $appId
|
2020-09-19 11:16:26 +00:00
|
|
|
* @return PromiseInterface[int]
|
2020-09-10 19:59:26 +00:00
|
|
|
*/
|
2020-09-19 11:16:26 +00:00
|
|
|
public function subscribeToApp($appId): PromiseInterface
|
2020-09-10 19:59:26 +00:00
|
|
|
{
|
2020-09-19 11:16:26 +00:00
|
|
|
return Helpers::createFulfilledPromise(0);
|
2020-09-10 19:59:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-09-19 11:16:26 +00:00
|
|
|
* Unsubscribe the connection from the channel, returning
|
|
|
|
|
* a promise containing the amount of connections after decrement.
|
2020-09-10 19:59:26 +00:00
|
|
|
*
|
|
|
|
|
* @param string|int $appId
|
2020-09-19 11:16:26 +00:00
|
|
|
* @return PromiseInterface[int]
|
2020-09-10 19:59:26 +00:00
|
|
|
*/
|
2020-09-19 11:16:26 +00:00
|
|
|
public function unsubscribeFromApp($appId): PromiseInterface
|
2020-09-10 19:59:26 +00:00
|
|
|
{
|
2020-09-19 11:16:26 +00:00
|
|
|
return Helpers::createFulfilledPromise(0);
|
2020-09-10 19:59:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the connections count on the app
|
|
|
|
|
* for the current server instance.
|
|
|
|
|
*
|
|
|
|
|
* @param string|int $appId
|
|
|
|
|
* @param string|null $channelName
|
2020-09-19 11:16:26 +00:00
|
|
|
* @return PromiseInterface[int]
|
2020-09-10 19:59:26 +00:00
|
|
|
*/
|
|
|
|
|
public function getLocalConnectionsCount($appId, string $channelName = null): PromiseInterface
|
|
|
|
|
{
|
2020-12-07 21:30:36 +00:00
|
|
|
return $this->getLocalChannels($appId)
|
|
|
|
|
->then(function ($channels) use ($channelName) {
|
|
|
|
|
return collect($channels)->when(! is_null($channelName), function ($collection) use ($channelName) {
|
|
|
|
|
return $collection->filter(function (Channel $channel) use ($channelName) {
|
|
|
|
|
return $channel->getName() === $channelName;
|
|
|
|
|
});
|
|
|
|
|
})
|
2022-01-05 14:59:37 +00:00
|
|
|
->flatMap(function (Channel $channel) {
|
|
|
|
|
return collect($channel->getConnections())->pluck('socketId');
|
|
|
|
|
})
|
|
|
|
|
->unique()->count();
|
2020-12-07 21:30:36 +00:00
|
|
|
});
|
2020-09-10 19:59:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the connections count
|
|
|
|
|
* across multiple servers.
|
|
|
|
|
*
|
|
|
|
|
* @param string|int $appId
|
|
|
|
|
* @param string|null $channelName
|
2020-09-19 11:16:26 +00:00
|
|
|
* @return PromiseInterface[int]
|
2020-09-10 19:59:26 +00:00
|
|
|
*/
|
|
|
|
|
public function getGlobalConnectionsCount($appId, string $channelName = null): PromiseInterface
|
|
|
|
|
{
|
|
|
|
|
return $this->getLocalConnectionsCount($appId, $channelName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Broadcast the message across multiple servers.
|
|
|
|
|
*
|
|
|
|
|
* @param string|int $appId
|
2020-09-18 08:57:10 +00:00
|
|
|
* @param string|null $socketId
|
2020-09-10 19:59:26 +00:00
|
|
|
* @param string $channel
|
|
|
|
|
* @param stdClass $payload
|
2020-09-18 08:57:10 +00:00
|
|
|
* @param string|null $serverId
|
2020-09-19 11:16:26 +00:00
|
|
|
* @return PromiseInterface[bool]
|
2020-09-10 19:59:26 +00:00
|
|
|
*/
|
2020-09-19 11:16:26 +00:00
|
|
|
public function broadcastAcrossServers($appId, ?string $socketId, string $channel, stdClass $payload, string $serverId = null): PromiseInterface
|
2020-09-10 19:59:26 +00:00
|
|
|
{
|
2020-09-19 11:16:26 +00:00
|
|
|
return Helpers::createFulfilledPromise(true);
|
2020-09-10 19:59:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle the user when it joined a presence channel.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @param stdClass $user
|
|
|
|
|
* @param string $channel
|
|
|
|
|
* @param stdClass $payload
|
2020-09-19 11:16:26 +00:00
|
|
|
* @return PromiseInterface[bool]
|
2020-09-10 19:59:26 +00:00
|
|
|
*/
|
2020-09-19 11:16:26 +00:00
|
|
|
public function userJoinedPresenceChannel(ConnectionInterface $connection, stdClass $user, string $channel, stdClass $payload): PromiseInterface
|
2020-09-10 19:59:26 +00:00
|
|
|
{
|
|
|
|
|
$this->users["{$connection->app->id}:{$channel}"][$connection->socketId] = json_encode($user);
|
2020-09-16 08:02:58 +00:00
|
|
|
$this->userSockets["{$connection->app->id}:{$channel}:{$user->user_id}"][] = $connection->socketId;
|
2020-09-19 11:16:26 +00:00
|
|
|
|
|
|
|
|
return Helpers::createFulfilledPromise(true);
|
2020-09-10 19:59:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle the user when it left a presence channel.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @param stdClass $user
|
|
|
|
|
* @param string $channel
|
|
|
|
|
* @param stdClass $payload
|
2020-09-19 11:16:26 +00:00
|
|
|
* @return PromiseInterface[bool]
|
2020-09-10 19:59:26 +00:00
|
|
|
*/
|
2020-09-19 11:16:26 +00:00
|
|
|
public function userLeftPresenceChannel(ConnectionInterface $connection, stdClass $user, string $channel): PromiseInterface
|
2020-09-10 19:59:26 +00:00
|
|
|
{
|
|
|
|
|
unset($this->users["{$connection->app->id}:{$channel}"][$connection->socketId]);
|
2020-09-16 08:02:58 +00:00
|
|
|
|
|
|
|
|
$deletableSocketKey = array_search(
|
|
|
|
|
$connection->socketId,
|
|
|
|
|
$this->userSockets["{$connection->app->id}:{$channel}:{$user->user_id}"]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($deletableSocketKey !== false) {
|
|
|
|
|
unset($this->userSockets["{$connection->app->id}:{$channel}:{$user->user_id}"][$deletableSocketKey]);
|
|
|
|
|
|
|
|
|
|
if (count($this->userSockets["{$connection->app->id}:{$channel}:{$user->user_id}"]) === 0) {
|
|
|
|
|
unset($this->userSockets["{$connection->app->id}:{$channel}:{$user->user_id}"]);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-09-19 11:16:26 +00:00
|
|
|
|
|
|
|
|
return Helpers::createFulfilledPromise(true);
|
2020-09-10 19:59:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the presence channel members.
|
|
|
|
|
*
|
|
|
|
|
* @param string|int $appId
|
|
|
|
|
* @param string $channel
|
|
|
|
|
* @return \React\Promise\PromiseInterface
|
|
|
|
|
*/
|
|
|
|
|
public function getChannelMembers($appId, string $channel): PromiseInterface
|
|
|
|
|
{
|
|
|
|
|
$members = $this->users["{$appId}:{$channel}"] ?? [];
|
|
|
|
|
|
|
|
|
|
$members = collect($members)->map(function ($user) {
|
|
|
|
|
return json_decode($user);
|
2020-09-17 08:30:36 +00:00
|
|
|
})->unique('user_id')->toArray();
|
2020-09-10 19:59:26 +00:00
|
|
|
|
2020-09-19 11:16:26 +00:00
|
|
|
return Helpers::createFulfilledPromise($members);
|
2020-09-10 19:59:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a member from a presence channel based on connection.
|
|
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
|
|
|
|
* @param string $channel
|
|
|
|
|
* @return \React\Promise\PromiseInterface
|
|
|
|
|
*/
|
|
|
|
|
public function getChannelMember(ConnectionInterface $connection, string $channel): PromiseInterface
|
|
|
|
|
{
|
|
|
|
|
$member = $this->users["{$connection->app->id}:{$channel}"][$connection->socketId] ?? null;
|
|
|
|
|
|
2020-09-19 11:16:26 +00:00
|
|
|
return Helpers::createFulfilledPromise($member);
|
2020-09-10 19:59:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the presence channels total members count.
|
|
|
|
|
*
|
|
|
|
|
* @param string|int $appId
|
|
|
|
|
* @param array $channelNames
|
|
|
|
|
* @return \React\Promise\PromiseInterface
|
|
|
|
|
*/
|
|
|
|
|
public function getChannelsMembersCount($appId, array $channelNames): PromiseInterface
|
|
|
|
|
{
|
2020-12-07 21:30:12 +00:00
|
|
|
$results = collect($channelNames)
|
|
|
|
|
->reduce(function ($results, $channel) use ($appId) {
|
|
|
|
|
$results[$channel] = isset($this->users["{$appId}:{$channel}"])
|
|
|
|
|
? count($this->users["{$appId}:{$channel}"])
|
|
|
|
|
: 0;
|
|
|
|
|
|
|
|
|
|
return $results;
|
|
|
|
|
}, []);
|
2020-09-10 19:59:26 +00:00
|
|
|
|
2020-09-19 11:16:26 +00:00
|
|
|
return Helpers::createFulfilledPromise($results);
|
2020-09-10 19:59:26 +00:00
|
|
|
}
|
|
|
|
|
|
2020-09-16 08:02:58 +00:00
|
|
|
/**
|
|
|
|
|
* Get the socket IDs for a presence channel member.
|
|
|
|
|
*
|
|
|
|
|
* @param string|int $userId
|
|
|
|
|
* @param string|int $appId
|
|
|
|
|
* @param string $channelName
|
|
|
|
|
* @return \React\Promise\PromiseInterface
|
|
|
|
|
*/
|
|
|
|
|
public function getMemberSockets($userId, $appId, $channelName): PromiseInterface
|
|
|
|
|
{
|
2020-09-19 11:16:26 +00:00
|
|
|
return Helpers::createFulfilledPromise(
|
2020-09-16 08:02:58 +00:00
|
|
|
$this->userSockets["{$appId}:{$channelName}:{$userId}"] ?? []
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-15 09:30:17 +00:00
|
|
|
/**
|
|
|
|
|
* Keep tracking the connections availability when they pong.
|
2026-01-24 11:17:46 +00:00
|
|
|
* Optimized: Uses unix timestamp directly instead of Carbon for performance.
|
2020-09-15 09:30:17 +00:00
|
|
|
*
|
|
|
|
|
* @param \Ratchet\ConnectionInterface $connection
|
2020-09-19 11:16:26 +00:00
|
|
|
* @return PromiseInterface[bool]
|
2020-09-15 09:30:17 +00:00
|
|
|
*/
|
2020-09-19 11:16:26 +00:00
|
|
|
public function connectionPonged(ConnectionInterface $connection): PromiseInterface
|
2020-09-15 09:30:17 +00:00
|
|
|
{
|
2026-01-24 11:17:46 +00:00
|
|
|
// Direct timestamp update on connection object - no channel iteration needed
|
|
|
|
|
// The connection object is passed by reference, so this updates it everywhere
|
|
|
|
|
$connection->lastPongedAt = time();
|
|
|
|
|
|
|
|
|
|
return Helpers::createFulfilledPromise(true);
|
2020-09-15 09:30:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Remove the obsolete connections that didn't ponged in a while.
|
2026-01-24 11:17:46 +00:00
|
|
|
* Optimized: Uses unix timestamp comparison instead of Carbon.
|
2020-09-15 09:30:17 +00:00
|
|
|
*
|
2020-09-19 11:16:26 +00:00
|
|
|
* @return PromiseInterface[bool]
|
2020-09-15 09:30:17 +00:00
|
|
|
*/
|
2020-09-19 11:16:26 +00:00
|
|
|
public function removeObsoleteConnections(): PromiseInterface
|
2020-09-15 09:30:17 +00:00
|
|
|
{
|
2022-10-06 11:46:54 +00:00
|
|
|
return $this->lock()->get(function () {
|
|
|
|
|
return $this->getLocalConnections()
|
|
|
|
|
->then(function ($connections) {
|
|
|
|
|
$promises = [];
|
2026-01-24 11:17:46 +00:00
|
|
|
$now = time();
|
2020-09-19 15:38:08 +00:00
|
|
|
|
2022-10-06 11:46:54 +00:00
|
|
|
foreach ($connections as $connection) {
|
2026-01-24 11:17:46 +00:00
|
|
|
// Handle both Carbon objects (legacy) and unix timestamps (optimized)
|
|
|
|
|
$lastPong = $connection->lastPongedAt ?? 0;
|
|
|
|
|
if (is_object($lastPong)) {
|
|
|
|
|
$differenceInSeconds = $lastPong->diffInSeconds(Carbon::now());
|
|
|
|
|
} else {
|
|
|
|
|
$differenceInSeconds = $now - (int) $lastPong;
|
|
|
|
|
}
|
2020-09-19 15:38:08 +00:00
|
|
|
|
2022-10-06 11:46:54 +00:00
|
|
|
if ($differenceInSeconds > 120) {
|
|
|
|
|
$promises[] = $this->unsubscribeFromAllChannels($connection);
|
|
|
|
|
}
|
2022-01-05 14:59:37 +00:00
|
|
|
}
|
2020-09-19 15:38:08 +00:00
|
|
|
|
2022-10-06 11:46:54 +00:00
|
|
|
return all($promises);
|
|
|
|
|
})->then(function () {
|
|
|
|
|
$this->lock()->release();
|
|
|
|
|
});
|
|
|
|
|
});
|
2022-01-05 14:59:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Pong connection in channels.
|
2026-01-24 11:17:46 +00:00
|
|
|
* Optimized: No longer iterates through channels - timestamp is on connection object.
|
2022-01-05 14:59:37 +00:00
|
|
|
*
|
|
|
|
|
* @param ConnectionInterface $connection
|
|
|
|
|
* @return PromiseInterface[bool]
|
|
|
|
|
*/
|
|
|
|
|
public function pongConnectionInChannels(ConnectionInterface $connection): PromiseInterface
|
|
|
|
|
{
|
2026-01-24 11:17:46 +00:00
|
|
|
// Simply update timestamp on the connection object directly
|
|
|
|
|
$connection->lastPongedAt = time();
|
2022-01-05 14:59:37 +00:00
|
|
|
|
2026-01-24 11:17:46 +00:00
|
|
|
return Helpers::createFulfilledPromise(true);
|
2020-09-19 15:38:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update the connection in all channels.
|
|
|
|
|
*
|
|
|
|
|
* @param ConnectionInterface $connection
|
|
|
|
|
* @return PromiseInterface[bool]
|
|
|
|
|
*/
|
|
|
|
|
public function updateConnectionInChannels($connection): PromiseInterface
|
|
|
|
|
{
|
2020-12-07 21:30:36 +00:00
|
|
|
return $this->getLocalChannels($connection->app->id)
|
|
|
|
|
->then(function ($channels) use ($connection) {
|
|
|
|
|
foreach ($channels as $channel) {
|
|
|
|
|
if ($channel->hasConnection($connection)) {
|
|
|
|
|
$channel->saveConnection($connection);
|
|
|
|
|
}
|
2020-09-19 15:38:08 +00:00
|
|
|
}
|
|
|
|
|
|
2020-12-07 21:30:36 +00:00
|
|
|
return true;
|
|
|
|
|
});
|
2020-09-15 09:30:17 +00:00
|
|
|
}
|
|
|
|
|
|
2020-09-12 14:45:07 +00:00
|
|
|
/**
|
|
|
|
|
* Mark the current instance as unable to accept new connections.
|
|
|
|
|
*
|
|
|
|
|
* @return $this
|
|
|
|
|
*/
|
|
|
|
|
public function declineNewConnections()
|
|
|
|
|
{
|
|
|
|
|
$this->acceptsNewConnections = false;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if the current server instance
|
|
|
|
|
* accepts new connections.
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function acceptsNewConnections(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->acceptsNewConnections;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-10 19:59:26 +00:00
|
|
|
/**
|
|
|
|
|
* Get the channel class by the channel name.
|
2026-01-24 11:17:46 +00:00
|
|
|
* Optimized: Direct string comparison instead of Str::startsWith
|
2020-09-10 19:59:26 +00:00
|
|
|
*
|
|
|
|
|
* @param string $channelName
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
protected function getChannelClassName(string $channelName): string
|
|
|
|
|
{
|
2026-01-24 11:17:46 +00:00
|
|
|
// Direct strncmp is faster than Str::startsWith for fixed prefixes
|
|
|
|
|
if (strncmp($channelName, 'private-', 8) === 0) {
|
2020-09-10 19:59:26 +00:00
|
|
|
return PrivateChannel::class;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 11:17:46 +00:00
|
|
|
if (strncmp($channelName, 'presence-', 9) === 0) {
|
2020-09-10 19:59:26 +00:00
|
|
|
return PresenceChannel::class;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 11:17:46 +00:00
|
|
|
if (strncmp($channelName, 'openpresence-', 13) === 0) {
|
2025-09-18 15:43:40 +00:00
|
|
|
return OpenPresenceChannel::class;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-10 19:59:26 +00:00
|
|
|
return Channel::class;
|
|
|
|
|
}
|
2020-09-19 15:38:08 +00:00
|
|
|
|
2020-12-09 09:37:03 +00:00
|
|
|
/**
|
|
|
|
|
* Get the unique identifier for the server.
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function getServerId(): string
|
|
|
|
|
{
|
|
|
|
|
return $this->serverId;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-19 15:38:08 +00:00
|
|
|
/**
|
|
|
|
|
* Get a new ArrayLock instance to avoid race conditions.
|
|
|
|
|
*
|
2022-10-06 11:46:54 +00:00
|
|
|
* @return ArrayLock
|
2020-09-19 15:38:08 +00:00
|
|
|
*/
|
|
|
|
|
protected function lock()
|
|
|
|
|
{
|
2020-09-19 15:46:13 +00:00
|
|
|
return new ArrayLock($this->store, static::$lockName, 0);
|
2020-09-19 15:38:08 +00:00
|
|
|
}
|
2020-09-10 19:59:26 +00:00
|
|
|
}
|