Hey all,
@lararara If you use private channels and the client is sending a message over that channel and you use Reverb's event listener to handle that incoming message there is no need for CSRF/authentication. On a private channel a user is already authenticated. So if you listen for an incoming message you can get the channel out of the message's json string (channel-property). And CSRF protection should not be an issue, because this is a websocket connection not http(s).
@marciliojunior The mssage contains all information you need. If the user sent a message on a private channel you get the channel-name from the event-payload (channel-property). If you want to "talk back" to the user - just use Laravel`s default Broadcasting and dispatch an event with Broadcasting enabled on the same channel.
I also had some progress on implementing a PHP "Echo Client" that is able to make a websocket connection to a remote Laravel application that serves Reverb.
I got this working:
- Websocket connection (initialization)
- Private channel subscription using Laravel Sanctum API tokens for authentication
- Receiving messages on the "client" application
- Sending messages from the "client" application to the remote server (running reverb)
- Sending/receiving messages on the remote server using the Reverb Event Listener and Broadcast Events
With this setup it is possible to have a persistent duplex connection between two (or more) Laravel applications. A bit like a pub/sub broker like redis but with authentication and authorization already included.
This is how I do it on the "client" application:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use function Amp\Websocket\Client\connect;
use function Amp\async;
use function Amp\delay;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
class Connect extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'connect';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Connect to control server';
private $deviceId;
private $connection;
private $channelName;
private $socketId;
private $auth;
/**
* Execute the console command.
*/
public function handle()
{
$this->deviceId = "1";
$this->channelName = 'private-devices.' . $this->deviceId;
$this->connection = connect('ws://localhost:8080/app/my-app-key?protocol=7&client=php&version=8.3');
$this->init();
// handle outgoing messages
$handleMessagesSend = async(function () {
while(true) {
$message = Cache::pull('send-message');
if ($message) {
$this->connection->sendText(json_encode([
'event' => 'client-event',
'channel' => $this->channelName,
'data' => [
'message' => $message,
],
]));
}
delay(2);
}
});
// handle incoming ping messages
foreach ($this->connection as $message) {
$this->info('Receiving messages');
$payload = $message->buffer();
if ($payload === '100') {
$this->connection->close();
}
$data = json_decode($payload);
if (isset($data->event) && $data->event === 'pusher:ping') {
$this->connection->sendText(json_encode([
'event' => 'pusher:pong',
'data' => [
'channel' => $this->channelName,
'auth' => $this->auth,
],
]));
}
}
}
private function init()
{
$payload = $this->connection->receive();
$bufferedData = $payload->buffer();
$this->info($bufferedData);
$data = json_decode($bufferedData);
if ($payload === '100') {
$this->connection->close();
}
if (isset($data->event) && $data->event === 'pusher:connection_established') {
$eventData = json_decode($data->data);
$this->socketId = $eventData->socket_id;
$authResponse = Http::withToken('my-sanctum-token')->acceptJson()->post('http://localhost/api/broadcasting/auth', [
'channel_name' => $this->channelName,
'socket_id' => $this->socketId,
]);
logger()->debug("Auth response", ['response' => $authResponse->body()]);
$this->auth = $authResponse['auth'];
$this->connection->sendText(json_encode([
'event' => 'pusher:subscribe',
'data' => [
'channel' => $this->channelName,
'auth' => $this->auth,
],
]));
}
}
}
It is not ideal (I "misuse" the cache to check for new messages to be send every two seconds) but it works.