Laravel Echo not receiving events from self hosted websocket - laravel

I have a Laravel application, running through docker-compose, and am trying to setup a Soketi self hosted Websocket, that is also run as a docker container
Everything is, as far as I can tell, running correctly. The websocker is running, and is receiving a connection from my app, subscribing to channels and also receiving events.
Only thing that is not working is on the frontend, where I am utilising the Laravel Echo library to communicate with the Websocket, is not recieving the events that I am broadcasting to the websocket
config/broadcasting.php
'pusher' => [
'driver' => 'pusher',
'key' => 'broadcasting',
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => 'broadcasting',
'options' => [
'cluster' => 'default',
'encrypted' => true,
'host' => 'soketi',
'port' => 6001,
'scheme' => 'http',
'useTLS' => false,
],
],
bootstrap.js
import Echo from 'laravel-echo'
window.Pusher = require('pusher-js');
window.laravelEcho = new Echo({
broadcaster: 'pusher',
key: 'broadcasting',
cluster: 'default',
wsHost: window.location.hostname,
wssHost: window.location.hostname,
wsPort: 6001,
wssPort: 6001,
forceTLS: false,
disableStats: true,
enabledTransports: ['ws', 'wss'],
encrypted: true,
enableLogging: true,
});
window.laravelEcho.channel(`klinik`)
.listen('.test', (e) => {
alert('works');
});
The event
class TestEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function broadcastOn()
{
return new Channel('klinik');
}
public function broadcastAs()
{
return 'test';
}
}
The event is simply being dispatched via a console command
public function handle()
{
TestEvent::dispatch();
}
Upon page load, the websocket is loaded and subscribes to the klinik channel, as shown in the logs from docker
[Tue Jan 31 2023 10:22:08 GMT+0000 (Coordinated Universal Time)] 👨‍🔬 New connection:
{
ws: uWS.WebSocket { ip: '172.24.0.1', ip2: '', appKey: 'broadcasting' }
}
[Tue Jan 31 2023 10:22:08 GMT+0000 (Coordinated Universal Time)] ✈ Sent message to client:
{
ws: uWS.WebSocket {
ip: '172.24.0.1',
ip2: '',
appKey: 'broadcasting',
sendJson: [Function (anonymous)],
id: '435260853.7865190267',
subscribedChannels: Set(0) {},
presence: Map(0) {},
app: App {
initialApp: [Object],
server: [Server],
enableUserAuthentication: false,
hasClientEventWebhooks: false,
hasChannelOccupiedWebhooks: false,
hasChannelVacatedWebhooks: false,
hasMemberAddedWebhooks: false,
hasMemberRemovedWebhooks: false,
hasCacheMissedWebhooks: false,
id: 'broadcasting',
key: 'broadcasting',
secret: 'app-secret',
maxConnections: -1,
enableClientMessages: false,
enabled: true,
maxClientEventsPerSecond: -1,
maxPresenceMembersPerChannel: 100,
maxPresenceMemberSizeInKb: 2,
maxChannelNameLength: 200,
maxEventChannelsAtOnce: 100,
maxEventNameLength: 200,
maxEventPayloadInKb: 100,
maxEventBatchSize: 10
},
timeout: Timeout {
_idleTimeout: 120000,
_idlePrev: [TimersList],
_idleNext: [TimersList],
_idleStart: 1682165,
_onTimeout: [Function (anonymous)],
_timerArgs: undefined,
_repeat: null,
_destroyed: false,
[Symbol(refed)]: true,
[Symbol(kHasPrimitive)]: false,
[Symbol(asyncId)]: 3549,
[Symbol(triggerId)]: 0
}
},
data: {
event: 'pusher:connection_established',
data: '{"socket_id":"435260853.7865190267","activity_timeout":30}'
}
}
[Tue Jan 31 2023 10:22:09 GMT+0000 (Coordinated Universal Time)] ⚡ New message received:
{
message: { event: 'pusher:subscribe', data: { auth: '', channel: 'klinik' } },
isBinary: false
}
[Tue Jan 31 2023 10:22:09 GMT+0000 (Coordinated Universal Time)] ✈ Sent message to client:
{
ws: uWS.WebSocket {
ip: '172.24.0.1',
ip2: '',
appKey: 'broadcasting',
sendJson: [Function (anonymous)],
id: '435260853.7865190267',
subscribedChannels: Set(1) { 'klinik' },
presence: Map(0) {},
app: App {
initialApp: [Object],
server: [Server],
enableUserAuthentication: false,
hasClientEventWebhooks: false,
hasChannelOccupiedWebhooks: false,
hasChannelVacatedWebhooks: false,
hasMemberAddedWebhooks: false,
hasMemberRemovedWebhooks: false,
hasCacheMissedWebhooks: false,
id: 'broadcasting',
key: 'broadcasting',
secret: 'app-secret',
maxConnections: -1,
enableClientMessages: false,
enabled: true,
maxClientEventsPerSecond: -1,
maxPresenceMembersPerChannel: 100,
maxPresenceMemberSizeInKb: 2,
maxChannelNameLength: 200,
maxEventChannelsAtOnce: 100,
maxEventNameLength: 200,
maxEventPayloadInKb: 100,
maxEventBatchSize: 10
},
timeout: Timeout {
_idleTimeout: 120000,
_idlePrev: [TimersList],
_idleNext: [TimersList],
_idleStart: 1682550,
_onTimeout: [Function (anonymous)],
_timerArgs: undefined,
_repeat: null,
_destroyed: false,
[Symbol(refed)]: true,
[Symbol(kHasPrimitive)]: false,
[Symbol(asyncId)]: 3553,
[Symbol(triggerId)]: 0
}
},
data: {
event: 'pusher_internal:subscription_succeeded',
channel: 'klinik'
}
}
And this can also verified in the network tab. Echo is also pinging the websocket without issue
pusher/pusher-php-server: v. 7.2
laravel-echo: v. 1.15
pusher-js: v. 8.0.1
I recently tested this, with all the same settings, but using the Pusher API, and it worked like a charm
Any idea what I am doing wrong?

