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
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
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
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"
}
}
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?
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);
});
}
};
},
})