Upgrade framework
This commit is contained in:
@@ -5,6 +5,7 @@ namespace Illuminate\Broadcasting;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Illuminate\Support\Facades\Broadcast;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
class BroadcastController extends Controller
|
||||
{
|
||||
@@ -16,6 +17,28 @@ class BroadcastController extends Controller
|
||||
*/
|
||||
public function authenticate(Request $request)
|
||||
{
|
||||
if ($request->hasSession()) {
|
||||
$request->session()->reflash();
|
||||
}
|
||||
|
||||
return Broadcast::auth($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the current user.
|
||||
*
|
||||
* See: https://pusher.com/docs/channels/server_api/authenticating-users/#user-authentication.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function authenticateUser(Request $request)
|
||||
{
|
||||
if ($request->hasSession()) {
|
||||
$request->session()->reflash();
|
||||
}
|
||||
|
||||
return Broadcast::resolveAuthenticatedUser($request)
|
||||
?? throw new AccessDeniedHttpException;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
namespace Illuminate\Broadcasting;
|
||||
|
||||
use ReflectionClass;
|
||||
use ReflectionProperty;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\Job;
|
||||
use Illuminate\Contracts\Broadcasting\Factory as BroadcastingFactory;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Contracts\Broadcasting\Broadcaster;
|
||||
use Illuminate\Support\Arr;
|
||||
use ReflectionClass;
|
||||
use ReflectionProperty;
|
||||
|
||||
class BroadcastEvent implements ShouldQueue
|
||||
{
|
||||
@@ -21,6 +21,27 @@ class BroadcastEvent implements ShouldQueue
|
||||
*/
|
||||
public $event;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries;
|
||||
|
||||
/**
|
||||
* The number of seconds the job can run before timing out.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $timeout;
|
||||
|
||||
/**
|
||||
* The number of seconds to wait before retrying the job when encountering an uncaught exception.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $backoff;
|
||||
|
||||
/**
|
||||
* Create a new job handler instance.
|
||||
*
|
||||
@@ -30,23 +51,40 @@ class BroadcastEvent implements ShouldQueue
|
||||
public function __construct($event)
|
||||
{
|
||||
$this->event = $event;
|
||||
$this->tries = property_exists($event, 'tries') ? $event->tries : null;
|
||||
$this->timeout = property_exists($event, 'timeout') ? $event->timeout : null;
|
||||
$this->backoff = property_exists($event, 'backoff') ? $event->backoff : null;
|
||||
$this->afterCommit = property_exists($event, 'afterCommit') ? $event->afterCommit : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the queued job.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Broadcasting\Broadcaster $broadcaster
|
||||
* @param \Illuminate\Contracts\Broadcasting\Factory $manager
|
||||
* @return void
|
||||
*/
|
||||
public function handle(Broadcaster $broadcaster)
|
||||
public function handle(BroadcastingFactory $manager)
|
||||
{
|
||||
$name = method_exists($this->event, 'broadcastAs')
|
||||
? $this->event->broadcastAs() : get_class($this->event);
|
||||
|
||||
$broadcaster->broadcast(
|
||||
array_wrap($this->event->broadcastOn()), $name,
|
||||
$this->getPayloadFromEvent($this->event)
|
||||
);
|
||||
$channels = Arr::wrap($this->event->broadcastOn());
|
||||
|
||||
if (empty($channels)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$connections = method_exists($this->event, 'broadcastConnections')
|
||||
? $this->event->broadcastConnections()
|
||||
: [null];
|
||||
|
||||
$payload = $this->getPayloadFromEvent($this->event);
|
||||
|
||||
foreach ($connections as $connection) {
|
||||
$manager->connection($connection)->broadcast(
|
||||
$channels, $name, $payload
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,10 +95,9 @@ class BroadcastEvent implements ShouldQueue
|
||||
*/
|
||||
protected function getPayloadFromEvent($event)
|
||||
{
|
||||
if (method_exists($event, 'broadcastWith')) {
|
||||
return array_merge(
|
||||
$event->broadcastWith(), ['socket' => data_get($event, 'socket')]
|
||||
);
|
||||
if (method_exists($event, 'broadcastWith') &&
|
||||
! is_null($payload = $event->broadcastWith())) {
|
||||
return array_merge($payload, ['socket' => data_get($event, 'socket')]);
|
||||
}
|
||||
|
||||
$payload = [];
|
||||
@@ -98,4 +135,14 @@ class BroadcastEvent implements ShouldQueue
|
||||
{
|
||||
return get_class($this->event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the instance for cloning.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$this->event = clone $this->event;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,24 +2,34 @@
|
||||
|
||||
namespace Illuminate\Broadcasting;
|
||||
|
||||
use Pusher;
|
||||
use Ably\AblyRest;
|
||||
use Closure;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use InvalidArgumentException;
|
||||
use GuzzleHttp\Client as GuzzleClient;
|
||||
use Illuminate\Broadcasting\Broadcasters\AblyBroadcaster;
|
||||
use Illuminate\Broadcasting\Broadcasters\LogBroadcaster;
|
||||
use Illuminate\Broadcasting\Broadcasters\NullBroadcaster;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Illuminate\Broadcasting\Broadcasters\RedisBroadcaster;
|
||||
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\Foundation\Application
|
||||
* @var \Illuminate\Contracts\Container\Container
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
@@ -40,7 +50,7 @@ class BroadcastManager implements FactoryContract
|
||||
/**
|
||||
* Create a new manager instance.
|
||||
*
|
||||
* @param \Illuminate\Foundation\Application $app
|
||||
* @param \Illuminate\Contracts\Container\Container $app
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($app)
|
||||
@@ -49,24 +59,62 @@ class BroadcastManager implements FactoryContract
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the routes for handling broadcast authentication and sockets.
|
||||
* 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->routesAreCached()) {
|
||||
if ($this->app instanceof CachesRoutes && $this->app->routesAreCached()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$attributes = $attributes ?: ['middleware' => ['web']];
|
||||
|
||||
$this->app['router']->group($attributes, function ($router) {
|
||||
$router->post('/broadcasting/auth', BroadcastController::class.'@authenticate');
|
||||
$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)
|
||||
{
|
||||
return $this->routes($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the socket ID for the given request.
|
||||
*
|
||||
@@ -81,16 +129,14 @@ class BroadcastManager implements FactoryContract
|
||||
|
||||
$request = $request ?: $this->app['request'];
|
||||
|
||||
if ($request->hasHeader('X-Socket-ID')) {
|
||||
return $request->header('X-Socket-ID');
|
||||
}
|
||||
return $request->header('X-Socket-ID');
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin broadcasting an event.
|
||||
*
|
||||
* @param mixed|null $event
|
||||
* @return \Illuminate\Broadcasting\PendingBroadcast|void
|
||||
* @return \Illuminate\Broadcasting\PendingBroadcast
|
||||
*/
|
||||
public function event($event = null)
|
||||
{
|
||||
@@ -105,10 +151,11 @@ class BroadcastManager implements FactoryContract
|
||||
*/
|
||||
public function queue($event)
|
||||
{
|
||||
$connection = $event instanceof ShouldBroadcastNow ? 'sync' : null;
|
||||
|
||||
if (is_null($connection) && isset($event->connection)) {
|
||||
$connection = $event->connection;
|
||||
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;
|
||||
@@ -121,15 +168,40 @@ class BroadcastManager implements FactoryContract
|
||||
$queue = $event->queue;
|
||||
}
|
||||
|
||||
$this->app->make('queue')->connection($connection)->pushOn(
|
||||
$queue, new BroadcastEvent(clone $event)
|
||||
);
|
||||
$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 $driver
|
||||
* @param string|null $driver
|
||||
* @return mixed
|
||||
*/
|
||||
public function connection($driver = null)
|
||||
@@ -140,7 +212,7 @@ class BroadcastManager implements FactoryContract
|
||||
/**
|
||||
* Get a driver instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|null $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function driver($name = null)
|
||||
@@ -158,11 +230,11 @@ class BroadcastManager implements FactoryContract
|
||||
*/
|
||||
protected function get($name)
|
||||
{
|
||||
return isset($this->drivers[$name]) ? $this->drivers[$name] : $this->resolve($name);
|
||||
return $this->drivers[$name] ?? $this->resolve($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given store.
|
||||
* Resolve the given broadcaster.
|
||||
*
|
||||
* @param string $name
|
||||
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
|
||||
@@ -174,7 +246,7 @@ class BroadcastManager implements FactoryContract
|
||||
$config = $this->getConfig($name);
|
||||
|
||||
if (is_null($config)) {
|
||||
throw new InvalidArgumentException("Broadcaster [{$name}] is not defined.");
|
||||
throw new InvalidArgumentException("Broadcast connection [{$name}] is not defined.");
|
||||
}
|
||||
|
||||
if (isset($this->customCreators[$config['driver']])) {
|
||||
@@ -209,10 +281,54 @@ class BroadcastManager implements FactoryContract
|
||||
*/
|
||||
protected function createPusherDriver(array $config)
|
||||
{
|
||||
return new PusherBroadcaster(
|
||||
new Pusher($config['key'], $config['secret'],
|
||||
$config['app_id'], Arr::get($config, 'options', []))
|
||||
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)
|
||||
{
|
||||
$pusher = new Pusher(
|
||||
$config['key'],
|
||||
$config['secret'],
|
||||
$config['app_id'],
|
||||
$config['options'] ?? [],
|
||||
isset($config['client_options']) && ! empty($config['client_options'])
|
||||
? new GuzzleClient($config['client_options'])
|
||||
: null,
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,7 +340,8 @@ class BroadcastManager implements FactoryContract
|
||||
protected function createRedisDriver(array $config)
|
||||
{
|
||||
return new RedisBroadcaster(
|
||||
$this->app->make('redis'), Arr::get($config, 'connection')
|
||||
$this->app->make('redis'), $config['connection'] ?? null,
|
||||
$this->app['config']->get('database.redis.options.prefix', '')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -260,7 +377,11 @@ class BroadcastManager implements FactoryContract
|
||||
*/
|
||||
protected function getConfig($name)
|
||||
{
|
||||
return $this->app['config']["broadcasting.connections.{$name}"];
|
||||
if (! is_null($name) && $name !== 'null') {
|
||||
return $this->app['config']["broadcasting.connections.{$name}"];
|
||||
}
|
||||
|
||||
return ['driver' => 'null'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -284,10 +405,23 @@ class BroadcastManager implements FactoryContract
|
||||
$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 string $driver
|
||||
* @param \Closure $callback
|
||||
* @return $this
|
||||
*/
|
||||
@@ -298,11 +432,46 @@ class BroadcastManager implements FactoryContract
|
||||
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
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $parameters)
|
||||
|
||||
@@ -2,19 +2,13 @@
|
||||
|
||||
namespace Illuminate\Broadcasting;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Contracts\Broadcasting\Factory as BroadcastingFactory;
|
||||
use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract;
|
||||
use Illuminate\Contracts\Broadcasting\Factory as BroadcastingFactory;
|
||||
use Illuminate\Contracts\Support\DeferrableProvider;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class BroadcastServiceProvider extends ServiceProvider
|
||||
class BroadcastServiceProvider extends ServiceProvider implements DeferrableProvider
|
||||
{
|
||||
/**
|
||||
* Indicates if loading of the provider is deferred.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $defer = true;
|
||||
|
||||
/**
|
||||
* Register the service provider.
|
||||
*
|
||||
|
||||
235
vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/AblyBroadcaster.php
vendored
Normal file
235
vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/AblyBroadcaster.php
vendored
Normal file
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Broadcasting\Broadcasters;
|
||||
|
||||
use Ably\AblyRest;
|
||||
use Ably\Exceptions\AblyException;
|
||||
use Ably\Models\Message as AblyMessage;
|
||||
use Illuminate\Broadcasting\BroadcastException;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
/**
|
||||
* @author Matthew Hall (matthall28@gmail.com)
|
||||
* @author Taylor Otwell (taylor@laravel.com)
|
||||
*/
|
||||
class AblyBroadcaster extends Broadcaster
|
||||
{
|
||||
/**
|
||||
* The AblyRest SDK instance.
|
||||
*
|
||||
* @var \Ably\AblyRest
|
||||
*/
|
||||
protected $ably;
|
||||
|
||||
/**
|
||||
* Create a new broadcaster instance.
|
||||
*
|
||||
* @param \Ably\AblyRest $ably
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(AblyRest $ably)
|
||||
{
|
||||
$this->ably = $ably;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the incoming request for a given channel.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
*/
|
||||
public function auth($request)
|
||||
{
|
||||
$channelName = $this->normalizeChannelName($request->channel_name);
|
||||
|
||||
if (empty($request->channel_name) ||
|
||||
($this->isGuardedChannel($request->channel_name) &&
|
||||
! $this->retrieveUser($request, $channelName))) {
|
||||
throw new AccessDeniedHttpException;
|
||||
}
|
||||
|
||||
return parent::verifyUserCanAccessChannel(
|
||||
$request, $channelName
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the valid authentication response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param mixed $result
|
||||
* @return mixed
|
||||
*/
|
||||
public function validAuthenticationResponse($request, $result)
|
||||
{
|
||||
if (str_starts_with($request->channel_name, 'private')) {
|
||||
$signature = $this->generateAblySignature(
|
||||
$request->channel_name, $request->socket_id
|
||||
);
|
||||
|
||||
return ['auth' => $this->getPublicToken().':'.$signature];
|
||||
}
|
||||
|
||||
$channelName = $this->normalizeChannelName($request->channel_name);
|
||||
|
||||
$user = $this->retrieveUser($request, $channelName);
|
||||
|
||||
$broadcastIdentifier = method_exists($user, 'getAuthIdentifierForBroadcasting')
|
||||
? $user->getAuthIdentifierForBroadcasting()
|
||||
: $user->getAuthIdentifier();
|
||||
|
||||
$signature = $this->generateAblySignature(
|
||||
$request->channel_name,
|
||||
$request->socket_id,
|
||||
$userData = array_filter([
|
||||
'user_id' => (string) $broadcastIdentifier,
|
||||
'user_info' => $result,
|
||||
])
|
||||
);
|
||||
|
||||
return [
|
||||
'auth' => $this->getPublicToken().':'.$signature,
|
||||
'channel_data' => json_encode($userData),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the signature needed for Ably authentication headers.
|
||||
*
|
||||
* @param string $channelName
|
||||
* @param string $socketId
|
||||
* @param array|null $userData
|
||||
* @return string
|
||||
*/
|
||||
public function generateAblySignature($channelName, $socketId, $userData = null)
|
||||
{
|
||||
return hash_hmac(
|
||||
'sha256',
|
||||
sprintf('%s:%s%s', $socketId, $channelName, $userData ? ':'.json_encode($userData) : ''),
|
||||
$this->getPrivateToken(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast the given event.
|
||||
*
|
||||
* @param array $channels
|
||||
* @param string $event
|
||||
* @param array $payload
|
||||
* @return void
|
||||
*
|
||||
* @throws \Illuminate\Broadcasting\BroadcastException
|
||||
*/
|
||||
public function broadcast(array $channels, $event, array $payload = [])
|
||||
{
|
||||
try {
|
||||
foreach ($this->formatChannels($channels) as $channel) {
|
||||
$this->ably->channels->get($channel)->publish(
|
||||
$this->buildAblyMessage($event, $payload)
|
||||
);
|
||||
}
|
||||
} catch (AblyException $e) {
|
||||
throw new BroadcastException(
|
||||
sprintf('Ably error: %s', $e->getMessage())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an Ably message object for broadcasting.
|
||||
*
|
||||
* @param string $event
|
||||
* @param array $payload
|
||||
* @return \Ably\Models\Message
|
||||
*/
|
||||
protected function buildAblyMessage($event, array $payload = [])
|
||||
{
|
||||
return tap(new AblyMessage, function ($message) use ($event, $payload) {
|
||||
$message->name = $event;
|
||||
$message->data = $payload;
|
||||
$message->connectionKey = data_get($payload, 'socket');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the channel is protected by authentication.
|
||||
*
|
||||
* @param string $channel
|
||||
* @return bool
|
||||
*/
|
||||
public function isGuardedChannel($channel)
|
||||
{
|
||||
return Str::startsWith($channel, ['private-', 'presence-']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove prefix from channel name.
|
||||
*
|
||||
* @param string $channel
|
||||
* @return string
|
||||
*/
|
||||
public function normalizeChannelName($channel)
|
||||
{
|
||||
if ($this->isGuardedChannel($channel)) {
|
||||
return str_starts_with($channel, 'private-')
|
||||
? Str::replaceFirst('private-', '', $channel)
|
||||
: Str::replaceFirst('presence-', '', $channel);
|
||||
}
|
||||
|
||||
return $channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the channel array into an array of strings.
|
||||
*
|
||||
* @param array $channels
|
||||
* @return array
|
||||
*/
|
||||
protected function formatChannels(array $channels)
|
||||
{
|
||||
return array_map(function ($channel) {
|
||||
$channel = (string) $channel;
|
||||
|
||||
if (Str::startsWith($channel, ['private-', 'presence-'])) {
|
||||
return str_starts_with($channel, 'private-')
|
||||
? Str::replaceFirst('private-', 'private:', $channel)
|
||||
: Str::replaceFirst('presence-', 'presence:', $channel);
|
||||
}
|
||||
|
||||
return 'public:'.$channel;
|
||||
}, $channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the public token value from the Ably key.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getPublicToken()
|
||||
{
|
||||
return Str::before($this->ably->options->key, ':');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the private token value from the Ably key.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getPrivateToken()
|
||||
{
|
||||
return Str::after($this->ably->options->key, ':');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying Ably SDK instance.
|
||||
*
|
||||
* @return \Ably\AblyRest
|
||||
*/
|
||||
public function getAbly()
|
||||
{
|
||||
return $this->ably;
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,28 @@
|
||||
|
||||
namespace Illuminate\Broadcasting\Broadcasters;
|
||||
|
||||
use ReflectionFunction;
|
||||
use ReflectionParameter;
|
||||
use Illuminate\Support\Str;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Contracts\Routing\BindingRegistrar;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract;
|
||||
use Illuminate\Contracts\Broadcasting\HasBroadcastChannel;
|
||||
use Illuminate\Contracts\Routing\BindingRegistrar;
|
||||
use Illuminate\Contracts\Routing\UrlRoutable;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Reflector;
|
||||
use ReflectionClass;
|
||||
use ReflectionFunction;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
abstract class Broadcaster implements BroadcasterContract
|
||||
{
|
||||
/**
|
||||
* The callback to resolve the authenticated user information.
|
||||
*
|
||||
* @var \Closure|null
|
||||
*/
|
||||
protected $authenticatedUserCallback = null;
|
||||
|
||||
/**
|
||||
* The registered channel authenticators.
|
||||
*
|
||||
@@ -20,24 +31,68 @@ abstract class Broadcaster implements BroadcasterContract
|
||||
*/
|
||||
protected $channels = [];
|
||||
|
||||
/**
|
||||
* The registered channel options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $channelOptions = [];
|
||||
|
||||
/**
|
||||
* The binding registrar instance.
|
||||
*
|
||||
* @var BindingRegistrar
|
||||
* @var \Illuminate\Contracts\Routing\BindingRegistrar
|
||||
*/
|
||||
protected $bindingRegistrar;
|
||||
|
||||
/**
|
||||
* Resolve the authenticated user payload for the incoming connection request.
|
||||
*
|
||||
* See: https://pusher.com/docs/channels/library_auth_reference/auth-signatures/#user-authentication.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array|null
|
||||
*/
|
||||
public function resolveAuthenticatedUser($request)
|
||||
{
|
||||
if ($this->authenticatedUserCallback) {
|
||||
return $this->authenticatedUserCallback->__invoke($request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the user retrieval callback used to authenticate connections.
|
||||
*
|
||||
* See: https://pusher.com/docs/channels/library_auth_reference/auth-signatures/#user-authentication.
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @return void
|
||||
*/
|
||||
public function resolveAuthenticatedUserUsing(Closure $callback)
|
||||
{
|
||||
$this->authenticatedUserCallback = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a channel authenticator.
|
||||
*
|
||||
* @param string $channel
|
||||
* @param callable $callback
|
||||
* @param \Illuminate\Contracts\Broadcasting\HasBroadcastChannel|string $channel
|
||||
* @param callable|string $callback
|
||||
* @param array $options
|
||||
* @return $this
|
||||
*/
|
||||
public function channel($channel, callable $callback)
|
||||
public function channel($channel, $callback, $options = [])
|
||||
{
|
||||
if ($channel instanceof HasBroadcastChannel) {
|
||||
$channel = $channel->broadcastChannelRoute();
|
||||
} elseif (is_string($channel) && class_exists($channel) && is_a($channel, HasBroadcastChannel::class, true)) {
|
||||
$channel = (new $channel)->broadcastChannelRoute();
|
||||
}
|
||||
|
||||
$this->channels[$channel] = $callback;
|
||||
|
||||
$this->channelOptions[$channel] = $options;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -47,22 +102,30 @@ abstract class Broadcaster implements BroadcasterContract
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $channel
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
*/
|
||||
protected function verifyUserCanAccessChannel($request, $channel)
|
||||
{
|
||||
foreach ($this->channels as $pattern => $callback) {
|
||||
if (! Str::is(preg_replace('/\{(.*?)\}/', '*', $pattern), $channel)) {
|
||||
if (! $this->channelNameMatchesPattern($channel, $pattern)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parameters = $this->extractAuthParameters($pattern, $channel, $callback);
|
||||
|
||||
if ($result = $callback($request->user(), ...$parameters)) {
|
||||
$handler = $this->normalizeChannelHandlerToCallable($callback);
|
||||
|
||||
$result = $handler($this->retrieveUser($request, $channel), ...$parameters);
|
||||
|
||||
if ($result === false) {
|
||||
throw new AccessDeniedHttpException;
|
||||
} elseif ($result) {
|
||||
return $this->validAuthenticationResponse($request, $result);
|
||||
}
|
||||
}
|
||||
|
||||
throw new HttpException(403);
|
||||
throw new AccessDeniedHttpException;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,12 +133,12 @@ abstract class Broadcaster implements BroadcasterContract
|
||||
*
|
||||
* @param string $pattern
|
||||
* @param string $channel
|
||||
* @param callable $callback
|
||||
* @param callable|string $callback
|
||||
* @return array
|
||||
*/
|
||||
protected function extractAuthParameters($pattern, $channel, $callback)
|
||||
{
|
||||
$callbackParameters = (new ReflectionFunction($callback))->getParameters();
|
||||
$callbackParameters = $this->extractParameters($callback);
|
||||
|
||||
return collect($this->extractChannelKeys($pattern, $channel))->reject(function ($value, $key) {
|
||||
return is_numeric($key);
|
||||
@@ -84,6 +147,44 @@ abstract class Broadcaster implements BroadcasterContract
|
||||
})->values()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the parameters out of what the user passed to handle the channel authentication.
|
||||
*
|
||||
* @param callable|string $callback
|
||||
* @return \ReflectionParameter[]
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function extractParameters($callback)
|
||||
{
|
||||
if (is_callable($callback)) {
|
||||
return (new ReflectionFunction($callback))->getParameters();
|
||||
} elseif (is_string($callback)) {
|
||||
return $this->extractParametersFromClass($callback);
|
||||
}
|
||||
|
||||
throw new Exception('Given channel handler is an unknown type.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the parameters out of a class channel's "join" method.
|
||||
*
|
||||
* @param string $callback
|
||||
* @return \ReflectionParameter[]
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function extractParametersFromClass($callback)
|
||||
{
|
||||
$reflection = new ReflectionClass($callback);
|
||||
|
||||
if (! $reflection->hasMethod('join')) {
|
||||
throw new Exception('Class based channel must define a "join" method.');
|
||||
}
|
||||
|
||||
return $reflection->getMethod('join')->getParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the channel keys from the incoming channel name.
|
||||
*
|
||||
@@ -140,6 +241,8 @@ abstract class Broadcaster implements BroadcasterContract
|
||||
* @param mixed $value
|
||||
* @param array $callbackParameters
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
*/
|
||||
protected function resolveImplicitBindingIfPossible($key, $value, $callbackParameters)
|
||||
{
|
||||
@@ -148,11 +251,13 @@ abstract class Broadcaster implements BroadcasterContract
|
||||
continue;
|
||||
}
|
||||
|
||||
$model = $parameter->getClass()->newInstance();
|
||||
$className = Reflector::getParameterClassName($parameter);
|
||||
|
||||
return $model->where($model->getRouteKeyName(), $value)->firstOr(function () {
|
||||
throw new HttpException(403);
|
||||
});
|
||||
if (is_null($model = (new $className)->resolveRouteBinding($value))) {
|
||||
throw new AccessDeniedHttpException;
|
||||
}
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
return $value;
|
||||
@@ -162,13 +267,13 @@ abstract class Broadcaster implements BroadcasterContract
|
||||
* Determine if a given key and parameter is implicitly bindable.
|
||||
*
|
||||
* @param string $key
|
||||
* @param ReflectionParameter $parameter
|
||||
* @param \ReflectionParameter $parameter
|
||||
* @return bool
|
||||
*/
|
||||
protected function isImplicitlyBindable($key, $parameter)
|
||||
{
|
||||
return $parameter->name === $key && $parameter->getClass() &&
|
||||
$parameter->getClass()->isSubclassOf(Model::class);
|
||||
return $parameter->getName() === $key &&
|
||||
Reflector::isParameterSubclassOf($parameter, UrlRoutable::class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,7 +292,7 @@ abstract class Broadcaster implements BroadcasterContract
|
||||
/**
|
||||
* Get the model binding registrar instance.
|
||||
*
|
||||
* @return BindingRegistrar
|
||||
* @return \Illuminate\Contracts\Routing\BindingRegistrar
|
||||
*/
|
||||
protected function binder()
|
||||
{
|
||||
@@ -198,4 +303,74 @@ abstract class Broadcaster implements BroadcasterContract
|
||||
|
||||
return $this->bindingRegistrar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the given callback into a callable.
|
||||
*
|
||||
* @param mixed $callback
|
||||
* @return callable
|
||||
*/
|
||||
protected function normalizeChannelHandlerToCallable($callback)
|
||||
{
|
||||
return is_callable($callback) ? $callback : function (...$args) use ($callback) {
|
||||
return Container::getInstance()
|
||||
->make($callback)
|
||||
->join(...$args);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the authenticated user using the configured guard (if any).
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $channel
|
||||
* @return mixed
|
||||
*/
|
||||
protected function retrieveUser($request, $channel)
|
||||
{
|
||||
$options = $this->retrieveChannelOptions($channel);
|
||||
|
||||
$guards = $options['guards'] ?? null;
|
||||
|
||||
if (is_null($guards)) {
|
||||
return $request->user();
|
||||
}
|
||||
|
||||
foreach (Arr::wrap($guards) as $guard) {
|
||||
if ($user = $request->user($guard)) {
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve options for a certain channel.
|
||||
*
|
||||
* @param string $channel
|
||||
* @return array
|
||||
*/
|
||||
protected function retrieveChannelOptions($channel)
|
||||
{
|
||||
foreach ($this->channelOptions as $pattern => $options) {
|
||||
if (! $this->channelNameMatchesPattern($channel, $pattern)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the channel name from the request matches a pattern from registered channels.
|
||||
*
|
||||
* @param string $channel
|
||||
* @param string $pattern
|
||||
* @return bool
|
||||
*/
|
||||
protected function channelNameMatchesPattern($channel, $pattern)
|
||||
{
|
||||
return preg_match('/'.preg_replace('/\{(.*?)\}/', '([^\.]+)', $pattern).'$/', $channel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,25 +2,28 @@
|
||||
|
||||
namespace Illuminate\Broadcasting\Broadcasters;
|
||||
|
||||
use Pusher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Broadcasting\BroadcastException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Pusher\ApiErrorException;
|
||||
use Pusher\Pusher;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
class PusherBroadcaster extends Broadcaster
|
||||
{
|
||||
use UsePusherChannelConventions;
|
||||
|
||||
/**
|
||||
* The Pusher SDK instance.
|
||||
*
|
||||
* @var \Pusher
|
||||
* @var \Pusher\Pusher
|
||||
*/
|
||||
protected $pusher;
|
||||
|
||||
/**
|
||||
* Create a new broadcaster instance.
|
||||
*
|
||||
* @param \Pusher $pusher
|
||||
* @param \Pusher\Pusher $pusher
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Pusher $pusher)
|
||||
@@ -28,22 +31,56 @@ class PusherBroadcaster extends Broadcaster
|
||||
$this->pusher = $pusher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the authenticated user payload for an incoming connection request.
|
||||
*
|
||||
* See: https://pusher.com/docs/channels/library_auth_reference/auth-signatures/#user-authentication
|
||||
* See: https://pusher.com/docs/channels/server_api/authenticating-users/#response
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array|null
|
||||
*/
|
||||
public function resolveAuthenticatedUser($request)
|
||||
{
|
||||
if (! $user = parent::resolveAuthenticatedUser($request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (method_exists($this->pusher, 'authenticateUser')) {
|
||||
return $this->pusher->authenticateUser($request->socket_id, $user);
|
||||
}
|
||||
|
||||
$settings = $this->pusher->getSettings();
|
||||
$encodedUser = json_encode($user);
|
||||
$decodedString = "{$request->socket_id}::user::{$encodedUser}";
|
||||
|
||||
$auth = $settings['auth_key'].':'.hash_hmac(
|
||||
'sha256', $decodedString, $settings['secret']
|
||||
);
|
||||
|
||||
return [
|
||||
'auth' => $auth,
|
||||
'user_data' => $encodedUser,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the incoming request for a given channel.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
*/
|
||||
public function auth($request)
|
||||
{
|
||||
if (Str::startsWith($request->channel_name, ['private-', 'presence-']) &&
|
||||
! $request->user()) {
|
||||
throw new HttpException(403);
|
||||
}
|
||||
$channelName = $this->normalizeChannelName($request->channel_name);
|
||||
|
||||
$channelName = Str::startsWith($request->channel_name, 'private-')
|
||||
? Str::replaceFirst('private-', '', $request->channel_name)
|
||||
: Str::replaceFirst('presence-', '', $request->channel_name);
|
||||
if (empty($request->channel_name) ||
|
||||
($this->isGuardedChannel($request->channel_name) &&
|
||||
! $this->retrieveUser($request, $channelName))) {
|
||||
throw new AccessDeniedHttpException;
|
||||
}
|
||||
|
||||
return parent::verifyUserCanAccessChannel(
|
||||
$request, $channelName
|
||||
@@ -59,27 +96,46 @@ class PusherBroadcaster extends Broadcaster
|
||||
*/
|
||||
public function validAuthenticationResponse($request, $result)
|
||||
{
|
||||
if (Str::startsWith($request->channel_name, 'private')) {
|
||||
if (str_starts_with($request->channel_name, 'private')) {
|
||||
return $this->decodePusherResponse(
|
||||
$this->pusher->socket_auth($request->channel_name, $request->socket_id)
|
||||
$request,
|
||||
method_exists($this->pusher, 'authorizeChannel')
|
||||
? $this->pusher->authorizeChannel($request->channel_name, $request->socket_id)
|
||||
: $this->pusher->socket_auth($request->channel_name, $request->socket_id)
|
||||
);
|
||||
}
|
||||
|
||||
$channelName = $this->normalizeChannelName($request->channel_name);
|
||||
|
||||
$user = $this->retrieveUser($request, $channelName);
|
||||
|
||||
$broadcastIdentifier = method_exists($user, 'getAuthIdentifierForBroadcasting')
|
||||
? $user->getAuthIdentifierForBroadcasting()
|
||||
: $user->getAuthIdentifier();
|
||||
|
||||
return $this->decodePusherResponse(
|
||||
$this->pusher->presence_auth(
|
||||
$request->channel_name, $request->socket_id, $request->user()->getAuthIdentifier(), $result)
|
||||
$request,
|
||||
method_exists($this->pusher, 'authorizePresenceChannel')
|
||||
? $this->pusher->authorizePresenceChannel($request->channel_name, $request->socket_id, $broadcastIdentifier, $result)
|
||||
: $this->pusher->presence_auth($request->channel_name, $request->socket_id, $broadcastIdentifier, $result)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the given Pusher response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param mixed $response
|
||||
* @return array
|
||||
*/
|
||||
protected function decodePusherResponse($response)
|
||||
protected function decodePusherResponse($request, $response)
|
||||
{
|
||||
return json_decode($response, true);
|
||||
if (! $request->input('callback', false)) {
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
return response()->json(json_decode($response, true))
|
||||
->withCallback($request->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,32 +145,46 @@ class PusherBroadcaster extends Broadcaster
|
||||
* @param string $event
|
||||
* @param array $payload
|
||||
* @return void
|
||||
*
|
||||
* @throws \Illuminate\Broadcasting\BroadcastException
|
||||
*/
|
||||
public function broadcast(array $channels, $event, array $payload = [])
|
||||
{
|
||||
$socket = Arr::pull($payload, 'socket');
|
||||
|
||||
$response = $this->pusher->trigger(
|
||||
$this->formatChannels($channels), $event, $payload, $socket, true
|
||||
);
|
||||
$parameters = $socket !== null ? ['socket_id' => $socket] : [];
|
||||
|
||||
if ((is_array($response) && $response['status'] >= 200 && $response['status'] <= 299)
|
||||
|| $response === true) {
|
||||
return;
|
||||
$channels = Collection::make($this->formatChannels($channels));
|
||||
|
||||
try {
|
||||
$channels->chunk(100)->each(function ($channels) use ($event, $payload, $parameters) {
|
||||
$this->pusher->trigger($channels->toArray(), $event, $payload, $parameters);
|
||||
});
|
||||
} catch (ApiErrorException $e) {
|
||||
throw new BroadcastException(
|
||||
sprintf('Pusher error: %s.', $e->getMessage())
|
||||
);
|
||||
}
|
||||
|
||||
throw new BroadcastException(
|
||||
is_bool($response) ? 'Failed to connect to Pusher.' : $response['body']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Pusher SDK instance.
|
||||
*
|
||||
* @return \Pusher
|
||||
* @return \Pusher\Pusher
|
||||
*/
|
||||
public function getPusher()
|
||||
{
|
||||
return $this->pusher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Pusher SDK instance.
|
||||
*
|
||||
* @param \Pusher\Pusher $pusher
|
||||
* @return void
|
||||
*/
|
||||
public function setPusher($pusher)
|
||||
{
|
||||
$this->pusher = $pusher;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
|
||||
namespace Illuminate\Broadcasting\Broadcasters;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Broadcasting\BroadcastException;
|
||||
use Illuminate\Contracts\Redis\Factory as Redis;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Illuminate\Support\Arr;
|
||||
use Predis\Connection\ConnectionException;
|
||||
use RedisException;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
class RedisBroadcaster extends Broadcaster
|
||||
{
|
||||
use UsePusherChannelConventions;
|
||||
|
||||
/**
|
||||
* The Redis instance.
|
||||
*
|
||||
@@ -19,20 +23,29 @@ class RedisBroadcaster extends Broadcaster
|
||||
/**
|
||||
* The Redis connection to use for broadcasting.
|
||||
*
|
||||
* @var ?string
|
||||
*/
|
||||
protected $connection = null;
|
||||
|
||||
/**
|
||||
* The Redis key prefix.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $connection;
|
||||
protected $prefix = '';
|
||||
|
||||
/**
|
||||
* Create a new broadcaster instance.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Redis\Factory $redis
|
||||
* @param string $connection
|
||||
* @param string|null $connection
|
||||
* @param string $prefix
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Redis $redis, $connection = null)
|
||||
public function __construct(Redis $redis, $connection = null, $prefix = '')
|
||||
{
|
||||
$this->redis = $redis;
|
||||
$this->prefix = $prefix;
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
@@ -41,17 +54,20 @@ class RedisBroadcaster extends Broadcaster
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
*/
|
||||
public function auth($request)
|
||||
{
|
||||
if (Str::startsWith($request->channel_name, ['private-', 'presence-']) &&
|
||||
! $request->user()) {
|
||||
throw new HttpException(403);
|
||||
}
|
||||
$channelName = $this->normalizeChannelName(
|
||||
str_replace($this->prefix, '', $request->channel_name)
|
||||
);
|
||||
|
||||
$channelName = Str::startsWith($request->channel_name, 'private-')
|
||||
? Str::replaceFirst('private-', '', $request->channel_name)
|
||||
: Str::replaceFirst('presence-', '', $request->channel_name);
|
||||
if (empty($request->channel_name) ||
|
||||
($this->isGuardedChannel($request->channel_name) &&
|
||||
! $this->retrieveUser($request, $channelName))) {
|
||||
throw new AccessDeniedHttpException;
|
||||
}
|
||||
|
||||
return parent::verifyUserCanAccessChannel(
|
||||
$request, $channelName
|
||||
@@ -71,8 +87,16 @@ class RedisBroadcaster extends Broadcaster
|
||||
return json_encode($result);
|
||||
}
|
||||
|
||||
$channelName = $this->normalizeChannelName($request->channel_name);
|
||||
|
||||
$user = $this->retrieveUser($request, $channelName);
|
||||
|
||||
$broadcastIdentifier = method_exists($user, 'getAuthIdentifierForBroadcasting')
|
||||
? $user->getAuthIdentifierForBroadcasting()
|
||||
: $user->getAuthIdentifier();
|
||||
|
||||
return json_encode(['channel_data' => [
|
||||
'user_id' => $request->user()->getAuthIdentifier(),
|
||||
'user_id' => $broadcastIdentifier,
|
||||
'user_info' => $result,
|
||||
]]);
|
||||
}
|
||||
@@ -84,9 +108,15 @@ class RedisBroadcaster extends Broadcaster
|
||||
* @param string $event
|
||||
* @param array $payload
|
||||
* @return void
|
||||
*
|
||||
* @throws \Illuminate\Broadcasting\BroadcastException
|
||||
*/
|
||||
public function broadcast(array $channels, $event, array $payload = [])
|
||||
{
|
||||
if (empty($channels)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$connection = $this->redis->connection($this->connection);
|
||||
|
||||
$payload = json_encode([
|
||||
@@ -95,8 +125,45 @@ class RedisBroadcaster extends Broadcaster
|
||||
'socket' => Arr::pull($payload, 'socket'),
|
||||
]);
|
||||
|
||||
foreach ($this->formatChannels($channels) as $channel) {
|
||||
$connection->publish($channel, $payload);
|
||||
try {
|
||||
$connection->eval(
|
||||
$this->broadcastMultipleChannelsScript(),
|
||||
0, $payload, ...$this->formatChannels($channels)
|
||||
);
|
||||
} catch (ConnectionException|RedisException $e) {
|
||||
throw new BroadcastException(
|
||||
sprintf('Redis error: %s.', $e->getMessage())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Lua script for broadcasting to multiple channels.
|
||||
*
|
||||
* ARGV[1] - The payload
|
||||
* ARGV[2...] - The channels
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function broadcastMultipleChannelsScript()
|
||||
{
|
||||
return <<<'LUA'
|
||||
for i = 2, #ARGV do
|
||||
redis.call('publish', ARGV[i], ARGV[1])
|
||||
end
|
||||
LUA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the channel array into an array of strings.
|
||||
*
|
||||
* @param array $channels
|
||||
* @return array
|
||||
*/
|
||||
protected function formatChannels(array $channels)
|
||||
{
|
||||
return array_map(function ($channel) {
|
||||
return $this->prefix.$channel;
|
||||
}, parent::formatChannels($channels));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Broadcasting\Broadcasters;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait UsePusherChannelConventions
|
||||
{
|
||||
/**
|
||||
* Return true if the channel is protected by authentication.
|
||||
*
|
||||
* @param string $channel
|
||||
* @return bool
|
||||
*/
|
||||
public function isGuardedChannel($channel)
|
||||
{
|
||||
return Str::startsWith($channel, ['private-', 'presence-']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove prefix from channel name.
|
||||
*
|
||||
* @param string $channel
|
||||
* @return string
|
||||
*/
|
||||
public function normalizeChannelName($channel)
|
||||
{
|
||||
foreach (['private-encrypted-', 'private-', 'presence-'] as $prefix) {
|
||||
if (Str::startsWith($channel, $prefix)) {
|
||||
return Str::replaceFirst($prefix, '', $channel);
|
||||
}
|
||||
}
|
||||
|
||||
return $channel;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Illuminate\Broadcasting;
|
||||
|
||||
use Illuminate\Contracts\Broadcasting\HasBroadcastChannel;
|
||||
|
||||
class Channel
|
||||
{
|
||||
/**
|
||||
@@ -14,12 +16,12 @@ class Channel
|
||||
/**
|
||||
* Create a new channel instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param \Illuminate\Contracts\Broadcasting\HasBroadcastChannel|string $name
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->name = $name instanceof HasBroadcastChannel ? $name->broadcastChannel() : $name;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
17
vendor/laravel/framework/src/Illuminate/Broadcasting/EncryptedPrivateChannel.php
vendored
Normal file
17
vendor/laravel/framework/src/Illuminate/Broadcasting/EncryptedPrivateChannel.php
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Broadcasting;
|
||||
|
||||
class EncryptedPrivateChannel extends Channel
|
||||
{
|
||||
/**
|
||||
* Create a new channel instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($name)
|
||||
{
|
||||
parent::__construct('private-encrypted-'.$name);
|
||||
}
|
||||
}
|
||||
40
vendor/laravel/framework/src/Illuminate/Broadcasting/InteractsWithBroadcasting.php
vendored
Normal file
40
vendor/laravel/framework/src/Illuminate/Broadcasting/InteractsWithBroadcasting.php
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Broadcasting;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
trait InteractsWithBroadcasting
|
||||
{
|
||||
/**
|
||||
* The broadcaster connection to use to broadcast the event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $broadcastConnection = [null];
|
||||
|
||||
/**
|
||||
* Broadcast the event using a specific broadcaster.
|
||||
*
|
||||
* @param array|string|null $connection
|
||||
* @return $this
|
||||
*/
|
||||
public function broadcastVia($connection = null)
|
||||
{
|
||||
$this->broadcastConnection = is_null($connection)
|
||||
? [null]
|
||||
: Arr::wrap($connection);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the broadcaster connections the event should be broadcast on.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function broadcastConnections()
|
||||
{
|
||||
return $this->broadcastConnection;
|
||||
}
|
||||
}
|
||||
21
vendor/laravel/framework/src/Illuminate/Broadcasting/LICENSE.md
vendored
Normal file
21
vendor/laravel/framework/src/Illuminate/Broadcasting/LICENSE.md
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Taylor Otwell
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -33,6 +33,21 @@ class PendingBroadcast
|
||||
$this->events = $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast the event using a specific broadcaster.
|
||||
*
|
||||
* @param string|null $connection
|
||||
* @return $this
|
||||
*/
|
||||
public function via($connection = null)
|
||||
{
|
||||
if (method_exists($this->event, 'broadcastVia')) {
|
||||
$this->event->broadcastVia($connection);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast the event to everyone except the current user.
|
||||
*
|
||||
|
||||
@@ -2,16 +2,20 @@
|
||||
|
||||
namespace Illuminate\Broadcasting;
|
||||
|
||||
use Illuminate\Contracts\Broadcasting\HasBroadcastChannel;
|
||||
|
||||
class PrivateChannel extends Channel
|
||||
{
|
||||
/**
|
||||
* Create a new channel instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param \Illuminate\Contracts\Broadcasting\HasBroadcastChannel|string $name
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($name)
|
||||
{
|
||||
$name = $name instanceof HasBroadcastChannel ? $name->broadcastChannel() : $name;
|
||||
|
||||
parent::__construct('private-'.$name);
|
||||
}
|
||||
}
|
||||
|
||||
61
vendor/laravel/framework/src/Illuminate/Broadcasting/UniqueBroadcastEvent.php
vendored
Normal file
61
vendor/laravel/framework/src/Illuminate/Broadcasting/UniqueBroadcastEvent.php
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Broadcasting;
|
||||
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Contracts\Cache\Repository;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
|
||||
class UniqueBroadcastEvent extends BroadcastEvent implements ShouldBeUnique
|
||||
{
|
||||
/**
|
||||
* The unique lock identifier.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $uniqueId;
|
||||
|
||||
/**
|
||||
* The number of seconds the unique lock should be maintained.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $uniqueFor;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param mixed $event
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($event)
|
||||
{
|
||||
$this->uniqueId = get_class($event);
|
||||
|
||||
if (method_exists($event, 'uniqueId')) {
|
||||
$this->uniqueId .= $event->uniqueId();
|
||||
} elseif (property_exists($event, 'uniqueId')) {
|
||||
$this->uniqueId .= $event->uniqueId;
|
||||
}
|
||||
|
||||
if (method_exists($event, 'uniqueFor')) {
|
||||
$this->uniqueFor = $event->uniqueFor();
|
||||
} elseif (property_exists($event, 'uniqueFor')) {
|
||||
$this->uniqueFor = $event->uniqueFor;
|
||||
}
|
||||
|
||||
parent::__construct($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the cache implementation that should manage the event's uniqueness.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Cache\Repository
|
||||
*/
|
||||
public function uniqueVia()
|
||||
{
|
||||
return method_exists($this->event, 'uniqueVia')
|
||||
? $this->event->uniqueVia()
|
||||
: Container::getInstance()->make(Repository::class);
|
||||
}
|
||||
}
|
||||
@@ -14,11 +14,15 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.6.4",
|
||||
"illuminate/bus": "5.4.*",
|
||||
"illuminate/contracts": "5.4.*",
|
||||
"illuminate/queue": "5.4.*",
|
||||
"illuminate/support": "5.4.*"
|
||||
"php": "^8.0.2",
|
||||
"ext-json": "*",
|
||||
"psr/log": "^1.0|^2.0|^3.0",
|
||||
"illuminate/bus": "^9.0",
|
||||
"illuminate/collections": "^9.0",
|
||||
"illuminate/container": "^9.0",
|
||||
"illuminate/contracts": "^9.0",
|
||||
"illuminate/queue": "^9.0",
|
||||
"illuminate/support": "^9.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -27,11 +31,12 @@
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.4-dev"
|
||||
"dev-master": "9.x-dev"
|
||||
}
|
||||
},
|
||||
"suggest": {
|
||||
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~2.0)."
|
||||
"ably/ably-php": "Required to use the Ably broadcast driver (^1.0).",
|
||||
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0)."
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
|
||||
Reference in New Issue
Block a user