Solution was to send with all relevant environment variables to the docker image. Socketi uses default values for app_id, app_key and app_secret. I had specified the id and key, but not the secret, and adding that to the container fixed the issue.
My docker-compose.yml looks like this now
soketi:
image: 'quay.io/soketi/soketi:latest-16-alpine'
environment:
SOKETI_DEBUG: '1'
SOKETI_METRICS_SERVER_PORT: '9601'
SOKETI_DEFAULT_APP_ID: 'broadcasting'
SOKETI_DEFAULT_APP_KEY: 'broadcasting'
SOKETI_DEFAULT_APP_SECRET: 'mysecret'
ports:
- '6001:6001'
- '9601:9601'
networks:
- vetisearch

Related

Laravel Sanctum and Nuxt Js returns Unauthenticated

I recently just deployed my Nuxt Js application and i am having trouble with authentication.
I have set the stateful and session domain in my .env as seen below
SESSION_DRIVER=file
SESSION_LIFETIME=120
SESSION_DOMAIN=api.example.com
SANCTUM_STATEFUL_DOMAINS=example.com
i also have this in my cors.php
'paths' => ['api/*', 'sanctum/csrf-cookie',],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
In my nuxt.config.js i have the following
auth: {
strategies: {
cookie: {
endpoints: {
csrf: {
url: '/sanctum/csrf-cookie'
},
login: {
url: '/api/login',
},
register: {
url: '/api/register',
},
logout: {
url: '/api/logout',
},
user: {
url: '/api/user',
}
},
user: {
property: 'data'
},
}
},
redirect: {
login: '/auth/login',
logout: '/auth/login',
home: '/dashboard',
},
plugins: [
'~/plugins/axios'
]
},
// Axios module configuration: https://go.nuxtjs.dev/config-axios
axios: {
// Workaround to avoid enforcing hard-coded localhost:3000: https://github.com/nuxt-community/axios-module/issues/308
baseURL: 'http://api.example.com',
credentials: true
},
I do not know what i am doing wrong could someone please help
In your .env file, Change your SESSION_DOMAIN from api.example.com to .example.com
And change your axios baseUrl from http://localhost to your api endpoint

Getting Unathenticated Error NuxtJs Get request

