Pusher WebSocketError -> code:4200 - message:Please reconnect immediately - php

we are using Pusher as our notification system and it is causing problem sometimes.
The issue is sometimes the connection closes automatically and the following error is printed on the console (actually this is an older error I used to get on console until some weeks ago):
Pusher : Error :
{"type":"WebSocketError","error":{"type":"PusherError","data":{"code":1006}}}
And recently I see this one:
Pusher : Error : {"type":"WebSocketError","error":{"type":"PusherError","data":{"code":4200,"message":"Please reconnect immediately"}}}
Here is the client (JS) code:
function initPusher(user) {
if (pusher) {
return;
} else {
Pusher.logToConsole = false;
pusher = new Pusher('pusher key', {
cluster: 'eu',
encrypted: true
});
try {
var channel = pusher.subscribe(user.user_channel);
channel.bind('app_event', function(data) {
if (data['event_type'] === 'reply-message') {
$rootScope.$broadcast('REPLY_RECEIVED', data);
} else if (data['event_type'] === 'new-message') {
$rootScope.$broadcast('MESSAGE_RECEIVED', data);
}
});
} catch(err) {
$state.go('logout');
}
}
}
This function is called on login and every page refresh.
Although I guess this is a client side issue I add the relevant PHP code where the API is instantiated in index.php:
require_once __DIR__.'/../vendor/pusher/pusher-php-server/lib/Pusher.php';
$options = array(
'cluster' => 'eu',
'encrypted' => true
);
$pusher = new Pusher(
'pusher key',
'secret',
'app id,
$options
);
Further Details
Backend framework in use is Silex and the frontend one AngularJS.
We are using the EU cluster of Pusher and it works fine while the connection is still open.
I have already seen this link but couldn't find an answer to my problem there.

Related

Problem with multiple Axios GET Requests NodeJS/MySQL Events

i have a problem with my NodeJS/MySQL-Events (from rodrigogs on Github) solution and hope someone here can help me.
Current setup looks like this:
MySQL Server <-> Socket.io with MySQL-Events listening for new Database Entrys (trigger on INSERT and UPDATE)
-> Axios GET Request from index.js to sync.php (different Server)
-> calling Method from class.php inside sync.php with data from that GET Request
No problems with Socket.IO clients. Just with to many GET Request at the same time from NodeJS/Socket Server -> Apache
If i get just a few new database entrys separately, everything works fine.
If i get a lot of new database entrys at almost the same time, some of them not running the Method from sync.php/class.php
index.js (Node/Socket Server with MySQL-Events Trigger)
const program = async () => {
const instance = new MySQLEvents(conn, {
startAtEnd: true,
excludedSchemas: {
mysql: true,
},
});
await instance.start();
const agent = new https.Agent({
rejectUnauthorized: false
});
instance.addTrigger({
name: 'INSERT',
expression: 'table.column',
statement: MySQLEvents.STATEMENTS.INSERT,
onEvent: (event) => {
console.log("New Entry in Database " + event)
const id = event.affectedRows[0].after.ID;
const status = event.affectedRows[0].after.STATUS;
if (status === 11) {
axios.get('https://serverurl.dev/sync.php', {
params: {
id: id,
status: status
},
withCredentials: true,
httpsAgent: agent,
auth: {
username: 'user',
password: 'pwd'
}
})
.then(response => {
console.log(response)
})
.catch(error => {
console.log(error)
})
}
}
});
instance.on(MySQLEvents.EVENTS.CONNECTION_ERROR, console.error);
instance.on(MySQLEvents.EVENTS.ZONGJI_ERROR, console.error);
};
serverurl.dev/sync.php
<?php
if (!empty($_GET)){
$id = $_GET['id'];
$status = $_GET['status'];
$classname->method($id, $status);
}
serverurl.dev/class.php
public function method($id, $status)
{
// lots of things happen here
}
My guess is that the method in class.php isnt finished running while the next GET Requests are coming in and calling the method again to fast?
Any ideas how to solve this problem?

Laravel Echo + Pusher subscription_error on private channel

