Skip to content
This repository was archived by the owner on Feb 7, 2024. It is now read-only.

Exist a way to implement webhooks? #80

Closed
joaokamun opened this issue Jan 9, 2019 · 22 comments
Closed

Exist a way to implement webhooks? #80

joaokamun opened this issue Jan 9, 2019 · 22 comments

Comments

@joaokamun
Copy link

joaokamun commented Jan 9, 2019

The only missing feature for me fully migrate from pusher is the event webhooks, I could not find anything about it. Is something planned to have? I can contribute to that, just want to know if you guys have something in mind or some direction to follow.

Regards,

@enzonotario
Copy link

I also want this!
For now (maybe it is a wrong way, but it works) I just have created a class that extends BeyondCode\LaravelWebSockets\WebSockets\WebSocketHandler, and a custom Router, and put my own logic in certain hooks...

@stayallive
Copy link
Contributor

This would be really cool but possibly also tricky looking at the docs it has a few "specialties" that requires some work to make this work correctly.

For example: https://pusher.com/docs/webhooks#delay

Just spit-ballin' here, but it might be a good one to write events to an Redis buffer/list and let another process/cron handle the actual sending to prevent adding to much work to the websocket server process causing it to block more than needed (which also would be an easy-ish way to get batched hook working easily).

@joaokamun
Copy link
Author

joaokamun commented Jan 11, 2019

@stayallive So basically the main feature to be develop here could be just an option to save events somewhere (like redis), and each individual project cares about what to do with the stored events?

@stayallive
Copy link
Contributor

@joaokamun well, I would implement a php artisan websockets:webhooks command or something like that in this package so you could just add to a cron or daemon running that command which handles all the webhook sending.

@stefandanaita
Copy link

stefandanaita commented Jan 20, 2019

I also want this!
For now (maybe it is a wrong way, but it works) I just have created a class that extends BeyondCode\LaravelWebSockets\WebSockets\WebSocketHandler, and a custom Router, and put my own logic in certain hooks...

@enzonotario could you please share an example of what you've done? Thanks!

Update: Managed to get it done by extending the websockets.router singleton in the IoC. Thanks for the idea, works nice and smooth. However, a nice webhooks implementation that allows 2 way communication between Laravel and the frontend would be amazing. CC @mpociot @freekmurze

@enzonotario
Copy link

@stefandanaita sorry for the delay... I just have registered a singleton in my AppServiceProvider:

    public function register()
    {
        $this->app->singleton('websockets.router', function () {
            return new Router();
        });
    }

Router.php

<?php

namespace App\WebSockets\Server;

use App\WebSockets\WebSockets\WebSocketHandler;
use BeyondCode\LaravelWebSockets\HttpApi\Controllers\FetchChannelController;
use BeyondCode\LaravelWebSockets\HttpApi\Controllers\FetchChannelsController;
use BeyondCode\LaravelWebSockets\HttpApi\Controllers\FetchUsersController;
use BeyondCode\LaravelWebSockets\HttpApi\Controllers\TriggerEventController;

class Router extends \BeyondCode\LaravelWebSockets\Server\Router
{

    public function echo()
    {
        $this->get('/app/{appKey}', WebSocketHandler::class);

        $this->post('/apps/{appId}/events', TriggerEventController::class);
        $this->get('/apps/{appId}/channels', FetchChannelsController::class);
        $this->get('/apps/{appId}/channels/{channelName}', FetchChannelController::class);
        $this->get('/apps/{appId}/channels/{channelName}/users', FetchUsersController::class);
    }

}

WebSocketHandler.php

<?php

namespace App\WebSockets\WebSockets;

use Ratchet\ConnectionInterface;
use Ratchet\RFC6455\Messaging\MessageInterface;

class WebSocketHandler extends \BeyondCode\LaravelWebSockets\WebSockets\WebSocketHandler
{

    public function onMessage(ConnectionInterface $connection, MessageInterface $message)
    {
        // My custom code...

        parent::onMessage($connection, $message);
    }

}

but sure, this is a weird way. Extending as you said in #21 I think that is a better way!

@nicolasvahidzein
Copy link

@stefandanaita You sir are BRILLIANT!!!!!!!!!! This was a lifesaver!!! Thank you so much. Quick question though, do you know i can know which client disconnect from the app when the connection is closed? That's the whole point of extending this package. I need to tell the chat app he is offline after a non graceful log out. Thanks.

@wmfairuz
Copy link

@stefandanaita You sir are BRILLIANT!!!!!!!!!! This was a lifesaver!!! Thank you so much. Quick question though, do you know i can know which client disconnect from the app when the connection is closed? That's the whole point of extending this package. I need to tell the chat app he is offline after a non graceful log out. Thanks.

@nicolasvahidzein Did you figured out how to identify user once we extend the WebSocketHandler?

@ame1337
Copy link

ame1337 commented Aug 15, 2020

Hi,
In my custom onClose function how can I get user's id or username if I use presence channels?

@rennokki rennokki mentioned this issue Aug 15, 2020
14 tasks
@rennokki
Copy link
Collaborator

@noobshooter27 The connection can make use of the channel manager and you can get the connection from there.

@rennokki
Copy link
Collaborator

2.x will contain extendable hooks: #465

@hengjingyoong
Copy link

Hi @rennokki , may I know how can I get the private channel name from the $connection in my custom onClose function?
Tried this $this->channelManager->getChannels($connection->app->id), but it seem like don't have the channel name inside.

@ame1337
Copy link

ame1337 commented Aug 25, 2020

Hi @rennokki , may I know how can I get the private channel name from the $connection in my custom onClose function?
Tried this $this->channelManager->getChannels($connection->app->id), but it seem like don't have the channel name inside.

@hengjingyoong
try dd($this->channelManager); you'll find everything there

