Search code examples
phplaravelsocketspusherlaravel-echo

Laravel Echo with Pusher not receiving messages on private channel


Every time I switch to the private channel nothing is ever received on the browser but when using just a standard channel it works. My current setup includes a Vue CLI app for the front-end and a Laravel API as the backend using Laravel Passport for authentication.

This is the code which is being used to connect to Pusher.

import LaravelEcho from 'laravel-echo';

// ...

export const Echo = new LaravelEcho({
  broadcaster: 'pusher',
  key: pusherKey,
  cluster,
  encrypted: true,
});

Echo.connector.pusher.config.authEndpoint = `${baseURL}/broadcasting/auth`;

store.watch((state: any) => state.$auth.token, (token: string) => {
  Echo.connector.pusher.config.auth.headers['Authorization'] = `Bearer ${token}`;
});

store.watch((state: any) => state.$auth.user, (n: any, o: any) => {
  if (n) {
    console.log(`App.User.${n.id}`); // This is being logged to the console App.User.1
    Echo.private(`App.User.${n.id}`)
      .listen('.friend.request', (e: any) => {
        console.log({ e });
      });
  }
});

And this is the channel route file:

<?php

Broadcast::channel('App.User.{id}', function ($user, $id) {
    return true;
    // return (int) $user->id === (int) $id;
});

A cut down version of the event.

<?php

namespace App\Events;

// ...

class FriendRequestSent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    // ...

    public function broadcastOn()
    {
        return new PrivateChannel("App.User.{$this->friend->id}");
    }

    // ...
}

And I have set up this testing route:

<?php

Route::get('/', function (Request $request) {
    event(new FriendRequestSent(User::find(1), User::find(1)));
});

After running the Echo.private()... code I can see the following show up in my Pusher dashboard:

Pusher Dashboard Connected

And then after firing off the event from the / route:

Queue

Finally, Pusher picking up the event and nothing showing in the console:

Output

However, if I change new PrivateChannel(...) to new Channel(...) and then use Echo.channel instead of Echo.private it will start to display something in the console.

Does anyone know why this could be happening?


An update to this, I found this webpage: Pusher Docs - Debugging

Pusher :  : ["Event sent",{"event":"pusher:subscribe","data":{"auth":"some_auth_code_returned_from_api_broadcasting_auth","channel":"private-App.User.1"}}]

Pusher :  : ["Event recd",{"event":"pusher_internal:subscription_succeeded","channel":"private-App.User.1","data":{}}]

Pusher :  : ["No callbacks on private-App.User.1 for pusher:subscription_succeeded"]

I can see by the final output No callbacks on private-App.User.1... could be causing the issue but the only things I could find on it were:

  1. Change to BROADCAST_DRIVER=pusher
  2. Attempting this code in channels.php:
<?php

// Notice {userId} rather than {id}.
Broadcast::channel('App.User.{userId}', function ($user, $userId) {
    return (int) $user->id === (int) $userId;
});

I have now tried this with Socket.IO and I'm getting the same results:

Socket.IO console output:

[6:36:11 AM] - Preparing authentication request to: http://isapi.test
[6:36:11 AM] - Sending auth request to: http://isapi.test/api/broadcasting/auth
[6:36:12 AM] - gT9--_C_2c-KwoxLAAAE authenticated for: private-App.User.1
[6:36:12 AM] - gT9--_C_2c-KwoxLAAAE joined channel: private-App.User.1

My Socket.IO configuration:

import io from 'socket.io-client';

(window as any).io = io;

export const Echo = new LaravelEcho({
  broadcaster: 'socket.io',
  host: `${baseURL.substr(0, baseURL.length - 4)}:6001`,
  encrypted: true,
});

Echo.connector.socket.io.opts.authEndpoint = `${baseURL}/broadcasting/auth`;

store.watch(
  (state: any) => state.$auth.token,
  (token: string) => {
    Echo.connector.socket.io.opts.auth.headers.Authorization = `Bearer ${token}`;
  },
);

So I have created a completely fresh install of Vue and Laravel with the following dependencies:

Laravel:

  • Passport
  • Barry CORS
  • Pusher Server

Vue:

  • TypeScript install
  • My custom HTTP wrapper (just wrapping fetch & axios)

And it works so I'm just going to have to find out what I did differently and see if I can solve it that way. If you would like to see this testing code I have placed it in this repository.

https://github.com/kingga/stackoverflow-59449785


Solution

  • As always it was a really simple solution which was overlooked.

    I had two store.watch's; one on the token and one on the user. The user one must have run first and then the token one ran afterwards causing the token to not be set when connecting to the private channel. E.g.

    store.watch(
      (state: any) => state.$auth.token,
      (token: string) => {
        Echo.connector.pusher.config.auth.headers.Authorization = `Bearer ${token}`;
      },
    );
    
    store.watch(
      (state: any) => state.$auth.user,
      (n: any, o: any) => {
        if (n) {
          // Added this line to fix the issue.
          Echo.connector.pusher.config.auth.headers.Authorization = `Bearer ${store.state.$auth.token}`;
          Echo.private(`App.User.${n.id}`).listen(
            '.friend.request',
            (data: any) => {
              // store.state.$friends.requests = data;
              console.log({ data });
            },
          );
        } else if (o) {
          Echo.private(`App.User.${o.id}`)
            .stopListening('.friend.request');
        }
      },
    );