I can't manage to get echo and pusher working when using private channels, googled for two days and found nada.
What seems to be happening is some sort of problem with the authentication (I'm using Laravel basic Auth) cause I can subscribe to public channels
routes/channels.php
Broadcast::channel('private-ci-received-{userId}', function ($user, $userId) {
return (int) $user->id === (int) $userId;
});
Broadcast::channel('private-ci-received-{toUserId}', function ($currentUser, $toUserId) {
return true;
});
bootstrap.js
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,
logToConsole: true,
encrypted: true,
});
default.blade.php(main layout)
Pusher.logToConsole = true;
Echo.logToConsole = true;
Echo.private('ci-received-{{ Auth::user()->id}}')
.listen('.CIReceived', (e) => {
console.log(e);
});
What get printed on the console is :
Pusher : No callbacks on private-ci-received-1 for pusher:subscription_error
It's a pretty generic error, then for debug purposes I tried to bind the error using Pusher (not laravel echo)
var pusher = new Pusher('MYSECRETAPPKEYHERE', {
cluster: 'us2',
forceTLS: true
});
var channel = pusher.subscribe('private-ci-received-1');
channel.bind('pusher:subscription_error', function(data) {
console.log(data);
});
console.log(data) output
JSON returned from webapp was invalid, yet status code was 200
The default authEndPoint is /broadcasting/auth IIRC I think it expects to return a JSON but instead it returns the HTML CODE from my page.
Those routes are created by the framework itself and from what I've read Laravel echo and Laravel Auth should work great together without much fiddling.
My .env file is correct i'm using pusher as broadcast driver and BroadcastServiceProvider is properly uncommented.
Can anyone shed a light in the matter? Thanks
This worked for me
Default value of .env is
BROADCAST_DRIVER=log
Pls change it to
BROADCAST_DRIVER=pusher
I was also getting ["No callbacks on private-user.1 for pusher:subscription_error"]
After making the above changes, it works fine for me
Set APP_DEBUG=true in your .env file and check the HTTP response of your auth route in the network section of your browser's developer tools. If authentication is successful, your laravel app should respond with JSON like this:
{"auth": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
If there's an error it will show you in the response since you set debug mode to true.
You do not need to add private- onto your channel in broadcasting.php this is done automagically
Try this
Broadcast::channel('ci-received-{userId}', function ($user, $userId) {
return (int) $user->id === (int) $userId;
});

Authorization for laravel passport through socket.io for broadcasting channels

I'm using laravel 5.3 + passport for authorization, Laravel is my back-end API which is restful.
front-end is written in angular.js which communicate with API with rest requests.
For Real-time notifications, I've used laravel broadcasting events + redis, and socket.io for socket server and socket client in angular.js.
I want to authorize these events and I've done it far as I could :
BroadcastServiceProvider :
public function boot()
{
Broadcast::routes(['middleware' => ['auth:api']]);
Broadcast::channel('App.User.*', function ($user, $userId)
{
return (int) $user->id === (int) $userId;
});
Broadcast::channel('notifs.*', function ($user, $userId) {
return $user->id === (int) $userId;
});
}
This is my socket.js code which runs my socket server :
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var Redis = require('ioredis');
var redis = new Redis();
redis.psubscribe('*', function(err, count) {});
redis.on('pmessage', function(subscribed, channel, message) {
console.log(channel);
message = JSON.parse(message);
io.emit(channel + ':' + message.event, message.data);
});
http.listen(3000, function () {
console.log('Listening on Port 3000');
});
redis.on("error", function (err) {
console.log(err);
});
The problem is I don't know how to authenticate these broadcasting events in socket server and also how to authorize the user in angular.js (SPA) to listen to these events.
I'd appreciate any help.
I'd definitely take a look at socketio-auth.
This module provides hooks to implement authentication in socket.io
without using querystrings to send credentials, which is not a good
security practice.
Another approach I took recently was simple token based authentication using JWT tokens (njwt).
I did not want to recreate the authentication code that checks user credentials within Node.js. (which in my case can't even connect to the database anyway). Rather, I'd let the PHP application that was using the socket leverage its already established authentication system. Passing a signed token with the socket connect requests.
Your node.JS code might look something like...
primus.on('connection', function (spark) {
logger.debug('primus event connection. spark id: ' + spark.id);
spark.on('data', function(data) {
var action = data.action;
njwt.verify(data.token, JWT_SECRET, function(err, verifiedJwt) {
if (err) {
logger.warn('Bad JWT Token! ' + spark.id + ' Error: ' + err);
spark.user = {id:null, is_authed: false, is_admin: false, application_ini: null};
spark.end('Bad Token Request');
return; //->
}
spark.user = { 'id': verifiedJwt.body['user_id'],
'is_authed': verifiedJwt.body['is_authed'],
'application_ini': verifiedJwt.body['application_ini'],
'is_admin': verifiedJwt.body['is_admin']};
sockoasRooms.connect(spark.id, spark.user.application_ini, spark.user.id);
switch (action) {
...
And then on the PHP side, you'll need some code for generating the JWT tokens, but use is very simple. Something like:
<?php
$tokenPayload = [ 'user_id' => ($this->currentUser) ? $this->currentUser->getId() : 0,
'is_authed' => ($this->currentUser) ? true : false,
'application_ini' => (string) APPLICATION_INI,
'is_admin' => (bool) ($this->currentUser) ? $this->currentUser->isAdministrator() : false,
'now' => time()
];
$jwtToken = \OAS_JWT::encode($tokenPayload, SOCK_OAS_JWT_KEY);
?>
$(document).ready(function() {
primus = Primus.connect('ws://<?=SOCK_OAS_IP?>:<?=SOCK_OAS_PORT?>/_admin');
primus.on('open', function () {
showConnected();
// Send request to subscribe
primus.write({action: 'dashboard-dump', token: '<?=$jwtToken?>'});
consoleWrite('Connected to dashboard.');
});
You can evaluate the time component to avoid replay attacks. Anyway, sounds like this approach might meet your needs.
Off topic but I'd also suggest taking a look at primus. It acts as a "universal wrapper for real-time frameworks". This lets you abstract things in a way so you could swap out the socket libraries with no hassle. Might be a little lower level (engine.IO) than what you are using though.

Pusher Private Channel Auth info required to subscribe to private-asdf

Trying to subscribe to a private channel with Pusher. But every time it tries to subscribe I get the error below. Which makes no sense since it's clearly returning the auth info.
Pusher : Error : {"type":"WebSocketError","error":{"type":"PusherError","data":{"code":null,"message":"Auth info required to subscribe to private-sadfsadf"}}}
Normally it's returning the auth json (below) with status 200.
"{\"auth\":\"0b1ce844906bd4d82cb4:21571e5667bf99f17bbf67ae0411594560748fde30b9edeca653653158f8a1f5\"}"
Pusher PHP (shortened)
$pusher = new Pusher($app_key, $app_secret, $app_id);
$auth = $pusher->socket_auth($postvars['channel_name'], $postvars['socked_id']);
if ($auth)
return $response->withJSON($auth);
else
return $response->withStatus(403);
Pusher JS
var pusher = new Pusher('0b1ce844906bd4d82cb4', {
cluster: 'us2',
encrypted: true,
authEndpoint: '{{site.uri.public}}/chat/auth/{{game.id}}',
authTransport: 'ajax',
auth: {
params: {
'csrf_name': '{{site.csrf.name}}',
'csrf_value': '{{site.csrf.value}}'
},
headers: {
'{{site.csrf.name}}': '{{site.csrf.value}}'
}
}
});
I had this error and it was caused by having the BROADCAST_DRIVER set to redis instead of pusher.
it was because my endpoint /pusher/auth returned the signature in JSON format.
So, I just changed this:
return $response
To this:
return json_decode($response)
NB: PUSHER signature use hash_hmac to generate hash:
you can compare you entryPoint return with
$sig = hash_hmac('sha256',$socket, $channelNam);
var_dump($sig);

Private one to one chat structure with pusher

I've implement chat with pusher and it works fine. But the problem is everyone can see it, there is no restriction to hide or make it private. How to make it private one to one chat room that other users can't able to see their conversations. I've call widget like this:
jQuery(function() {
var pusher = new Pusher("MY_APP_KEY" , { authEndpoint: '/pusher_auth.php' })
var chatWidget = new PusherChatWidget(pusher, {
appendTo: "#pusher_chat_widget"
});
});
and in pusher_auth.php
global $user;
if ($user->uid)
{
$pusher = new Pusher(APP_KEY, APP_SECRET, APP_ID);
echo $pusher->socket_auth($_POST['channel_name'], $_POST['socket_id']);
}
else
{
header('', true, 403);
echo "Forbidden";
}
so please tell me how its possible,
Thanks.

Categories