AblyBroadcaster.php
TLDR
This file, AblyBroadcaster.php
, is a part of the Illuminate\Broadcasting\Broadcasters
namespace in the Demo Projects
project. It is a class that extends the Broadcaster
class and provides methods for authenticating requests, broadcasting messages, generating Ably authentication signatures, and formatting channel names. It also has methods for retrieving the public and private tokens from the Ably key and for getting the underlying Ably SDK instance.
Methods
auth
This method is used to authenticate an incoming request for a given channel. It takes an Illuminate\Http\Request
object as a parameter and throws an AccessDeniedHttpException
if the channel name is empty or the user does not have access to the guarded channel.
validAuthenticationResponse
This method is used to return the valid authentication response. It takes an Illuminate\Http\Request
object and a result as parameters. If the channel name starts with "private", it generates an Ably signature and returns it as part of the response. Otherwise, it retrieves the user, generates an Ably signature and returns the signature and user data as part of the response.
generateAblySignature
This method is used to generate the signature needed for Ably authentication headers. It takes the channel name, socket ID, and optional user data as parameters and returns the generated signature.
broadcast
This method is used to broadcast an event to the specified channels. It takes an array of channels, an event name, and an optional payload as parameters. It uses the AblyRest SDK instance to publish the event to each channel.
buildAblyMessage
This method is used to build an Ably message object for broadcasting. It takes an event name and an optional payload as parameters and returns an AblyMessage object with the event name, payload, and connection key.
isGuardedChannel
This method is used to check if a channel is protected by authentication. It takes a channel name as a parameter and returns true if the channel starts with "private-" or "presence-".
normalizeChannelName
This method is used to remove the prefix from a channel name. It takes a channel name as a parameter and returns the normalized channel name by removing "private-" or "presence-" if present.
formatChannels
This method is used to format an array of channels into an array of strings. It takes an array of channels as a parameter and returns an array of formatted channel names.
getPublicToken
This method is used to get the public token value from the Ably key. It returns the part of the Ably key before the ":".
getPrivateToken
This method is used to get the private token value from the Ably key. It returns the part of the Ably key after the ":".
getAbly
This method is used to get the underlying Ably SDK instance. It returns the AblyRest object.
Classes
This file does not define any additional classes.
<?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 string
*/
protected function getPublicToken()
{
return Str::before($this->ably->options->key, ':');
}
/**
* Get the private token value from the Ably key.
*
* @return string
*/
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;
}
}