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/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": {

View File

@ -7,31 +7,53 @@
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
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>
</head>
<body>
<div class="container">
<div class="panel panel-default">
<div id="status"></div>
<div class="panel-heading">
<span class="panel-title">WebSockets Console</span>
<div class="container" id="app">
<div class="card col-xs-12">
<div class="card-header">
<form id="connect" class="form-inline" role="form">
<div class="form-group">
<select class="form-control" name="app" id="app">
@foreach ($clients as $client)
<option value="{{ $client->appKey }}">{{ $client->name }}</option>
@endforeach
</select>
</div>
<button type="submit" class="btn btn-primary">Connect</button>
</form>
<form id="disconnect" class="form-inline" role="form">
<button type="submit" class="btn btn-danger">Disconnect</button>
<label class="my-1 mr-2" for="client">Client:</label>
<select class="form-control form-control-sm mr-2" name="client" id="client" v-model="client">
<option v-for="client in clients" :value="client">@{{ client.name }}</option>
</select>
<label class="my-1 mr-2" for="client">Port:</label>
<input class="form-control form-control-sm mr-2" v-model="port" placeholder="Port">
<button v-if="! connected" type="submit" @click.prevent="connect" class="mr-2 btn btn-sm btn-primary">Connect</button>
<button v-if="connected" type="submit" @click.prevent="disconnect" class="btn btn-sm btn-danger">Disconnect</button>
</form>
<div id="status"></div>
</div>
<div class="panel-body col-md-8">
<h3>Events <span class="badge" id="counter"></span></h3>
<div class="card-body">
<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">
<thead>
<tr>
@ -42,38 +64,113 @@
</tr>
</thead>
<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>
</table>
</div>
</div>
</div>
<script>
$(function(){
$('#connect').submit(function(event) {
connect($('#app').val());
event.preventDefault();
});
new Vue({
el: '#app',
$('#disconnect').submit(function(event) {
var appKey = $('#appKey').val("");
var secret = $('#secret').val("");
event.preventDefault();
});
data: {
connected: false,
pusher: null,
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>
</body>
</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
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;
use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Dashboard;
use Illuminate\Http\Request;
class TriggerEvent extends EchoController
@ -11,6 +12,8 @@ class TriggerEvent extends EchoController
$this->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([

View File

@ -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)

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;
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);

View File

@ -2,6 +2,7 @@
namespace BeyondCode\LaravelWebSockets\LaravelEcho\WebSocket;
use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Dashboard;
use Exception;
use Ratchet\ConnectionInterface;
use Ratchet\RFC6455\Messaging\MessageInterface;
@ -68,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([
@ -83,20 +86,4 @@ class PusherServer extends WebSocketController
$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
]
]);
}
}