Skip to content

Commit

Permalink
Adds support for Pusher cache channels (#25)
Browse files Browse the repository at this point in the history
* adds cache channels

* send empty cache message

* stub private and presence cache channels

* wip

* Fix code styling
  • Loading branch information
joedixon authored Nov 28, 2023
1 parent eed918c commit a900fb1
Show file tree
Hide file tree
Showing 18 changed files with 706 additions and 114 deletions.
47 changes: 47 additions & 0 deletions src/Channels/CacheChannel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace Laravel\Reverb\Channels;

use Laravel\Reverb\Contracts\Connection;

class CacheChannel extends Channel
{
/**
* Data from last event triggered.
*/
protected ?array $payload = null;

/**
* Send a message to all connections subscribed to the channel.
*/
public function broadcast(array $payload, Connection $except = null): void
{
$this->payload = $payload;

parent::broadcast($payload, $except);
}

/**
* Broadcast a message triggered from an internal source.
*/
public function broadcastInternally(array $payload, Connection $except = null): void
{
parent::broadcast($payload, $except);
}

/**
* Determine if the channel has a cached payload.
*/
public function hasCachedPayload(): bool
{
return $this->payload !== null;
}

/**
* Get the cached payload.
*/
public function cachedPayload(): ?array
{
return $this->payload;
}
}
8 changes: 8 additions & 0 deletions src/Channels/Channel.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ public function broadcast(array $payload, Connection $except = null): void
}
}

/**
* Broadcast a message triggered from an internal source.
*/
public function broadcastInternally(array $payload, Connection $except = null): void
{
$this->broadcast($payload, $except);
}

