Giter VIP home page Giter VIP logo

Comments (5)

NK0D1NG avatar NK0D1NG commented on June 27, 2024 3

I was finally able to reproduce the issue and solve it. It is important to call the callback on the custom authorizer:

function setupEcho() {
    const token = useCookie('XSRF-TOKEN').value
    const config = useRuntimeConfig()
    channelName.value = 'room.' + roomsStore.currentRoom.roomUuid
    console.log('Channel Name: ' + channelName)
    if (!echo.value) {
        echo.value = new Echo({
        authorizer: (channel, options) => {
        return {
            authorize: (socketId, callback) => {
                const request = $fetch(config.baseURL + '/broadcasting/auth', {
                    async onRequest({ request, options }) {
                    options.method = 'post'
                    options.body = {
                        socket_id: socketId,
                        channel_name: channel.name
                    }
                    options.headers = {'X-XSRF-TOKEN': token,
                    'Accept': 'application/json',
                    },
                    options.credentials = 'include'
                    },
                })
                request.then((response) => {
                    console.log(response)
                    callback(null, response)
                })
            }
        };
        },
        broadcaster: 'pusher',
        key: 'app-key',
        cluster: 'mt1',
        wsHost: 'localhost',
        wsPort: 6001,
        forceTLS: false,
        disableStats: true,
        encrypted: true,
        auth: {
            headers: {'X-XSRF-TOKEN': token,
                       'Accept': 'application/json',
            },
        },
        enabledTransports: ['ws', 'wss'],
    });
    }
}

This part is the important one:

                request.then((response) => {
                    console.log(response)
                    callback(null, response)
                })

I am not sure what callback is used internally by Laravel Echo but it is obligatory for the subscription of private/presence channels to work. All examples in the docs use axios so I had to change some code to work with the fetch API (which is used by the ohmyfetch library). After calling the callback (with a null value as first parameter which seems a bit strange but is also mentioned in the Laravel Docs) the subscription works and I get the event data:

grafik

This issue was not easy to debug, because the same setup works perfectly fine with public channels (including the subscription!) and there are absolutely no errors in the console when trying the same with private channels.

The issue can be closed.

from echo.

jessarcher avatar jessarcher commented on June 27, 2024

Hi @NK0D1NG, I am unable to replicate this issue.

Your example code seemed quite complicated so I just set up a minimal example to confirm that I can subscribe to private channels.

I spun up a Soketi server with Docker, per their docs:

docker run -p 6001:6001 -p 9601:9601 quay.io/soketi/soketi:1.4-16-debian

I set up a fresh Laravel install with Laravel Breeze's Vue stack to scaffold out auth views.

I installed the Pusher and Echo packages:

composer require pusher/pusher-php-server
npm install --save-dev laravel-echo pusher-js

I configured my .env as follows:

BROADCAST_DRIVER=pusher
PUSHER_APP_ID=app-id
PUSHER_APP_KEY=app-key
PUSHER_APP_SECRET=app-secret
PUSHER_HOST=127.0.0.1
PUSHER_PORT=6001
PUSHER_SCHEME=http
PUSHER_APP_CLUSTER=mt1

I uncommented the default Echo configuration in resources/js/bootstrap.js:

import Echo from 'laravel-echo';

import Pusher from 'pusher-js';

window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: import.meta.env.VITE_PUSHER_APP_KEY,
    wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
    wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
    wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
    forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
});

I then logged into the Laravel app and observed Echo connecting to Soketi:

image

I then uncommented App\Providers\BroadcastServiceProvider::class in config.app and set up a channel in routes/channels.php:

Broadcast::channel('foo-channel', fn () => true);

I then created an event with artisan make:event FooEvent and updated it to implement ShouldBroadcast. I updated the constructor to accept a $message and updated the broadcastOn method to use the foo channel:

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

    public function __construct(public string $message)
    {
        //
    }

    public function broadcastOn()
    {
        return new PrivateChannel('foo-channel');
    }
}

In Dashboard.vue I subscribed to the event on the private channel:

Echo.private('foo-channel')
    .listen('FooEvent', (e) => {
        console.log(e);
    });

And observed that Echo made the auth request:

image

I then used Laravel Tinker to dispatch the event:

image

And saw it come through in the console:

image

Are you able to confirm whether the issue exists for you with a minimal setup like this? If not, is there something specific I should do to replicate the issue?

from echo.

NK0D1NG avatar NK0D1NG commented on June 27, 2024

Hey @jessarcher
thanks for investigating into this. The only difference to my setup could be the custom auth guard I am using:

from config/auth.php:

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'vcs' => [
            'driver' => 'session',
            'provider' => 'clients',
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],

        'clients' => [
            'driver' => 'eloquent',
            'model' => App\Models\Client::class,
        ],
    ],

from routes/channels.php:

Broadcast::channel('room.{ruuid}', function ($client, $ruuid) {
    Log::info('Broadcasting.Channel: ' . $ruuid);
    return $client->room_uuid == $ruuid;
}, ['guards' => ['vcs']]);

My
app/Providers/BroadcastServiceProvider.php:

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;

class BroadcastServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Broadcast::routes(['middleware' => 'web', 'guards' => 'vcs']);

        require base_path('routes/channels.php');
    }
}

This is also the reason why my Echo setup seems complicated. I am also using Laravel Sanctum with an external Vue SPA which authenticates using Laravel's default session driver as you can see. Everything is set up like described in the official docs and not the first time I am doing it like this. Because Laravel Sanctum requires the X-XSRF-TOKEN and the Session Cookie to be set and Laravel Echo does not send token/session cookie automatically I added it by myself using the custom authorizer. As well as the application/json header. The Vue SPA is a different repository/project. Authenticating using the custom guard works just fine (for normal API routes and for the channels as well as you can see in the images in my original post). Because I have no monorepo I wasn't able to setup a minimal example for this whole setup yet.

So maybe Echo has a problem with the custom guard?

Can you confirm a subscription message inside the websocket connection? What do your request/responses in the websocket connection look like? Is there a subscription/subscription succeeded message? Can you confirm it works in your minimal example using a custom guard and Laravel Sanctum? I am using just another Eloquent model like:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Spatie\Permission\Traits\HasRoles;
use App\Traits\Uuids;

class Client extends Authenticatable
{
    use HasFactory, HasApiTokens, HasRoles, Uuids;

    protected $guard_name = "vcs";

    public function room()
    {
        return $this->belongsTo(Room::class, 'room_uuid', 'uuid');
    }
}

I also use Redis as my queue driver.

I can also confirm that the event is correctly fired (the Laravel Event Listener gets called and I can see the event payload in the Soketi Logs) - so the only thing missing is the subscription call for the private channel from Laravel Echo..

from echo.

NK0D1NG avatar NK0D1NG commented on June 27, 2024

By the way: I am not using axios - I am using the fetch API (The frontend is developed with NuxtJS which uses ohmyfetch as a fetch API dependency).

from echo.

NK0D1NG avatar NK0D1NG commented on June 27, 2024

@driesvints @jessarcher
I provided two demo repositories (links in the original post) which also demonstrate the issue. They should work out of the box after installing the dependencies. Please follow the README of the Laravel Backend Repo to do the setup and create a first user with a custom guard. Even with this simplified setup Laravel Echo does not subscribe to the private channel.

For the frontend just use the newly created user (model is called 'client') to login. Then click on the Join Room Chat button which calls the listen-method - but the callback never gets executed and there is no subscription message in the websocket connection.

from echo.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.