Am trying to do a simple GET request in NuxtJs using Axios. l have configured my nuxt.config.js like this
axios: {
proxy: true,
credentials: true,
baseURL: 'http://localhost:8000/api/'
},
proxy: {
'/api': { target: 'http://localhost:8000', pathRewrite: {'^/api/v1/': ''} }
},
auth: {
strategies: {
laravelSanctum: {
provider: 'laravel/sanctum',
url: 'http://localhost:8000',
token: {
property: 'token',
global: true,
required: true,
type: 'Bearer',
name: 'Authorization'
},
refreshToken: {
property: 'token',
data: 'token',
maxAge: 60 * 60 * 24 * 30
},
user: {
property: 'user',
},
endpoints: {
login: { url: '/api/auth/login', method: 'post' },
user: { url: '/api/user', method: 'get' },
logout: { url: '/api/auth/logout', method: 'get' },
refresh: { url: '/api/refresh', method: 'get' },
},
tokenType: 'Bearer',
tokenRequired: true,
cookie: {
cookie: {
name: 'XSRF-TOKEN',
},
},
}
},
redirect: {
login: '/',
logout: '/',
callback: false,
home: '/'
},
},
router: {
middleware: 'auth'
},
l have tried alot of methods that were posted here before but no avail. Same issues. l also noticed that when l reload the page. Vuex shows user object empty.
Am using Laravel Sactum, any help will be greatly appreciated. Thanks in advance

Handshake problem on websocket connection when using Laravel Echo Server as Pusher replacement

I want to use Laravel Echo Server as Pusher replacement
I am seeking the Laracast tutorial series for Laravel broadcasting: https://laracasts.com/series/get-real-with-laravel-echo.
I have succesfully created a testing javascript client that listens to the events for channel orders. This is the client code:
import Echo from 'laravel-echo';
window.Pusher = require('pusher-js');
window.Echo = new Echo({
broadcaster: 'pusher',
key: process.env.MIX_PUSHER_APP_KEY,
cluster: process.env.MIX_PUSHER_APP_CLUSTER,
forceTLS: true
});
window.Echo.channel('orders')
.listen('OrderStatusUpdate', e => {
console.log("Status has been updated behind the scenes.")
console.log(e);
});
But the company where I work requires to have their own websocket resource, so we cannot depends upon Pusher.
Then, we are working on Laravel Echo Server: https://github.com/tlaverdure/Laravel-Echo-Server. Almost everything is working. We have changed the config/broadcasting.php options to fit to Laravel Echo Server. And successfully we can see the events being emitted inside laravel echo log, just changing some configuratons parameters in config/broadcasting.php:
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => false,
'host' => '192.168.15.36',
'port' => 6001,
'scheme' => 'http'
],
'client_options' => [
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
],
],
The address 192.168.15.36 is the local ip address.
Also changed the client code to deal with the changes:
import Echo from 'laravel-echo';
window.Pusher = require('pusher-js');
window.Echo = new Echo({
broadcaster: 'pusher',
key: process.env.MIX_PUSHER_APP_KEY,
forceTLS: false,
wsHost: '192.168.15.36',
wsPort: 6001,
encrypted: false,
enabledTransports: ['ws']
});
window.Echo.channel('orders')
.listen('OrderStatusUpdate', e => {
console.log("Status has been updated behind the scenes.")
console.log(e);
});
And the .env is:
PUSHER_APP_ID=5073cdd7d79e501f
PUSHER_APP_KEY=3ad3a4d1eb46d4d4794533414dce747a
PUSHER_APP_SECRET=
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
But in the client javascript (using Chrome) I see an error message in the browser console: WebSocket connection to 'ws://192.168.15.36:6001/app/3ad3a4d1eb46d4d4794533414dce747a?protocol=7&client=js&version=7.0.6&flash=false' failed: Connection closed before receiving a handshake response.
How to solve this problem? Is there something that is needed furthermore to make Laravel Echo Server works as a Pusher replacement?
May worth show here the laravel echo server settings as well:
{
"authHost": "http://192.168.15.36",
"authEndpoint": "/broadcasting/auth",
"clients": [
{
"appId": "5073cdd7d79e501f",
"key": "3ad3a4d1eb46d4d4794533414dce747a"
}
],
"database": "redis",
"databaseConfig": {
"redis": {},
"sqlite": {
"databasePath": "/database/laravel-echo-server.sqlite"
}
},
"devMode": true,
"host": null,
"port": "6001",
"protocol": "http",
"socketio": {},
"secureOptions": 67108864,
"sslCertPath": "",
"sslKeyPath": "",
"sslCertChainPath": "",
"sslPassphrase": "",
"subscribers": {
"http": true,
"redis": true
},
"apiOriginAllow": {
"allowCors": true,
"allowOrigin": "http://192.168.15.36:80",
"allowMethods": "GET, POST",
"allowHeaders": "Origin, Content-Type, X-Auth-Token, X-Requested-With, Accept, Authorization, X-CSRF-TOKEN, X-Socket-Id"
}
}

