diff --git a/composer.json b/composer.json
index d80f32c..4ebd5c7 100644
--- a/composer.json
+++ b/composer.json
@@ -25,11 +25,13 @@
"php": "^7.1",
"ext-json": "*",
"cboden/ratchet": "^0.4.1",
- "illuminate/console": "5.6.*|5.7.*",
- "illuminate/http": "5.6.*|5.7.*",
- "illuminate/routing": "5.6.*|5.7.*",
- "illuminate/support": "5.6.*|5.7.*",
+ "illuminate/console": "5.7.*",
+ "illuminate/http": "5.7.*",
+ "illuminate/routing": "5.7.*",
+ "illuminate/broadcasting": "5.7.*",
+ "illuminate/support": "5.7.*",
"symfony/http-kernel": "~4.0",
+ "pusher/pusher-php-server": "~3.0",
"symfony/psr-http-message-bridge": "^1.1"
},
"require-dev": {
diff --git a/config/websockets.php b/config/websockets.php
index 85bdf28..8e1d003 100644
--- a/config/websockets.php
+++ b/config/websockets.php
@@ -1,5 +1,6 @@
[
[
+ 'name' => env('APP_NAME'),
'app_id' => env('WEBSOCKETS_APP_ID'),
'app_key' => env('WEBSOCKETS_APP_KEY'),
'app_secret' => env('WEBSOCKETS_APP_SECRET')
@@ -58,4 +60,20 @@ return [
* `ClientProvier` interface.
*/
'client_provider' => ConfigClientProvider::class,
+
+ 'dashboard' => [
+
+ /*
+ * Path for the Websockets debug console
+ */
+ 'path' => '/websockets',
+
+ /*
+ * Middleware that will be applied to the dashboard routes.
+ */
+ 'middleware' => [
+ Authorize::class,
+ ],
+
+ ]
];
\ No newline at end of file
diff --git a/resources/views/console.blade.php b/resources/views/console.blade.php
new file mode 100644
index 0000000..1fc817f
--- /dev/null
+++ b/resources/views/console.blade.php
@@ -0,0 +1,176 @@
+
+
+
+ WebSockets Console
+
+
+
+
+
+
+
+
+
+
+
+
+
Events
+
+
+
+ | Type |
+ Socket |
+ Details |
+ Time |
+
+
+
+
+ | @{{ log.type }} |
+ @{{ log.socketId }} |
+ @{{ log.details }} |
+ @{{ log.time }} |
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ClientProviders/Client.php b/src/ClientProviders/Client.php
index cd3f3c7..688a3ef 100644
--- a/src/ClientProviders/Client.php
+++ b/src/ClientProviders/Client.php
@@ -16,6 +16,9 @@ class Client
/** @var string */
public $appSecret;
+ /** @var string|null */
+ public $name;
+
public static function findByAppId(int $appId)
{
return app(ClientProvider::class)->findByAppId($appId);
@@ -26,7 +29,7 @@ class Client
return app(ClientProvider::class)->findByAppKey($appKey);
}
- public function __construct($appId, string $appKey, string $appSecret)
+ public function __construct($appId, string $appKey, string $appSecret, ?string $name)
{
if (!is_numeric($appId)) {
throw InvalidClient::appIdIsNotNumeric($appId);
@@ -45,6 +48,8 @@ class Client
$this->appKey = $appKey;
$this->appSecret = $appSecret;
+
+ $this->name = $name;
}
diff --git a/src/ClientProviders/ClientProvider.php b/src/ClientProviders/ClientProvider.php
index f363f53..4f67e0c 100644
--- a/src/ClientProviders/ClientProvider.php
+++ b/src/ClientProviders/ClientProvider.php
@@ -8,4 +8,6 @@ interface ClientProvider
public function findByAppId(int $appId): ?Client;
public function findByAppKey(string $appKey): ?Client;
+
+ public function all(): array;
}
\ No newline at end of file
diff --git a/src/ClientProviders/ConfigClientProvider.php b/src/ClientProviders/ConfigClientProvider.php
index 6cf63d1..6a4653e 100644
--- a/src/ClientProviders/ConfigClientProvider.php
+++ b/src/ClientProviders/ConfigClientProvider.php
@@ -24,6 +24,15 @@ class ConfigClientProvider implements ClientProvider
return $this->instanciate($clientAttributes);
}
+ public function all(): array
+ {
+ return $this->allClients()
+ ->map(function ($client) {
+ return $this->instanciate($client);
+ })
+ ->toArray();
+ }
+
protected function allClients(): Collection
{
return collect(config('websockets.clients'));
@@ -38,7 +47,8 @@ class ConfigClientProvider implements ClientProvider
return new Client(
$clientAttributes['app_id'],
$clientAttributes['app_key'],
- $clientAttributes['app_secret']
+ $clientAttributes['app_secret'],
+ $clientAttributes['name'] ?? null
);
}
}
\ No newline at end of file
diff --git a/src/Http/Controllers/AuthenticateConsole.php b/src/Http/Controllers/AuthenticateConsole.php
new file mode 100644
index 0000000..2dce0f7
--- /dev/null
+++ b/src/Http/Controllers/AuthenticateConsole.php
@@ -0,0 +1,14 @@
+validAuthenticationResponse($request, []);
+ }
+}
\ No newline at end of file
diff --git a/src/Http/Controllers/SendMessage.php b/src/Http/Controllers/SendMessage.php
new file mode 100644
index 0000000..ff9b39b
--- /dev/null
+++ b/src/Http/Controllers/SendMessage.php
@@ -0,0 +1,21 @@
+key, $request->secret,
+ $request->appId, config('broadcasting.connections.pusher.options', [])
+ );
+
+ return (new PusherBroadcaster($pusher))
+ ->broadcast([$request->channel], $request->event, json_decode($request->data, true));
+ }
+}
\ No newline at end of file
diff --git a/src/Http/Controllers/ShowConsole.php b/src/Http/Controllers/ShowConsole.php
new file mode 100644
index 0000000..e8fa6ce
--- /dev/null
+++ b/src/Http/Controllers/ShowConsole.php
@@ -0,0 +1,16 @@
+ $clients->all()
+ ]);
+ }
+}
\ No newline at end of file
diff --git a/src/Http/Middleware/Authorize.php b/src/Http/Middleware/Authorize.php
new file mode 100644
index 0000000..a2d80c4
--- /dev/null
+++ b/src/Http/Middleware/Authorize.php
@@ -0,0 +1,13 @@
+verifySignature($request);
foreach ($request->json()->get('channels', []) as $channelId) {
+ Dashboard::apiMessage($request->appId, $channelId, $request->json()->get('name'), $request->json()->get('data'));
+
$channel = $this->channelManager->find($request->appId, $channelId);
optional($channel)->broadcastToEveryoneExcept([
diff --git a/src/LaravelEcho/Pusher/Channels/Channel.php b/src/LaravelEcho/Pusher/Channels/Channel.php
index d84ea03..0eb9864 100644
--- a/src/LaravelEcho/Pusher/Channels/Channel.php
+++ b/src/LaravelEcho/Pusher/Channels/Channel.php
@@ -2,6 +2,7 @@
namespace BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Channels;
+use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Dashboard;
use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Exceptions\InvalidSignatureException;
use Illuminate\Support\Collection;
use Ratchet\ConnectionInterface;
@@ -56,11 +57,21 @@ class Channel
public function unsubscribe(ConnectionInterface $connection)
{
unset($this->subscriptions[$connection->socketId]);
+
+ if (! $this->hasConnections()) {
+ Dashboard::vacated($connection, $this->channelId);
+ }
}
protected function saveConnection(ConnectionInterface $connection)
{
+ if (! $this->hasConnections()) {
+ Dashboard::occupied($connection, $this->channelId);
+ }
+
$this->subscriptions[$connection->socketId] = $connection;
+
+ Dashboard::subscribed($connection, $this->channelId);
}
public function broadcast($payload)
diff --git a/src/LaravelEcho/Pusher/Dashboard.php b/src/LaravelEcho/Pusher/Dashboard.php
new file mode 100644
index 0000000..8a325e0
--- /dev/null
+++ b/src/LaravelEcho/Pusher/Dashboard.php
@@ -0,0 +1,100 @@
+httpRequest;
+
+ self::log($connection->client->appId, self::TYPE_CONNECTION, [
+ 'details' => "Origin: {$request->getUri()->getScheme()}://{$request->getUri()->getHost()}",
+ 'socketId' => $connection->socketId,
+ ]);
+ }
+
+ public static function disconnection(ConnectionInterface $connection)
+ {
+ self::log($connection->client->appId, self::TYPE_DISCONNECTION, [
+ 'socketId' => $connection->socketId
+ ]);
+ }
+
+ public static function vacated(ConnectionInterface $connection, string $channelId)
+ {
+ self::log($connection->client->appId, self::TYPE_VACATED, [
+ 'details' => "Channel: {$channelId}"
+ ]);
+ }
+
+ public static function occupied(ConnectionInterface $connection, string $channelId)
+ {
+ self::log($connection->client->appId, self::TYPE_OCCUPIED, [
+ 'details' => "Channel: {$channelId}"
+ ]);
+ }
+
+ public static function subscribed(ConnectionInterface $connection, string $channelId)
+ {
+ self::log($connection->client->appId, self::TYPE_SUBSCRIBED, [
+ 'socketId' => $connection->socketId,
+ 'details' => "Channel: {$channelId}"
+ ]);
+ }
+
+ public static function clientMessage(ConnectionInterface $connection, stdClass $payload)
+ {
+ self::log($connection->client->appId, self::TYPE_CLIENT_MESSAGE, [
+ 'details' => "Channel: {$payload->channel}, Event: {$payload->event}",
+ 'socketId' => $connection->socketId,
+ 'data' => json_encode($payload)
+ ]);
+ }
+
+ public static function apiMessage($appId, string $channel, string $event, string $payload)
+ {
+ self::log($appId, self::TYPE_API_MESSAGE, [
+ 'details' => "Channel: {$channel}, Event: {$event}",
+ 'data' => $payload
+ ]);
+ }
+
+ public static function log($appId, string $type, array $attributes = [])
+ {
+ $channelId = self::LOG_CHANNEL_PREFIX . $type;
+
+ $channel = app(ChannelManager::class)->find($appId, $channelId);
+
+ optional($channel)->broadcast([
+ 'event' => 'log_message',
+ 'channel' => $channelId,
+ 'data' => [
+ 'type' => $type,
+ 'time' => strftime("%H:%M:%S")
+ ] + $attributes
+ ]);
+ }
+
+}
\ No newline at end of file
diff --git a/src/LaravelEcho/Pusher/Exceptions/InvalidConnectionException.php b/src/LaravelEcho/Pusher/Exceptions/InvalidConnectionException.php
new file mode 100644
index 0000000..143270c
--- /dev/null
+++ b/src/LaravelEcho/Pusher/Exceptions/InvalidConnectionException.php
@@ -0,0 +1,12 @@
+message = 'Invalid Connection';
+ $this->code = 4009;
+ }
+}
\ No newline at end of file
diff --git a/src/LaravelEcho/Pusher/Exceptions/UnknownAppKey.php b/src/LaravelEcho/Pusher/Exceptions/UnknownAppKeyException.php
similarity index 81%
rename from src/LaravelEcho/Pusher/Exceptions/UnknownAppKey.php
rename to src/LaravelEcho/Pusher/Exceptions/UnknownAppKeyException.php
index 76e856f..c154d27 100644
--- a/src/LaravelEcho/Pusher/Exceptions/UnknownAppKey.php
+++ b/src/LaravelEcho/Pusher/Exceptions/UnknownAppKeyException.php
@@ -2,7 +2,7 @@
namespace BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Exceptions;
-class UnknownAppKey extends PusherException
+class UnknownAppKeyException extends PusherException
{
public function __construct(string $appKey)
{
diff --git a/src/LaravelEcho/WebSocket/Message.php b/src/LaravelEcho/WebSocket/Message.php
index ad089d2..5617f59 100644
--- a/src/LaravelEcho/WebSocket/Message.php
+++ b/src/LaravelEcho/WebSocket/Message.php
@@ -3,6 +3,7 @@
namespace BeyondCode\LaravelWebSockets\LaravelEcho\WebSocket;
use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Channels\ChannelManager;
+use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Dashboard;
use Ratchet\ConnectionInterface;
use stdClass;
@@ -29,6 +30,8 @@ class Message implements RespondableMessage
public function respond()
{
if (starts_with($this->payload->event, 'client-')) {
+ Dashboard::clientMessage($this->connection, $this->payload);
+
$channel = $this->channelManager->find($this->connection->client->appId, $this->payload->channel);
optional($channel)->broadcast($this->payload);
diff --git a/src/LaravelEcho/WebSocket/PusherServer.php b/src/LaravelEcho/WebSocket/PusherServer.php
index f48027f..b81127e 100644
--- a/src/LaravelEcho/WebSocket/PusherServer.php
+++ b/src/LaravelEcho/WebSocket/PusherServer.php
@@ -2,14 +2,15 @@
namespace BeyondCode\LaravelWebSockets\LaravelEcho\WebSocket;
-use BeyondCode\LaravelWebSockets\ClientProviders\Client;
-use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Exceptions\PusherException;
-use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Exceptions\UnknownAppKey;
+use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Dashboard;
use Exception;
use Ratchet\ConnectionInterface;
use Ratchet\RFC6455\Messaging\MessageInterface;
use BeyondCode\LaravelWebSockets\WebSocketController;
+use BeyondCode\LaravelWebSockets\ClientProviders\Client;
use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Channels\ChannelManager;
+use BeyondCode\LaravelWebsockets\LaravelEcho\Pusher\Exceptions\PusherException;
+use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Exceptions\UnknownAppKeyException;
class PusherServer extends WebSocketController
{
@@ -49,7 +50,6 @@ class PusherServer extends WebSocketController
$exception->getPayload()
));
}
- dump($exception);
}
protected function verifyConnection(ConnectionInterface $connection)
@@ -61,7 +61,7 @@ class PusherServer extends WebSocketController
parse_str($request->getUri()->getQuery(), $queryParameters);
if (! $client = Client::findByAppKey($queryParameters['appKey'])) {
- throw new UnknownAppKey($queryParameters['appKey']);
+ throw new UnknownAppKeyException($queryParameters['appKey']);
}
$connection->client = $client;
@@ -69,6 +69,8 @@ class PusherServer extends WebSocketController
protected function establishConnection(ConnectionInterface $connection)
{
+ Dashboard::connection($connection);
+
$connection->send(json_encode([
'event' => 'pusher:connection_established',
'data' => json_encode([
diff --git a/src/LaravelWebSocketsServiceProvider.php b/src/LaravelWebSocketsServiceProvider.php
index c96fe8b..8d92ab0 100644
--- a/src/LaravelWebSocketsServiceProvider.php
+++ b/src/LaravelWebSocketsServiceProvider.php
@@ -2,6 +2,8 @@
namespace BeyondCode\LaravelWebSockets;
+use Illuminate\Support\Facades\Gate;
+use Illuminate\Support\Facades\Route;
use BeyondCode\LaravelWebSockets\ClientProviders\ClientProvider;
use Illuminate\Support\ServiceProvider;
use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Channels\ChannelManager;
@@ -10,15 +12,39 @@ class LaravelWebSocketsServiceProvider extends ServiceProvider
{
public function boot()
{
+ Route::middlewareGroup('websockets', config('websockets.dashboard.middleware', []));
+
$this->publishes([
__DIR__.'/../config/websockets.php' => base_path('config/websockets.php'),
], 'config');
+ $this->registerRoutes();
+
+ $this->registerDashboardGate();
+
+ $this->loadViewsFrom(__DIR__.'/../resources/views/', 'websockets');
+
$this->commands([
Console\StartWebSocketServer::class,
]);
}
+ protected function registerRoutes()
+ {
+ Route::group($this->routeConfiguration(), function () {
+ $this->loadRoutesFrom(__DIR__.'/Http/routes.php');
+ });
+ }
+
+ protected function routeConfiguration()
+ {
+ return [
+ 'namespace' => 'BeyondCode\LaravelWebSockets\Http\Controllers',
+ 'prefix' => config('websockets.dashboard.path'),
+ 'middleware' => 'websockets'
+ ];
+ }
+
public function register()
{
$this->mergeConfigFrom(__DIR__.'/../config/websockets.php', 'websockets');
@@ -35,4 +61,11 @@ class LaravelWebSocketsServiceProvider extends ServiceProvider
return app(config('websockets.client_provider'));
});
}
+
+ protected function registerDashboardGate()
+ {
+ Gate::define('viewWebSocketDashboard', function ($user = null) {
+ return app()->environment('local');
+ });
+ }
}