This commit is contained in:
Marcel Pociot 2018-11-25 22:24:31 +01:00
parent 8faa7bf3f1
commit b4141c81db
9 changed files with 284 additions and 59 deletions

View File

@ -28,8 +28,10 @@
"illuminate/console": "5.7.*", "illuminate/console": "5.7.*",
"illuminate/http": "5.7.*", "illuminate/http": "5.7.*",
"illuminate/routing": "5.7.*", "illuminate/routing": "5.7.*",
"illuminate/broadcasting": "5.7.*",
"illuminate/support": "5.7.*", "illuminate/support": "5.7.*",
"symfony/http-kernel": "~4.0", "symfony/http-kernel": "~4.0",
"pusher/pusher-php-server": "~3.0",
"symfony/psr-http-message-bridge": "^1.1" "symfony/psr-http-message-bridge": "^1.1"
}, },
"require-dev": { "require-dev": {

View File

@ -7,31 +7,53 @@
src="https://code.jquery.com/jquery-3.3.1.min.js" src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script> crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
<script src="https://js.pusher.com/4.3/pusher.min.js"></script> <script src="https://js.pusher.com/4.3/pusher.min.js"></script>
</head> </head>
<body> <body>
<div class="container"> <div class="container" id="app">
<div class="panel panel-default"> <div class="card col-xs-12">
<div id="status"></div> <div class="card-header">
<div class="panel-heading">
<span class="panel-title">WebSockets Console</span>
<form id="connect" class="form-inline" role="form"> <form id="connect" class="form-inline" role="form">
<div class="form-group"> <label class="my-1 mr-2" for="client">Client:</label>
<select class="form-control" name="app" id="app"> <select class="form-control form-control-sm mr-2" name="client" id="client" v-model="client">
@foreach ($clients as $client) <option v-for="client in clients" :value="client">@{{ client.name }}</option>
<option value="{{ $client->appKey }}">{{ $client->name }}</option> </select>
@endforeach <label class="my-1 mr-2" for="client">Port:</label>
</select> <input class="form-control form-control-sm mr-2" v-model="port" placeholder="Port">
</div> <button v-if="! connected" type="submit" @click.prevent="connect" class="mr-2 btn btn-sm btn-primary">Connect</button>
<button type="submit" class="btn btn-primary">Connect</button> <button v-if="connected" type="submit" @click.prevent="disconnect" class="btn btn-sm btn-danger">Disconnect</button>
</form>
<form id="disconnect" class="form-inline" role="form">
<button type="submit" class="btn btn-danger">Disconnect</button>
</form> </form>
<div id="status"></div>
</div> </div>
<div class="panel-body col-md-8"> <div class="card-body">
<h3>Events <span class="badge" id="counter"></span></h3> <div v-if="connected">
<h4>Event Creator</h4>
<form>
<div class="row">
<div class="col">
<input type="text" class="form-control" v-model="form.channel" placeholder="Channel">
</div>
<div class="col">
<input type="text" class="form-control" v-model="form.event" placeholder="Event">
</div>
</div>
<div class="row mt-3">
<div class="col">
<div class="form-group">
<textarea placeholder="Data" v-model="form.data" class="form-control" id="data" rows="3"></textarea>
</div>
</div>
</div>
<div class="row text-right">
<div class="col">
<button type="submit" @click.prevent="sendEvent" class="btn btn-sm btn-primary">Send event</button>
</div>
</div>
</form>
</div>
<h4>Events</h4>
<table id="events" class="table table-striped table-hover"> <table id="events" class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
@ -42,38 +64,113 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="log in logs.slice().reverse()">
<td><span class="badge" :class="getBadgeClass(log)">@{{ log.type }}</span></td>
<td>@{{ log.socketId }}</td>
<td>@{{ log.details }}</td>
<td>@{{ log.time }}</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
$(function(){ new Vue({
$('#connect').submit(function(event) { el: '#app',
connect($('#app').val());
event.preventDefault();
});
$('#disconnect').submit(function(event) { data: {
var appKey = $('#appKey').val(""); connected: false,
var secret = $('#secret').val(""); pusher: null,
event.preventDefault(); port: 6001,
}); client: null,
clients: {!! json_encode($clients) !!},
form: {
channel: null,
event: null,
data: null
},
logs: [],
},
methods: {
connect() {
this.pusher = new Pusher(this.client.appKey, {
wsHost: window.location.hostname,
wsPort: this.port,
authEndpoint: '{{ config('websockets.dashboard.path') }}/auth',
enabledTransports: ['ws', 'flash']
});
this.pusher.connection.bind('state_change', states => {
$('div#status').text("Channels current state is " + states.current);
});
this.pusher.connection.bind('connected', () => {
this.connected = true;
});
this.pusher.connection.bind('disconnected', () => {
this.connected = false;
this.logs = [];
});
this.subscribeToChannel('disconnection');
this.subscribeToChannel('connection');
this.subscribeToChannel('vacated');
this.subscribeToChannel('occupied');
this.subscribeToChannel('subscribed');
this.subscribeToChannel('client_message');
this.subscribeToChannel('api_message');
},
disconnect() {
this.pusher.disconnect();
},
subscribeToChannel(channel) {
this.pusher.subscribe('{{ \BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Dashboard::LOG_CHANNEL_PREFIX }}'+channel)
.bind('log_message', (data) => {
this.logs.push(data);
});
},
getBadgeClass(log) {
if (log.type === 'occupied' || log.type === 'connection') {
return 'badge-primary';
}
if (log.type === 'vacated') {
return 'badge-warning';
}
if (log.type === 'disconnection') {
return 'badge-error';
}
if (log.type === 'api_message') {
return 'badge-info';
}
return 'badge-secondary';
},
sendEvent() {
$.post('{{ config('websockets.dashboard.path') }}/event', {
key: this.client.appKey,
secret: this.client.appSecret,
appId: this.client.appId,
channel: this.form.channel,
event: this.form.event,
data: this.form.data,
}).fail(e => {
alert('Error sending event.');
});
}
}
}); });
function connect(appKey) {
pusher = new Pusher(appKey, {
wsHost: window.location.hostname,
wsPort: 6001,
authEndpoint: '{{ config('websockets.dashboard.path') }}/auth',
enabledTransports: ['ws', 'flash']
});
pusher.subscribe('private-logger-new_connection')
.bind('private-logger-connection', function(data) {
alert(JSON.stringify(data));
});
}
</script> </script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,21 @@
<?php
namespace BeyondCode\LaravelWebsockets\Http\Controllers;
use Pusher\Pusher;
use Illuminate\Http\Request;
use Illuminate\Broadcasting\Broadcasters\PusherBroadcaster;
class SendMessage
{
public function __invoke(Request $request)
{
$pusher = new Pusher(
$request->key, $request->secret,
$request->appId, config('broadcasting.connections.pusher.options', [])
);
return (new PusherBroadcaster($pusher))
->broadcast([$request->channel], $request->event, json_decode($request->data, true));
}
}

View File

@ -1,4 +1,5 @@
<?php <?php
Route::get('/', 'ShowConsole'); Route::get('/', 'ShowConsole');
Route::post('/auth', 'AuthenticateConsole'); Route::post('/auth', 'AuthenticateConsole');
Route::post('/event', 'SendMessage');

View File

@ -2,6 +2,7 @@
namespace BeyondCode\LaravelWebSockets\LaravelEcho\Http\Controllers; namespace BeyondCode\LaravelWebSockets\LaravelEcho\Http\Controllers;
use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Dashboard;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class TriggerEvent extends EchoController class TriggerEvent extends EchoController
@ -11,6 +12,8 @@ class TriggerEvent extends EchoController
$this->verifySignature($request); $this->verifySignature($request);
foreach ($request->json()->get('channels', []) as $channelId) { 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); $channel = $this->channelManager->find($request->appId, $channelId);
optional($channel)->broadcastToEveryoneExcept([ optional($channel)->broadcastToEveryoneExcept([

View File

@ -2,6 +2,7 @@
namespace BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Channels; namespace BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Channels;
use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Dashboard;
use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Exceptions\InvalidSignatureException; use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Exceptions\InvalidSignatureException;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Ratchet\ConnectionInterface; use Ratchet\ConnectionInterface;
@ -56,11 +57,21 @@ class Channel
public function unsubscribe(ConnectionInterface $connection) public function unsubscribe(ConnectionInterface $connection)
{ {
unset($this->subscriptions[$connection->socketId]); unset($this->subscriptions[$connection->socketId]);
if (! $this->hasConnections()) {
Dashboard::vacated($connection, $this->channelId);
}
} }
protected function saveConnection(ConnectionInterface $connection) protected function saveConnection(ConnectionInterface $connection)
{ {
if (! $this->hasConnections()) {
Dashboard::occupied($connection, $this->channelId);
}
$this->subscriptions[$connection->socketId] = $connection; $this->subscriptions[$connection->socketId] = $connection;
Dashboard::subscribed($connection, $this->channelId);
} }
public function broadcast($payload) public function broadcast($payload)

View File

@ -0,0 +1,100 @@
<?php
namespace BeyondCode\LaravelWebSockets\LaravelEcho\Pusher;
use Ratchet\ConnectionInterface;
use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Channels\ChannelManager;
use stdClass;
class Dashboard
{
const LOG_CHANNEL_PREFIX = 'private-websockets-dashboard-';
const TYPE_DISCONNECTION = 'disconnection';
const TYPE_CONNECTION = 'connection';
const TYPE_VACATED = 'vacated';
const TYPE_OCCUPIED = 'occupied';
const TYPE_SUBSCRIBED = 'subscribed';
const TYPE_CLIENT_MESSAGE = 'client_message';
const TYPE_API_MESSAGE = 'api_message';
public static function connection(ConnectionInterface $connection)
{
/** @var \GuzzleHttp\Psr7\Request $request */
$request = $connection->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
]);
}
}

View File

@ -3,6 +3,7 @@
namespace BeyondCode\LaravelWebSockets\LaravelEcho\WebSocket; namespace BeyondCode\LaravelWebSockets\LaravelEcho\WebSocket;
use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Channels\ChannelManager; use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Channels\ChannelManager;
use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Dashboard;
use Ratchet\ConnectionInterface; use Ratchet\ConnectionInterface;
use stdClass; use stdClass;
@ -29,6 +30,8 @@ class Message implements RespondableMessage
public function respond() public function respond()
{ {
if (starts_with($this->payload->event, 'client-')) { if (starts_with($this->payload->event, 'client-')) {
Dashboard::clientMessage($this->connection, $this->payload);
$channel = $this->channelManager->find($this->connection->client->appId, $this->payload->channel); $channel = $this->channelManager->find($this->connection->client->appId, $this->payload->channel);
optional($channel)->broadcast($this->payload); optional($channel)->broadcast($this->payload);

View File

@ -2,6 +2,7 @@
namespace BeyondCode\LaravelWebSockets\LaravelEcho\WebSocket; namespace BeyondCode\LaravelWebSockets\LaravelEcho\WebSocket;
use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Dashboard;
use Exception; use Exception;
use Ratchet\ConnectionInterface; use Ratchet\ConnectionInterface;
use Ratchet\RFC6455\Messaging\MessageInterface; use Ratchet\RFC6455\Messaging\MessageInterface;
@ -68,6 +69,8 @@ class PusherServer extends WebSocketController
protected function establishConnection(ConnectionInterface $connection) protected function establishConnection(ConnectionInterface $connection)
{ {
Dashboard::connection($connection);
$connection->send(json_encode([ $connection->send(json_encode([
'event' => 'pusher:connection_established', 'event' => 'pusher:connection_established',
'data' => json_encode([ 'data' => json_encode([
@ -83,20 +86,4 @@ class PusherServer extends WebSocketController
$connection->socketId = $socketId; $connection->socketId = $socketId;
} }
public function log(string $appId, string $type, string $details)
{
$channelId = "private-logger-{$type}";
$channel = $this->channelManager->find($appId, $channelId);
optional($channel)->broadcast([
'event' => $type,
'channel' => $channelId,
'data' => [
'type' => $type,
'details' => $details
]
]);
}
} }