Laravel broadcasting to presence channel has no member data

I'm trying to broadcasting to presence channel. But auth seems to not send a member data.
Versions
composer.json
"laravel/framework": "6.20.22",
"laravel/passport": "9.0",
"predis/predis": "1.1.7",
package.json
laravel-echo-server: 1.6.2
"socket.io-client": "2.4.0",
"laravel-echo": "1.10.0",
Server side
.env
BROADCAST_DRIVER=redis
QUEUE_CONNECTION=sync
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_PREFIX=
laravel-echo-server.json
{
"authHost": "http://localhost:8080",
"authEndpoint": "/custom/broadcasting/auth",
"clients": [],
"database": "redis",
"databaseConfig": {
"redis": {},
"sqlite": {
"databasePath": "/database/laravel-echo-server.sqlite"
}
},
"devMode": true,
"host": null,
"port": "6001",
"protocol": "http",
"socketio": {},
"secureOptions": 67108864,
"sslCertPath": "",
"sslKeyPath": "",
"sslCertChainPath": "",
"sslPassphrase": "",
"subscribers": {
"http": true,
"redis": true
}
}
web.php
Route::post('/custom/broadcasting/auth', function (\Illuminate\Http\Request $request) {
Log::debug('custom auth');
$user = User::where('id', 1)->first();
return [$user->name];
});
Route::post('/messages', function (\Illuminate\Http\Request $request) {
broadcast(new \App\Events\MyEvent($request->clientId, $request->message));
return [
'message' => $request->message
];
});
MyEvent.php
class MyEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $message;
public $clientID;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($clientID, $message)
{
$this->message = $message;
$this->clientID = $clientID;
}
public function broadcastOn()
{
return new PresenceChannel('chat.' . $this->clientID);
}
}
config/database.php
'redis' => [
'client' => env('REDIS_CLIENT', 'predis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
],
],
client side
bootstrap.js
import Echo from "laravel-echo"
window.io = require('socket.io-client');
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname + ':8080',
authEndpoint: '/custom/broadcasting/auth',
authorizer: (channel, options) => {
return {
authorize: (socketId, callback) => {
axios.post('/custom/broadcasting/auth', {
socket_id: socketId,
channel_name: channel.name
})
.then(response => {
callback(false, response.data);
})
.catch(error => {
callback(true, error);
});
}
};
},
});
client.vue
export default {
props: ['clientId'],
data: function () {
return {
messages: [],
newMessage: '',
statusMessage: 'Joined a room',
isStarted: false,
members: [],
checkedMember: '',
}
},
created() {
},
computed: {
channel() {
var joining = window.Echo.join(`chat.${this.clientId}`)
console.log(joining);
return window.Echo.join(`chat.${this.clientId}`)
}
},
mounted() {
console.log(`Joining to chat.${this.clientId}`)
console.log('this channel', this.channel);
this.channel.here((users) => {
console.log(`Joined chat.${this.clientId}`)
this.members = users;
})
.joining((user) => {
console.log('Joining ', JSON.stringify(user));
this.members.put(user)
})
.leaving((user) => {
this.members = this.members.filter(member => user.id !== member.id)
console.log(`leaved ${user.name}`);
})
.error((error) => {
console.error(error);
})
.listen('MyEvent', (e) => {
console.log(e);
this.messages.push({
message: e.message.message,
user: e.user
});
});
},
methods: {
addMessage(clientId, message) {
axios.post('/messages', {
clientId,
message
}).then(response => {
this.messages.push({
message: response.data.message.message,
// user: response.data.user
});
});
},
sendMessage() {
this.addMessage(this.clientId, this.newMessage);
this.newMessage = '';
},
sendStartMessage() {
this.statusMessage = 'Started';
this.isStarted = true;
this.addMessage(this.clientId, 'start');
},
sendStopMessage() {
this.statusMessage = 'Ready';
this.isStarted = false;
this.addMessage(this.clientId, 'stop');
}
}
}
</script>
console debug
SocketIoPresenceChannel {events: {…}, listeners: {…}, name: "presence-chat.my-client-id", socket: Socket, options: {…}, …}
ㄴ eventFormatter: EventFormatter {namespace: "App.Events"}
ㄴ events: {presence:subscribed: ƒ, presence:joining: ƒ, presence:leaving: ƒ, App\Events\MyEvent: ƒ}
ㄴ listeners: {presence:subscribed: Array(1), presence:joining: Array(1), presence:leaving: Array(1), App\Events\MyEvent: Array(1)}
name: "presence-chat.my-client-id"
ㄴ options: {auth: {…}, authEndpoint: "/custom/broadcasting/auth", broadcaster: "socket.io", csrfToken: null, host: "localhost:8080", …}
ㄴ socket: Socket {io: Manager, nsp: "/", json: Socket, ids: 0, acks: {…}, …}
__proto__: SocketIoPrivateChannel
If I try with private and public channel, It works.
But If I try with Presence channel, always not working.
laravel echo server said
Event: [object Object]
(node:75981) UnhandledPromiseRejectionWarning: TypeError: Cannot convert undefined or null to object
at /Users/myname/.nvm/versions/node/v14.5.0/lib/node_modules/laravel-echo-server/dist/channels/presence-channel.js:78:21
at processTicksAndRejections (internal/process/task_queues.js:93:5)
(node:75981) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 3)
Unable to join channel. Member data for presence channel missing
And wired thing is that I can listen an event.
Could you let me know the problem?

Laravel (Echo, Sanctum, websockets) + Pusher + Nuxtjs SPA

I'm trying to send events to private channel, but I can't receive them.
Otherwise it works fine for public channels.
Here's my code :
Plugin : Echo.js
window.Echo = new Echo({
broadcaster: 'pusher',
key: process.env.MIX_PUSHER_PUBLIC_KEY,
wsHost: process.env.VUE_APP_WEBSOCKETS_SERVER , //window.location.hostname,//
cluster: process.env.MIX_PUSHER_APP_CLUSTER,
wsPort: 6001,
forceTLS: false,
/* key: process.env.MIX_ABLY_PUBLIC_KEY,
wsHost: 'realtime-pusher.ably.io',
wsPort: 443, */
disableStats: true,
encrypted: true,
auth: {
headers: {
'X-CSRF-TOKEN': Cookies.get('XSRF-TOKEN'),
},
},
authorizer: (channel, options) => {
return {
authorize: (socketId, callback) => {
axios.post(process.env.VUE_APP_WEBSOCKETS_SERVER+'/api/broadcasting/auth', {
socket_id: socketId,
channel_name: channel.name
})
.then(response => {
callback(false, response.data);
})
.catch(error => {
callback(true, error);
});
}
};
},
})
Laravel Broadcasting.php :
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => false,
'encrypted' => true,
'host' => '127.0.0.1',
'port' => 6001,
'scheme' => 'http',
],
Api Routes :
Broadcast::routes(['middleware' => ['auth:sanctum']]);
Client Side :
created() {
Pusher.logToConsole = true;
Echo.logToConsole = true;
window.Echo.private('shop')
.listen('MessageSent', (e) => {
console.log('Hi' + e)
})
},
I notice that pusher subscribed to the channel with no errors, but the result in console is like this :
Pusher : : ["Event sent",{"event":"pusher:subscribe","data":{"channel":"private-shop"}}]
Hi maybe this could help you. Please make sure that the response in the authorize function is used correctly. I realize after a while that the response token was send as response not response.data. Try this.
window.Echo = new Echo({
broadcaster: 'pusher',
key: process.env.MIX_PUSHER_PUBLIC_KEY,
wsHost: process.env.VUE_APP_WEBSOCKETS_SERVER , //window.location.hostname,//
cluster: process.env.MIX_PUSHER_APP_CLUSTER,
wsPort: 6001,
forceTLS: false,
/* key: process.env.MIX_ABLY_PUBLIC_KEY,
wsHost: 'realtime-pusher.ably.io',
wsPort: 443, */
disableStats: true,
encrypted: true,
auth: {
headers: {
'X-CSRF-TOKEN': Cookies.get('XSRF-TOKEN'),
},
},
authorizer: (channel, options) => {
return {
authorize: (socketId, callback) => {
axios.post(process.env.VUE_APP_WEBSOCKETS_SERVER+'/api/broadcasting/auth', {
socket_id: socketId,
channel_name: channel.name
})
.then(response => {
callback(false, response);
})
.catch(error => {
callback(true, error);
});
}
};
},
})

Resources