Skip to content

Commit

Permalink
Fixes presence data (#19)
Browse files Browse the repository at this point in the history
* store connection data

* fix presence channels

* fix presence test

* wip

* Fix code styling

* fix tests

* wip

* wip

* wip

* wip

* wip

* Fix code styling

* fix test

* Fix code styling

* fix test

* wip

* formatting
  • Loading branch information
joedixon authored Nov 20, 2023
1 parent 9470b39 commit bde198e
Show file tree
Hide file tree
Showing 25 changed files with 234 additions and 129 deletions.
14 changes: 11 additions & 3 deletions src/Channels/Channel.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,26 @@ public function unsubscribe(Connection $connection): void
$this->connections->remove($connection);
}

/**
* Determine if the connection is subscribed to the channel.
*/
public function subscribed(Connection $connection): bool
{
return $this->connections->find($connection) !== null;
}

/**
* Send a message to all connections subscribed to the channel.
*/
public function broadcast(Application $app, array $payload, Connection $except = null): void
{
collect($this->connections())
->each(function ($connection) use ($payload, $except) {
if ($except && $except->identifier() === $connection->identifier()) {
if ($except && $except->identifier() === $connection->connection()->identifier()) {
return;
}

if (isset($payload['except']) && $payload['except'] === $connection->identifier()) {
if (isset($payload['except']) && $payload['except'] === $connection->connection()->identifier()) {
return;
}

Expand All @@ -86,7 +94,7 @@ public function broadcast(Application $app, array $payload, Connection $except =
/**
* Get the data associated with the channel.
*/
public function data(Application $app): array
public function data(): array
{
return [];
}
Expand Down
36 changes: 22 additions & 14 deletions src/Channels/PresenceChannel.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

namespace Laravel\Reverb\Channels;

use Illuminate\Support\Facades\App;
use Laravel\Reverb\Application;
use Laravel\Reverb\Contracts\ChannelManager;
use Laravel\Reverb\Contracts\Connection;

class PresenceChannel extends PrivateChannel
Expand Down Expand Up @@ -32,16 +29,18 @@ public function subscribe(Connection $connection, string $auth = null, string $d
*/
public function unsubscribe(Connection $connection): void
{
$data = App::make(ChannelManager::class)
->for($connection->app())
->data($this, $connection);
if (! $subscription = $this->connections->find($connection)) {
parent::unsubscribe($connection);

if (isset($data['user_id'])) {
return;
}

if ($userId = $subscription->data('user_id')) {
$this->broadcast(
$connection->app(),
[
'event' => 'pusher_internal:member_removed',
'data' => ['user_id' => $data['user_id']],
'data' => ['user_id' => $userId],
'channel' => $this->name(),
],
$connection
Expand All @@ -54,16 +53,25 @@ public function unsubscribe(Connection $connection): void
/**
* Get the data associated with the channel.
*/
public function data(Application $app): array
public function data(): array
{
$connections = App::make(ChannelManager::class)
->for($app)
->connectionKeys($this);
$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(),
'ids' => $connections->map(fn ($connection) => $connection['user_id'])->toArray(),
'count' => $connections->count() ?? 0,
'ids' => $connections->map(fn ($connection) => $connection['user_id'])->all(),
'hash' => $connections->keyBy('user_id')->map->user_info->toArray(),
],
];
Expand Down
11 changes: 10 additions & 1 deletion src/Contracts/ChannelConnectionManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,29 @@

namespace Laravel\Reverb\Contracts;

use Laravel\Reverb\Servers\Reverb\ChannelConnection;

interface ChannelConnectionManager
{
/**
* Add a connection.
*/
public function add(Connection $connection): void;
public function add(Connection $connection, array $data): void;

/**
* Remove a connection.
*/
public function remove(Connection $connection): void;

/**
* Find a connection by its identifier.
*/
public function find(Connection $connection): ?ChannelConnection;

/**
* Get all the connections.
*
* @return array<string, \Laravel\Reverb\Servers\Reverb\ChannelConnection>
*/
public function all(): array;

Expand Down
15 changes: 12 additions & 3 deletions src/Managers/ArrayChannelConnectionManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,23 @@

use Laravel\Reverb\Contracts\ChannelConnectionManager;
use Laravel\Reverb\Contracts\Connection;
use Laravel\Reverb\Servers\Reverb\ChannelConnection;

class ArrayChannelConnectionManager implements ChannelConnectionManager
{
/**
* Connection store.
*
* @var array<string, array<string, \Laravel\Reverb\Connection>>
* @var array<string, \Laravel\Reverb\Servers\Reverb\ChannelConnection>
*/
protected $connections = [];

/**
* Add a connection.
*/
public function add(Connection $connection): void
public function add(Connection $connection, array $data): void
{
$this->connections[$connection->identifier()] = $connection;
$this->connections[$connection->identifier()] = new ChannelConnection($connection, $data);
}

/**
Expand All @@ -30,6 +31,14 @@ public function remove(Connection $connection): void
unset($this->connections[$connection->identifier()]);
}

/**
* Find a connection by its identifier.
*/
public function find(Connection $connection): ?ChannelConnection
{
return $this->connections[$connection->identifier()] ?? null;
}

/**
* Get all the connections.
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Pusher/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public static function subscribe(Connection $connection, string $channel, string

$channel->subscribe($connection, $auth, $data);

self::sendInternally($connection, 'subscription_succeeded', $channel->name(), $channel->data($connection->app()));
self::sendInternally($connection, 'subscription_succeeded', $channel->name(), $channel->data());
}

/**
Expand Down
7 changes: 6 additions & 1 deletion src/Pusher/Http/Controllers/ChannelUsersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ public function handle(RequestInterface $request, Connection $connection, ...$ar
return new JsonResponse((object) [], 400);
}

return new JsonResponse((object) []);
$connections = collect($channel->connections())
->map(fn ($connection) => $connection->data())
->map(fn ($data) => ['id' => $data['user_id']])
->values();

return new JsonResponse((object) ['users' => $connections]);
}
}
42 changes: 42 additions & 0 deletions src/Servers/Reverb/ChannelConnection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Laravel\Reverb\Servers\Reverb;

use Illuminate\Support\Arr;
use Laravel\Reverb\Contracts\Connection;

class ChannelConnection
{
public function __construct(protected Connection $connection, protected array $data = [])
{
//
}

/**
* Get the underlying connection.
*/
public function connection(): Connection
{
return $this->connection;
}

/**
* Get the connection data.
*/
public function data(string $key = null): mixed
{
if ($key) {
return Arr::get($this->data, $key);
}

return $this->data;
}

/**
* Call the method on the connection.
*/
public function __call(string $method, array $parameters): mixed
{
return $this->connection->{$method}(...$parameters);
}
}
16 changes: 0 additions & 16 deletions tests/Feature/Ratchet/ChannelUsersControllerTest.php

This file was deleted.

29 changes: 29 additions & 0 deletions tests/Feature/Reverb/ChannelUsersControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

use Laravel\Reverb\Contracts\ApplicationProvider;
use Laravel\Reverb\Contracts\ChannelManager;
use Laravel\Reverb\Tests\Connection;
use Laravel\Reverb\Tests\ReverbTestCase;
use React\Http\Message\ResponseException;

use function React\Async\await;

uses(ReverbTestCase::class);

it('returns an error when presence channel not provided', function () {
await($this->signedRequest('channels/test-channel/users'));
})->throws(ResponseException::class);

it('returns the user data', function () {
$channel = app(ChannelManager::class)
->for(app()->make(ApplicationProvider::class)->findByKey('pusher-key'))
->find('presence-test-channel');
$channel->subscribe($connection = new Connection('test-connection-one'), validAuth($connection, 'presence-test-channel', $data = json_encode(['user_id' => 1, 'user_info' => ['name' => 'Taylor']])), $data);
$channel->subscribe($connection = new Connection('test-connection-two'), validAuth($connection, 'presence-test-channel', $data = json_encode(['user_id' => 2, 'user_info' => ['name' => 'Joe']])), $data);
$channel->subscribe($connection = new Connection('test-connection-three'), validAuth($connection, 'presence-test-channel', $data = json_encode(['user_id' => 3, 'user_info' => ['name' => 'Jess']])), $data);

$response = await($this->signedRequest('channels/presence-test-channel/users'));

expect($response->getStatusCode())->toBe(200);
expect($response->getBody()->getContents())->toBe('{"users":[{"id":1},{"id":2},{"id":3}]}');
});
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@
it('can ignore a subscriber', function () {
$connection = $this->connect();
$this->subscribe('test-channel-two', connection: $connection);

$promiseOne = $this->messagePromise($connection);
$response = await($this->signedPostRequest('events', [
'name' => 'NewEvent',
'channels' => ['test-channel-one', 'test-channel-two'],
'data' => ['some' => 'data'],
]));
expect(await($promiseOne))->toBe('{"event":"NewEvent","data":{"some":"data"},"channel":"test-channel-two"}');

$promiseTwo = $this->messagePromise($connection);
$response = await($this->signedPostRequest('events', [
Expand All @@ -91,6 +91,5 @@

$this->assertSame(200, $response->getStatusCode());
$this->assertSame('{}', $response->getBody()->getContents());
expect(await($promiseOne))->toBe('{"event":"NewEvent","data":{"some":"data"},"channel":"test-channel-two"}');
expect(await($promiseTwo))->toBeFalse();
});
Loading

0 comments on commit bde198e

Please sign in to comment.