@rennokki
Copy link
Collaborator

@hengjingyoong Have you called parent::onClose($connection, $exception) before anything else in your custom method?

@hengjingyoong
Copy link

hengjingyoong commented Aug 25, 2020

@rennokki, I just used the original WebSocketHandler to test it out.

Thanks @noobshooter27 , finally I managed to find out what is the channel name from the closing connection.

What I did was defined a custom function in BeyondCode\LaravelWebSockets\WebSockets\Channels\Channel

    public function checkConnection($socketId)
    {
        if ($this->subscribedConnections[$socketId] ?? null) {
            return $this->channelName;
        }

        return false;
    }

and then in BeyondCode\LaravelWebSockets\WebSockets\WebSocketHandler, get the channel name before it closed.

    public function onClose(ConnectionInterface $connection)
    {
        $allChannels = $this->channelManager->getChannels($connection->app->id);

        foreach($allChannels as $channel){
            if ($channelName = $channel->checkConnection($connection->socketId)) {
                break;
            }
        }
        dd($channelName);

        $this->channelManager->removeFromAllChannels($connection);

        DashboardLogger::disconnection($connection);

        StatisticsLogger::disconnection($connection);
    }

But I'm not sure if that is secure to work in this way.

ps: my working scenario is attach the user_id to the private channel name, that's why I need to get back the user_id from channel name, and notify my backend which user is offline. This is to handle the non graceful log out mentioned by @nicolasvahidzein

@rennokki
Copy link
Collaborator

@hengjingyoong It's highly recommended to not change any vendor/* files.

@hengjingyoong
Copy link

hengjingyoong commented Aug 25, 2020

@hengjingyoong It's highly recommended to not change any vendor/* files.

Do you have any though on getting the channel name without adding my own function in BeyondCode\LaravelWebSockets\WebSockets\Channels\Channel?

@hengjingyoong
Copy link

Finally I figure out how to get the closing channel name.

Here is it.

<?php

namespace App\WebSocket;

use Ratchet\ConnectionInterface;
use BeyondCode\LaravelWebSockets\WebSockets\WebSocketHandler as BaseWebSocketHandler;

class CustomWebSocketHandler extends BaseWebSocketHandler
{
    public function onClose(ConnectionInterface $connection)
    {
        $allChannels = $this->channelManager->getChannels($connection->app->id);

        foreach($allChannels as $channelName => $channel){

            if($channel->getSubscribedConnections()[$connection->socketId] ?? null) {
                $closingChannel = $channel->getChannelName();
                break;
            }
        }

        dd($closingChannel);

        parent::onClose($connection);
    }
}

ps: I was missed out the getSubscribedConnections function in BeyondCode\LaravelWebSockets\WebSockets\Channels\Channel lol

@damianed
Copy link

Thank you @hengjingyoong!
I made just had to do some minor changes because it was throwing an error, I will leave it here in case someone else needs it

<?php

namespace App\WebSocket;

use Ratchet\ConnectionInterface;
use BeyondCode\LaravelWebSockets\WebSockets\WebSocketHandler as BaseWebSocketHandler;

class CustomWebSocketHandler extends BaseWebSocketHandler
{
    public function onClose(ConnectionInterface $connection)
    {
        $allChannels = $this->channelManager->getChannels($connection->app->id);
        $closingChannel = null;

        foreach($allChannels as $channelName => $channel){

            if($channel->getSubscribedConnections()[$connection->socketId] ?? null) {
                $closingChannel = $channelName;
                break;
            }
        }

        dd($closingChannel);

        parent::onClose($connection);
    }
}

@S1ipKn0T
Copy link

S1ipKn0T commented May 10, 2022

Thank you @hengjingyoong! I made just had to do some minor changes because it was throwing an error, I will leave it here in case someone else needs it

<?php

namespace App\WebSocket;

use Ratchet\ConnectionInterface;
use BeyondCode\LaravelWebSockets\WebSockets\WebSocketHandler as BaseWebSocketHandler;

class CustomWebSocketHandler extends BaseWebSocketHandler
{
    public function onClose(ConnectionInterface $connection)
    {
        $allChannels = $this->channelManager->getChannels($connection->app->id);
        $closingChannel = null;

        foreach($allChannels as $channelName => $channel){

            if($channel->getSubscribedConnections()[$connection->socketId] ?? null) {
                $closingChannel = $channelName;
                break;
            }
        }

        dd($closingChannel);

        parent::onClose($connection);
    }
}

Hi thank you for your code but I need users data inside of $allChannels how can i get it?
(In my case I use presence channel)

@garrettboone
Copy link

garrettboone commented Apr 22, 2023

Another approach that is simple is using the custom ArrayChannelManager as indicated in the websockets config, and then make a call like:

    public function removeFromAllChannels(ConnectionInterface $connection)
    {
        // THIS ONE LINE
        $response = Http::get(config('app.url') . '/socket-disconnected/' . $connection->socketId . '/{key-for-security}');
        
        if (!isset($connection->app)) {
            return;
        }

       ....

    }

In web.php:

use App\Http\Controllers\WebsocketController;
Route::get('/socket-disconnected/{socket_id}/{security_key}', [WebsocketController::class, 'socketDisconnected']);

Dispatch the desired job in WebsocketController:

class WebsocketController extends Controller
{
    public function socketDisconnected(Request $request, string $socket_id, string $security_key)
    {
        logger("Socket id received: " . $socket_id);
        if($security_key == config('websockets.security_key')) {
            ProcessWebsocketDisconnects::dispatch($socket_id);
       }
        return response('OK', 200)->header('Content-Type', 'text/plain');
    }
}

@nicolasvahidzein
Copy link

Thank you @garrettboone I will have to try this soon. Sounds awesome.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests