Skip to content

Commit c69b7b4

Browse files
authored
Adds Pusher routes (#16)
* close with message * ensure app exists * handle event trigger * scaffold routes * implement events controller * update phpunit config * implement batch events endpoint * implement channels route * wip * add channels test * use correct id * wip * revert * add channel endpoint * Fix code styling * implements users routes * implement terminate user endpoint * formatting
1 parent 2300297 commit c69b7b4

29 files changed

+835
-82
lines changed

composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
"pestphp/pest": "^2.0",
3535
"phpstan/phpstan": "^1.10",
3636
"ratchet/pawl": "^0.4.1",
37-
"react/async": "^4.0"
37+
"react/async": "^4.0",
38+
"react/http": "^1.9"
3839
},
3940
"autoload": {
4041
"psr-4": {

phpunit.xml.dist

+23-22
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" colors="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd">
3-
<testsuites>
4-
<testsuite name="Unit">
5-
<directory suffix="Test.php">./tests/Unit</directory>
6-
</testsuite>
7-
<testsuite name="Feature">
8-
<directory suffix="Test.php">./tests/Feature</directory>
9-
</testsuite>
10-
</testsuites>
11-
<coverage>
12-
<include>
13-
<directory suffix=".php">./src</directory>
14-
</include>
15-
</coverage>
16-
<php>
17-
<env name="APP_KEY" value="base64:uz4B1RtFO57QGzbZX1kRYX9hIRB50+QzqFeg9zbFJlY="/>
18-
<env name="PUSHER_APP_ID" value="123456"/>
19-
<env name="PUSHER_APP_KEY" value="pusher-key"/>
20-
<env name="PUSHER_APP_SECRET" value="pusher-secret"/>
21-
<env name="REVERB_API_GATEWAY_CONNECTION_CACHE" value="redis"/>
22-
</php>
23-
</phpunit>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" colors="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.4/phpunit.xsd">
3+
<testsuites>
4+
<testsuite name="Unit">
5+
<directory suffix="Test.php">./tests/Unit</directory>
6+
</testsuite>
7+
<testsuite name="Feature">
8+
<directory suffix="Test.php">./tests/Feature</directory>
9+
</testsuite>
10+
</testsuites>
11+
<coverage/>
12+
<php>
13+
<env name="APP_KEY" value="base64:uz4B1RtFO57QGzbZX1kRYX9hIRB50+QzqFeg9zbFJlY="/>
14+
<env name="PUSHER_APP_ID" value="123456"/>
15+
<env name="PUSHER_APP_KEY" value="pusher-key"/>
16+
<env name="PUSHER_APP_SECRET" value="pusher-secret"/>
17+
<env name="REVERB_API_GATEWAY_CONNECTION_CACHE" value="redis"/>
18+
</php>
19+
<source>
20+
<include>
21+
<directory suffix=".php">./src</directory>
22+
</include>
23+
</source>
24+
</phpunit>

phpunit.xml.dist.bak

-23
This file was deleted.

src/Channels/Channel.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ public function broadcast(Application $app, array $payload, Connection $except =
5151
{
5252
collect(App::make(ChannelManager::class)->for($app)->connections($this))
5353
->each(function ($connection) use ($payload, $except) {
54-
if ($except && $except->identifier() === $connection->identifier()) {
54+
if ($except && $except->id() === $connection->id()) {
5555
return;
5656
}
5757

58-
if (isset($payload['except']) && $payload['except'] === $connection->identifier()) {
58+
if (isset($payload['except']) && $payload['except'] === $connection->id()) {
5959
return;
6060
}
6161

src/ClientEvent.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public static function whisper(Connection $connection, array $payload): void
3333
{
3434
Event::dispatch(
3535
$connection->app(),
36-
$payload + ['except' => $connection->identifier()],
36+
$payload + ['except' => $connection->id()],
3737
$connection
3838
);
3939
}

src/Concerns/ClosesConnections.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ trait ClosesConnections
1111
/**
1212
* Close the connection.
1313
*/
14-
protected function close(Connection $connection, int $statusCode = 400, array $headers = []): void
14+
protected function close(Connection $connection, int $statusCode = 400, string $message = '', array $headers = []): void
1515
{
16-
$response = new Response($statusCode, $headers);
16+
$response = new Response($statusCode, $headers, $message);
1717

1818
$connection->send(Message::toString($response));
1919
$connection->close();

src/Contracts/Connection.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public function disconnect(): void
9797

9898
App::make(ConnectionManager::class)
9999
->for($this->app())
100-
->disconnect($this->identifier());
100+
->disconnect($this->id());
101101

102102
$this->terminate();
103103
}

src/Event.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Laravel\Reverb;
44

5+
use Illuminate\Support\Arr;
56
use Illuminate\Support\Facades\App;
67
use Laravel\Reverb\Channels\ChannelBroker;
78
use Laravel\Reverb\Contracts\Connection;
@@ -33,10 +34,12 @@ public static function dispatch(Application $app, array $payload, Connection $co
3334
*/
3435
public static function dispatchSynchronously(Application $app, array $payload, Connection $connection = null): void
3536
{
36-
$channels = isset($payload['channel']) ? [$payload['channel']] : $payload['channels'];
37+
$channels = Arr::wrap($payload['channels'] ?? $payload['channel'] ?? []);
3738

3839
foreach ($channels as $channel) {
40+
unset($payload['channels']);
3941
$channel = ChannelBroker::create($channel);
42+
$payload['channel'] = $channel->name();
4043

4144
$channel->broadcast($app, $payload, $connection);
4245
}

src/Http/Router.php

+9-3
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,18 @@ public function dispatch(RequestInterface $request, Connection $connection): mix
3838
try {
3939
$route = $this->matcher->match($uri->getPath());
4040
} catch (MethodNotAllowedException $e) {
41-
return $this->close($connection, 405, ['Allow' => $e->getAllowedMethods()]);
41+
return $this->close($connection, 405, 'Method now allowed', ['Allow' => $e->getAllowedMethods()]);
4242
} catch (ResourceNotFoundException $e) {
43-
return $this->close($connection, 404);
43+
return $this->close($connection, 404, 'Not found.');
4444
}
4545

46-
return $route['_controller']($request, $connection, ...Arr::except($route, ['_controller', '_route']));
46+
$response = $route['_controller']($request, $connection, ...Arr::except($route, ['_controller', '_route']));
47+
48+
if (! $this->isWebSocketRequest($request)) {
49+
return $connection->send($response)->close();
50+
}
51+
52+
return null;
4753
}
4854

4955
/**

src/Http/Server.php

+10-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,15 @@ public function start(): void
4545
$this->loop->run();
4646
}
4747

48+
/**
49+
* Stop the Http server
50+
*/
51+
public function stop(): void
52+
{
53+
$this->loop->stop();
54+
$this->socket->close();
55+
}
56+
4857
/**
4958
* Handle an incoming request.
5059
*/
@@ -71,7 +80,7 @@ protected function createRequest(string $message, Connection $connection): Reque
7180
try {
7281
return Request::from($message, $connection);
7382
} catch (OverflowException $e) {
74-
$this->close($connection, 413);
83+
$this->close($connection, 413, 'Payload too large.');
7584
}
7685
}
7786
}

src/Managers/ChannelManager.php

+8-4
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,15 @@ public function app(): ?Application
4545
*/
4646
public function subscribe(Channel $channel, Connection $connection, $data = []): void
4747
{
48-
$this->connections[$this->application->id()][$channel->name()][$connection->identifier()] = $connection;
48+
$this->connections[$this->application->id()][$channel->name()][$connection->id()] = $connection;
4949
}
5050

5151
/**
5252
* Unsubscribe from a channel.
5353
*/
5454
public function unsubscribe(Channel $channel, Connection $connection): void
5555
{
56-
unset($this->connections[$this->application->id()][$channel->name()][$connection->identifier()]);
56+
unset($this->connections[$this->application->id()][$channel->name()][$connection->id()]);
5757
}
5858

5959
/**
@@ -89,16 +89,20 @@ public function connections(Channel $channel): array
8989
/**
9090
* Get the given channel from the cache.
9191
*/
92-
protected function channel(Channel $channel): Collection
92+
public function channel(Channel $channel): Collection
9393
{
9494
return $this->channels($channel);
9595
}
9696

9797
/**
9898
* Get the channels from the cache.
9999
*/
100-
protected function channels(Channel $channel = null): Collection
100+
public function channels(Channel $channel = null): Collection
101101
{
102+
if (! isset($this->connections[$this->application->id()])) {
103+
$this->connections[$this->application->id()] = [];
104+
}
105+
102106
$channels = $this->connections[$this->application->id()];
103107

104108
if ($channel) {

src/Managers/ConnectionManager.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public function all(): array
104104
*/
105105
public function save(Connection $connection): void
106106
{
107-
$this->connections[$this->application->id()][$connection->identifier()] = $connection;
107+
$this->connections[$this->application->id()][$connection->id()] = $connection;
108108
}
109109

110110
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Laravel\Reverb\Pusher\Http\Controllers;
4+
5+
use Laravel\Reverb\Channels\ChannelBroker;
6+
use Laravel\Reverb\Http\Connection;
7+
use Psr\Http\Message\RequestInterface;
8+
use Symfony\Component\HttpFoundation\JsonResponse;
9+
use Symfony\Component\HttpFoundation\Response;
10+
11+
class ChannelController extends Controller
12+
{
13+
/**
14+
* Handle the request.
15+
*/
16+
public function handle(RequestInterface $request, Connection $connection, ...$args): Response
17+
{
18+
$info = explode(',', $this->query['info'] ?? '');
19+
$connections = $this->channels->channel(ChannelBroker::create($args['channel']));
20+
$totalConnections = count($connections);
21+
22+
return new JsonResponse((object) array_filter([
23+
'occupied' => $totalConnections > 0,
24+
'user_count' => in_array('user_count', $info) ? $totalConnections : null,
25+
'subscription_count' => in_array('subscription_count', $info) ? $totalConnections : null,
26+
'cache' => in_array('cache', $info) ? '{}' : null,
27+
], fn ($item) => $item !== null));
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Laravel\Reverb\Pusher\Http\Controllers;
4+
5+
use Laravel\Reverb\Channels\ChannelBroker;
6+
use Laravel\Reverb\Channels\PresenceChannel;
7+
use Laravel\Reverb\Http\Connection;
8+
use Psr\Http\Message\RequestInterface;
9+
use Symfony\Component\HttpFoundation\JsonResponse;
10+
use Symfony\Component\HttpFoundation\Response;
11+
12+
class ChannelUsersController extends Controller
13+
{
14+
/**
15+
* Handle the request.
16+
*/
17+
public function handle(RequestInterface $request, Connection $connection, ...$args): Response
18+
{
19+
$channel = ChannelBroker::create($args['channel']);
20+
21+
if (! $channel instanceof PresenceChannel) {
22+
return new JsonResponse((object) [], 400);
23+
}
24+
25+
return new JsonResponse((object) []);
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Laravel\Reverb\Pusher\Http\Controllers;
4+
5+
use Illuminate\Support\Str;
6+
use Laravel\Reverb\Http\Connection;
7+
use Psr\Http\Message\RequestInterface;
8+
use Symfony\Component\HttpFoundation\JsonResponse;
9+
use Symfony\Component\HttpFoundation\Response;
10+
11+
class ChannelsController extends Controller
12+
{
13+
/**
14+
* Handle the request.
15+
*/
16+
public function handle(RequestInterface $request, Connection $connection, ...$args): Response
17+
{
18+
$channels = $this->channels->channels();
19+
$info = explode(',', $this->query['info'] ?? '');
20+
21+
if (isset($this->query['filter_by_prefix'])) {
22+
$channels = $channels->filter(fn ($connections, $name) => Str::startsWith($name, $this->query['filter_by_prefix']));
23+
}
24+
25+
$channels = $channels->mapWithKeys(function ($connections, $name) use ($info) {
26+
return [$name => array_filter(['user_count' => in_array('user_count', $info) ? count($connections) : null])];
27+
});
28+
29+
return new JsonResponse((object) ['channels' => $channels]);
30+
}
31+
}

0 commit comments

Comments
 (0)