Laravel-websockets: Feature Request: Another Type of Channel

Created on 11 Dec 2020  路  3Comments  路  Source: beyondcode/laravel-websockets

Based on this opened discussion on laravel/framework repo https://github.com/laravel/framework/discussions/35569#discussion-59436

How could we implement something similar to that use case?

We want to be able to broadcast to some filtered subscribers.

But the challenge would be
How could lar-websockets determine which subscribed users to broadcast message to.

I checked this file https://github.com/beyondcode/laravel-websockets/blob/89ace081399f197de46c9b5e7466611da62f4add/src/WebSockets/Channels/PresenceChannel.php

And im thinking we could implement another type of channel

Maybe something like DataChannel or QueryChannel
This channel should collect some initial user data when they subscribe

E.g

{
    "userdata": {
        "age": 20,
        "gender": "female"
    }
}

Then during onMessage
There should be an instruction from the broadcaster for which subscribed users to receive the message

E.g

{
    "message": "...",
    "conditions": "Send to subscribed users having age from 20-25 and gender is equal to female"
}

Then based on the instructions received from the onMessage

The message could be sent this way:

array_map(function ($conn){
    if($conn->gender == "female" && in_array($conn->age, [20, ..., 25])){
        // send the message
    }
}, $connections);

Is this a worthy feature we can try to implement?

enhancement good first issue help wanted

All 3 comments

I am also thinking of an alternative way to pass this limitation.

using Presence channel as an example which stores subscribers info

then before we broadcast a message we could send a request to getUserIds of users subscribed to the message channel
for each userId then filter users that wont be receiving the message,
then message can be broadcasted with array of filtered userId and websocket server should only broadcast to users which id is in the broadcast userIds array.

Demonstration:

public function broadcastFor(array $userIds){
    return User::whereIn('id', $userIds)->whereIn('age',  [20, ..., 25])
    ->whereGender('female')
    ->get('id')->toArray();
}

laravel should provide broadcastFor method with userIds argument
laravel should have made a request to getUserIds of the message channel

From the websocket server message should be broadcasted this way

array_map(function ($conn){
    if(in_array($conn->user_id, $userIds)){
        // send the message
    }
}, $connections);

Would this be a good idea?

I think this would be a worthy feature.

FTR it's possible to do it the other way around, with existent software : instead of filtering server-side, you subscribe each user to all channels that might concern them. I have worked on a websocket-synced calendar : if a user subscribes to a given day, you add them to said day, and also corresponding week, month, plus previous or next month if applicable (accounting for calendar overlaps between two months). I think it is generally possible to determine the subscribing conditions in advance. So in your case, you'd be subscribing to 6 channels.

Your solution would be more memory-efficient though. And would account for the times when you cannot determine these conditions.

We might have an advantage here over socket.io, because it's all PHP, so extracting the conditions from the existing software might be easier.

Hello @rennokki

Based on my recent research i found out that we can securely serialize php functions for execution on other server using this library https://github.com/opis/closure

For example we can have a method that accepts a closure that will compute if a connection can receive the broadcast message.

broadcast(new UserCreated)->onlyTo(function($connection) {
    return in_array($connection->age, [20, 21, ..., 25]) && $connection->gender == 'female';
});

The onlyTo method is expected to serialize the closure and send along to the websocket server. then the websocket should unserialize the closure and invoke the method by passing argument of each connection to the closure.

Serialization from laravel server:

use Opis\Closure\SerializableClosure;

SerializableClosure::setSecretKey(config('broadcast.connections.pusher.secret'));

// Here you can serialize closures
$serializedOnlyTo = serialize(new SerializableClosure($onlyTo));

Unserilization from websocket server:

use Opis\Closure\SerializableClosure;

SerializableClosure::setSecretKey(config('websockets.apps.0.secret'));

// Here you can fetch closures from remote and unserialize them
$onlyTo = unserialize($serializedOnlyTo);

array_map(function ($conn){
    if($onlyTo($conn)){
        // send the message
    }
}, $connections);

Please let me no if its a good idea and i will try to implement the feature using this way.

Was this page helpful?
0 / 5 - 0 ratings