/**
* Get the data associated with the channel.
*/
Expand Down
9 changes: 6 additions & 3 deletions src/Channels/ChannelBroker.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ class ChannelBroker
*/
public static function create(string $name): Channel
{
return match (Str::before($name, '-')) {
'private' => new PrivateChannel($name),
'presence' => new PresenceChannel($name),
return match (true) {
Str::startsWith($name, 'private-cache-') => new PrivateCacheChannel($name),
Str::startsWith($name, 'presence-cache-') => new PresenceCacheChannel($name),
Str::startsWith($name, 'cache') => new CacheChannel($name),
Str::startsWith($name, 'private') => new PrivateChannel($name),
Str::startsWith($name, 'presence') => new PresenceChannel($name),
default => new Channel($name),
};
}
Expand Down
81 changes: 81 additions & 0 deletions src/Channels/Concerns/InteractsWithPresenceChannels.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

namespace Laravel\Reverb\Channels\Concerns;

use Laravel\Reverb\Contracts\Connection;

trait InteractsWithPresenceChannels
{
use InteractsWithPrivateChannels;

/**
* Subscribe to the given channel.
*/
public function subscribe(Connection $connection, string $auth = null, string $data = null): void
{
$this->verify($connection, $auth, $data);

parent::subscribe($connection, $auth, $data);

parent::broadcastInternally(
[
'event' => 'pusher_internal:member_added',
'data' => $data ? json_decode($data, true) : [],
'channel' => $this->name(),
],
$connection
);
}

/**
* Unsubscribe from the given channel.
*/
public function unsubscribe(Connection $connection): void
{
if (! $subscription = $this->connections->find($connection)) {
parent::unsubscribe($connection);

return;
}

if ($userId = $subscription->data('user_id')) {
parent::broadcast(
[
'event' => 'pusher_internal:member_removed',
'data' => ['user_id' => $userId],
'channel' => $this->name(),
],
$connection
);
}

parent::unsubscribe($connection);
}

/**
* Get the data associated with the channel.
*/
public function data(): array
{
$connections = collect($this->connections->all())
->map(fn ($connection) => $connection->data());

if ($connections->contains(fn ($connection) => ! isset($connection['user_id']))) {
return [
'presence' => [
'count' => 0,
'ids' => [],
'hash' => [],
],
];
}

return [
'presence' => [
'count' => $connections->count() ?? 0,
'ids' => $connections->map(fn ($connection) => $connection['user_id'])->all(),
'hash' => $connections->keyBy('user_id')->map->user_info->toArray(),
],
];
}
}
45 changes: 45 additions & 0 deletions src/Channels/Concerns/InteractsWithPrivateChannels.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace Laravel\Reverb\Channels\Concerns;

use Illuminate\Support\Str;
use Laravel\Reverb\Contracts\Connection;
use Laravel\Reverb\Exceptions\ConnectionUnauthorized;

trait InteractsWithPrivateChannels
{
/**
* Subscribe to the given channel.
*/
public function subscribe(Connection $connection, string $auth = null, string $data = null): void
{
$this->verify($connection, $auth, $data);

parent::subscribe($connection, $auth, $data);
}

/**
* Deteremine whether the given auth token is valid.
*/
protected function verify(Connection $connection, string $auth, string $data = null): bool
{
$signature = "{$connection->id()}:{$this->name()}";

if ($data) {
$signature .= ":{$data}";
}

if (! hash_equals(
hash_hmac(
'sha256',
$signature,
$connection->app()->secret(),
),
Str::after($auth, ':')
)) {
throw new ConnectionUnauthorized;
}

return true;
}
}
10 changes: 10 additions & 0 deletions src/Channels/PresenceCacheChannel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Laravel\Reverb\Channels;

use Laravel\Reverb\Channels\Concerns\InteractsWithPresenceChannels;

class PresenceCacheChannel extends CacheChannel
{
use InteractsWithPresenceChannels;
}
71 changes: 2 additions & 69 deletions src/Channels/PresenceChannel.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,76 +2,9 @@

namespace Laravel\Reverb\Channels;

use Laravel\Reverb\Contracts\Connection;
use Laravel\Reverb\Channels\Concerns\InteractsWithPresenceChannels;

class PresenceChannel extends PrivateChannel
{
/**
* Subscribe to the given channel.
*/
public function subscribe(Connection $connection, string $auth = null, string $data = null): void
{
parent::subscribe($connection, $auth, $data);

$this->broadcast(
[
'event' => 'pusher_internal:member_added',
'data' => $data ? json_decode($data, true) : [],
'channel' => $this->name(),
],
$connection
);
}

/**
* Unsubscribe from the given channel.
*/
public function unsubscribe(Connection $connection): void
{
if (! $subscription = $this->connections->find($connection)) {
parent::unsubscribe($connection);

return;
}

if ($userId = $subscription->data('user_id')) {
$this->broadcast(
[
'event' => 'pusher_internal:member_removed',
'data' => ['user_id' => $userId],
'channel' => $this->name(),
],
$connection
);
}

parent::unsubscribe($connection);
}

/**
* Get the data associated with the channel.
*/
public function data(): array
{
$connections = collect($this->connections->all())
->map(fn ($connection) => $connection->data());

if ($connections->contains(fn ($connection) => ! isset($connection['user_id']))) {
return [
'presence' => [
'count' => 0,
'ids' => [],
'hash' => [],
],
];
}

return [
'presence' => [
'count' => $connections->count() ?? 0,
'ids' => $connections->map(fn ($connection) => $connection['user_id'])->all(),
'hash' => $connections->keyBy('user_id')->map->user_info->toArray(),
],
];
}
use InteractsWithPresenceChannels;
}
10 changes: 10 additions & 0 deletions src/Channels/PrivateCacheChannel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Laravel\Reverb\Channels;

use Laravel\Reverb\Channels\Concerns\InteractsWithPrivateChannels;

class PrivateCacheChannel extends CacheChannel
{
use InteractsWithPrivateChannels;
}
39 changes: 2 additions & 37 deletions src/Channels/PrivateChannel.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,9 @@

namespace Laravel\Reverb\Channels;

use Illuminate\Support\Str;
use Laravel\Reverb\Contracts\Connection;
use Laravel\Reverb\Exceptions\ConnectionUnauthorized;
use Laravel\Reverb\Channels\Concerns\InteractsWithPrivateChannels;

class PrivateChannel extends Channel
{
/**
* Subscribe to the given channel.
*/
public function subscribe(Connection $connection, string $auth = null, string $data = null): void
{
$this->verify($connection, $auth, $data);

parent::subscribe($connection, $auth, $data);
}

/**
* Deteremine whether the given auth token is valid.
*/
protected function verify(Connection $connection, string $auth, string $data = null): bool
{
$signature = "{$connection->id()}:{$this->name()}";

if ($data) {
$signature .= ":{$data}";
}

if (! hash_equals(
hash_hmac(
'sha256',
$signature,
$connection->app()->secret(),
),
Str::after($auth, ':')
)) {
throw new ConnectionUnauthorized;
}

return true;
}
use InteractsWithPrivateChannels;
}
Loading

0 comments on commit a900fb1

Please sign in to comment.