526 lines
13 KiB
PHP
526 lines
13 KiB
PHP
<?php
|
|
|
|
namespace Illuminate\Broadcasting;
|
|
|
|
use Ably\AblyRest;
|
|
use Closure;
|
|
use GuzzleHttp\Client as GuzzleClient;
|
|
use Illuminate\Broadcasting\Broadcasters\AblyBroadcaster;
|
|
use Illuminate\Broadcasting\Broadcasters\LogBroadcaster;
|
|
use Illuminate\Broadcasting\Broadcasters\NullBroadcaster;
|
|
use Illuminate\Broadcasting\Broadcasters\PusherBroadcaster;
|
|
use Illuminate\Broadcasting\Broadcasters\RedisBroadcaster;
|
|
use Illuminate\Bus\UniqueLock;
|
|
use Illuminate\Contracts\Broadcasting\Factory as FactoryContract;
|
|
use Illuminate\Contracts\Broadcasting\ShouldBeUnique;
|
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
|
use Illuminate\Contracts\Bus\Dispatcher as BusDispatcherContract;
|
|
use Illuminate\Contracts\Cache\Repository as Cache;
|
|
use Illuminate\Contracts\Foundation\CachesRoutes;
|
|
use InvalidArgumentException;
|
|
use Psr\Log\LoggerInterface;
|
|
use Pusher\Pusher;
|
|
|
|
/**
|
|
* @mixin \Illuminate\Contracts\Broadcasting\Broadcaster
|
|
*/
|
|
class BroadcastManager implements FactoryContract
|
|
{
|
|
/**
|
|
* The application instance.
|
|
*
|
|
* @var \Illuminate\Contracts\Container\Container
|
|
*/
|
|
protected $app;
|
|
|
|
/**
|
|
* The array of resolved broadcast drivers.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $drivers = [];
|
|
|
|
/**
|
|
* The registered custom driver creators.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $customCreators = [];
|
|
|
|
/**
|
|
* Create a new manager instance.
|
|
*
|
|
* @param \Illuminate\Contracts\Container\Container $app
|
|
* @return void
|
|
*/
|
|
public function __construct($app)
|
|
{
|
|
$this->app = $app;
|
|
}
|
|
|
|
/**
|
|
* Register the routes for handling broadcast channel authentication and sockets.
|
|
*
|
|
* @param array|null $attributes
|
|
* @return void
|
|
*/
|
|
public function routes(?array $attributes = null)
|
|
{
|
|
if ($this->app instanceof CachesRoutes && $this->app->routesAreCached()) {
|
|
return;
|
|
}
|
|
|
|
$attributes = $attributes ?: ['middleware' => ['web']];
|
|
|
|
$this->app['router']->group($attributes, function ($router) {
|
|
$router->match(
|
|
['get', 'post'], '/broadcasting/auth',
|
|
'\\'.BroadcastController::class.'@authenticate'
|
|
)->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class]);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Register the routes for handling broadcast user authentication.
|
|
*
|
|
* @param array|null $attributes
|
|
* @return void
|
|
*/
|
|
public function userRoutes(?array $attributes = null)
|
|
{
|
|
if ($this->app instanceof CachesRoutes && $this->app->routesAreCached()) {
|
|
return;
|
|
}
|
|
|
|
$attributes = $attributes ?: ['middleware' => ['web']];
|
|
|
|
$this->app['router']->group($attributes, function ($router) {
|
|
$router->match(
|
|
['get', 'post'], '/broadcasting/user-auth',
|
|
'\\'.BroadcastController::class.'@authenticateUser'
|
|
)->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class]);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Register the routes for handling broadcast authentication and sockets.
|
|
*
|
|
* Alias of "routes" method.
|
|
*
|
|
* @param array|null $attributes
|
|
* @return void
|
|
*/
|
|
public function channelRoutes(?array $attributes = null)
|
|
{
|
|
$this->routes($attributes);
|
|
}
|
|
|
|
/**
|
|
* Get the socket ID for the given request.
|
|
*
|
|
* @param \Illuminate\Http\Request|null $request
|
|
* @return string|null
|
|
*/
|
|
public function socket($request = null)
|
|
{
|
|
if (! $request && ! $this->app->bound('request')) {
|
|
return;
|
|
}
|
|
|
|
$request = $request ?: $this->app['request'];
|
|
|
|
return $request->header('X-Socket-ID');
|
|
}
|
|
|
|
/**
|
|
* Begin sending an anonymous broadcast to the given channels.
|
|
*/
|
|
public function on(Channel|string|array $channels): AnonymousEvent
|
|
{
|
|
return new AnonymousEvent($channels);
|
|
}
|
|
|
|
/**
|
|
* Begin sending an anonymous broadcast to the given private channels.
|
|
*/
|
|
public function private(string $channel): AnonymousEvent
|
|
{
|
|
return $this->on(new PrivateChannel($channel));
|
|
}
|
|
|
|
/**
|
|
* Begin sending an anonymous broadcast to the given presence channels.
|
|
*/
|
|
public function presence(string $channel): AnonymousEvent
|
|
{
|
|
return $this->on(new PresenceChannel($channel));
|
|
}
|
|
|
|
/**
|
|
* Begin broadcasting an event.
|
|
*
|
|
* @param mixed|null $event
|
|
* @return \Illuminate\Broadcasting\PendingBroadcast
|
|
*/
|
|
public function event($event = null)
|
|
{
|
|
return new PendingBroadcast($this->app->make('events'), $event);
|
|
}
|
|
|
|
/**
|
|
* Queue the given event for broadcast.
|
|
*
|
|
* @param mixed $event
|
|
* @return void
|
|
*/
|
|
public function queue($event)
|
|
{
|
|
if ($event instanceof ShouldBroadcastNow ||
|
|
(is_object($event) &&
|
|
method_exists($event, 'shouldBroadcastNow') &&
|
|
$event->shouldBroadcastNow())) {
|
|
return $this->app->make(BusDispatcherContract::class)->dispatchNow(new BroadcastEvent(clone $event));
|
|
}
|
|
|
|
$queue = null;
|
|
|
|
if (method_exists($event, 'broadcastQueue')) {
|
|
$queue = $event->broadcastQueue();
|
|
} elseif (isset($event->broadcastQueue)) {
|
|
$queue = $event->broadcastQueue;
|
|
} elseif (isset($event->queue)) {
|
|
$queue = $event->queue;
|
|
}
|
|
|
|
$broadcastEvent = new BroadcastEvent(clone $event);
|
|
|
|
if ($event instanceof ShouldBeUnique) {
|
|
$broadcastEvent = new UniqueBroadcastEvent(clone $event);
|
|
|
|
if ($this->mustBeUniqueAndCannotAcquireLock($broadcastEvent)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
$this->app->make('queue')
|
|
->connection($event->connection ?? null)
|
|
->pushOn($queue, $broadcastEvent);
|
|
}
|
|
|
|
/**
|
|
* Determine if the broadcastable event must be unique and determine if we can acquire the necessary lock.
|
|
*
|
|
* @param mixed $event
|
|
* @return bool
|
|
*/
|
|
protected function mustBeUniqueAndCannotAcquireLock($event)
|
|
{
|
|
return ! (new UniqueLock(
|
|
method_exists($event, 'uniqueVia')
|
|
? $event->uniqueVia()
|
|
: $this->app->make(Cache::class)
|
|
))->acquire($event);
|
|
}
|
|
|
|
/**
|
|
* Get a driver instance.
|
|
*
|
|
* @param string|null $driver
|
|
* @return mixed
|
|
*/
|
|
public function connection($driver = null)
|
|
{
|
|
return $this->driver($driver);
|
|
}
|
|
|
|
/**
|
|
* Get a driver instance.
|
|
*
|
|
* @param string|null $name
|
|
* @return mixed
|
|
*/
|
|
public function driver($name = null)
|
|
{
|
|
$name = $name ?: $this->getDefaultDriver();
|
|
|
|
return $this->drivers[$name] = $this->get($name);
|
|
}
|
|
|
|
/**
|
|
* Attempt to get the connection from the local cache.
|
|
*
|
|
* @param string $name
|
|
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
|
|
*/
|
|
protected function get($name)
|
|
{
|
|
return $this->drivers[$name] ?? $this->resolve($name);
|
|
}
|
|
|
|
/**
|
|
* Resolve the given broadcaster.
|
|
*
|
|
* @param string $name
|
|
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
|
|
*
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
protected function resolve($name)
|
|
{
|
|
$config = $this->getConfig($name);
|
|
|
|
if (is_null($config)) {
|
|
throw new InvalidArgumentException("Broadcast connection [{$name}] is not defined.");
|
|
}
|
|
|
|
if (isset($this->customCreators[$config['driver']])) {
|
|
return $this->callCustomCreator($config);
|
|
}
|
|
|
|
$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
|
|
|
|
if (! method_exists($this, $driverMethod)) {
|
|
throw new InvalidArgumentException("Driver [{$config['driver']}] is not supported.");
|
|
}
|
|
|
|
return $this->{$driverMethod}($config);
|
|
}
|
|
|
|
/**
|
|
* Call a custom driver creator.
|
|
*
|
|
* @param array $config
|
|
* @return mixed
|
|
*/
|
|
protected function callCustomCreator(array $config)
|
|
{
|
|
return $this->customCreators[$config['driver']]($this->app, $config);
|
|
}
|
|
|
|
/**
|
|
* Create an instance of the driver.
|
|
*
|
|
* @param array $config
|
|
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
|
|
*/
|
|
protected function createReverbDriver(array $config)
|
|
{
|
|
return $this->createPusherDriver($config);
|
|
}
|
|
|
|
/**
|
|
* Create an instance of the driver.
|
|
*
|
|
* @param array $config
|
|
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
|
|
*/
|
|
protected function createPusherDriver(array $config)
|
|
{
|
|
return new PusherBroadcaster($this->pusher($config));
|
|
}
|
|
|
|
/**
|
|
* Get a Pusher instance for the given configuration.
|
|
*
|
|
* @param array $config
|
|
* @return \Pusher\Pusher
|
|
*/
|
|
public function pusher(array $config)
|
|
{
|
|
$guzzleClient = new GuzzleClient(
|
|
array_merge(
|
|
[
|
|
'connect_timeout' => 10,
|
|
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT,
|
|
'timeout' => 30,
|
|
],
|
|
$config['client_options'] ?? [],
|
|
),
|
|
);
|
|
|
|
$pusher = new Pusher(
|
|
$config['key'],
|
|
$config['secret'],
|
|
$config['app_id'],
|
|
$config['options'] ?? [],
|
|
$guzzleClient,
|
|
);
|
|
|
|
if ($config['log'] ?? false) {
|
|
$pusher->setLogger($this->app->make(LoggerInterface::class));
|
|
}
|
|
|
|
return $pusher;
|
|
}
|
|
|
|
/**
|
|
* Create an instance of the driver.
|
|
*
|
|
* @param array $config
|
|
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
|
|
*/
|
|
protected function createAblyDriver(array $config)
|
|
{
|
|
return new AblyBroadcaster($this->ably($config));
|
|
}
|
|
|
|
/**
|
|
* Get an Ably instance for the given configuration.
|
|
*
|
|
* @param array $config
|
|
* @return \Ably\AblyRest
|
|
*/
|
|
public function ably(array $config)
|
|
{
|
|
return new AblyRest($config);
|
|
}
|
|
|
|
/**
|
|
* Create an instance of the driver.
|
|
*
|
|
* @param array $config
|
|
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
|
|
*/
|
|
protected function createRedisDriver(array $config)
|
|
{
|
|
return new RedisBroadcaster(
|
|
$this->app->make('redis'), $config['connection'] ?? null,
|
|
$this->app['config']->get('database.redis.options.prefix', '')
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Create an instance of the driver.
|
|
*
|
|
* @param array $config
|
|
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
|
|
*/
|
|
protected function createLogDriver(array $config)
|
|
{
|
|
return new LogBroadcaster(
|
|
$this->app->make(LoggerInterface::class)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Create an instance of the driver.
|
|
*
|
|
* @param array $config
|
|
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
|
|
*/
|
|
protected function createNullDriver(array $config)
|
|
{
|
|
return new NullBroadcaster;
|
|
}
|
|
|
|
/**
|
|
* Get the connection configuration.
|
|
*
|
|
* @param string $name
|
|
* @return array
|
|
*/
|
|
protected function getConfig($name)
|
|
{
|
|
if (! is_null($name) && $name !== 'null') {
|
|
return $this->app['config']["broadcasting.connections.{$name}"];
|
|
}
|
|
|
|
return ['driver' => 'null'];
|
|
}
|
|
|
|
/**
|
|
* Get the default driver name.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getDefaultDriver()
|
|
{
|
|
return $this->app['config']['broadcasting.default'];
|
|
}
|
|
|
|
/**
|
|
* Set the default driver name.
|
|
*
|
|
* @param string $name
|
|
* @return void
|
|
*/
|
|
public function setDefaultDriver($name)
|
|
{
|
|
$this->app['config']['broadcasting.default'] = $name;
|
|
}
|
|
|
|
/**
|
|
* Disconnect the given disk and remove from local cache.
|
|
*
|
|
* @param string|null $name
|
|
* @return void
|
|
*/
|
|
public function purge($name = null)
|
|
{
|
|
$name ??= $this->getDefaultDriver();
|
|
|
|
unset($this->drivers[$name]);
|
|
}
|
|
|
|
/**
|
|
* Register a custom driver creator Closure.
|
|
*
|
|
* @param string $driver
|
|
* @param \Closure $callback
|
|
* @return $this
|
|
*/
|
|
public function extend($driver, Closure $callback)
|
|
{
|
|
$this->customCreators[$driver] = $callback;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get the application instance used by the manager.
|
|
*
|
|
* @return \Illuminate\Contracts\Foundation\Application
|
|
*/
|
|
public function getApplication()
|
|
{
|
|
return $this->app;
|
|
}
|
|
|
|
/**
|
|
* Set the application instance used by the manager.
|
|
*
|
|
* @param \Illuminate\Contracts\Foundation\Application $app
|
|
* @return $this
|
|
*/
|
|
public function setApplication($app)
|
|
{
|
|
$this->app = $app;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Forget all of the resolved driver instances.
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function forgetDrivers()
|
|
{
|
|
$this->drivers = [];
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Dynamically call the default driver instance.
|
|
*
|
|
* @param string $method
|
|
* @param array $parameters
|
|
* @return mixed
|
|
*/
|
|
public function __call($method, $parameters)
|
|
{
|
|
return $this->driver()->$method(...$parameters);
|
|
}
|
|
}
|