mafortis
mafortis

Reputation: 7128

Laravel event with socket-io [receive notifications]

For the first time attempt with laravel and socket-io I am trying to send very simple notification to admins. So far my event is firing but I need help with receiving event notifications.

Logic

It's very basic because I want to understand the process.

  1. User opens page Add Product
  2. Admin gets notification that user X is in App Product page.

So far

So far I can fire event and get user data (user that is in Add Product page)

Need help for

I need help to understand the way that admin receives notifications.

Code

Component script

created() {
  let user = JSON.parse(localStorage.getItem("user"))
  this.listenForBroadcast(user);
},
methods: {
  listenForBroadcast(user) {
    Echo.join('userInAddProduct')
    .here((Loggeduser) => {
      console.log('My user data', Loggeduser);
    });
  }
}

Result of code above

My user data [{…}]
  0:
    id: 1
    name: "Test User"
    photo: "User-1588137335.png"
    __ob__: Observer {value: {…}, dep: Dep, vmCount: 0}
    get id: ƒ reactiveGetter()
    set id: ƒ reactiveSetter(newVal)
    get name: ƒ reactiveGetter()
    set name: ƒ reactiveSetter(newVal)
    get photo: ƒ reactiveGetter()
    set photo: ƒ reactiveSetter(newVal)
    __proto__: Object
    length: 1
    __proto__: Array(0)

Channels route

Broadcast::channel('userInAddProduct', function ($user) {
    return [
        'id' => $user->id,
        'photo' => $user->photo,
        'name' => $user->name
    ];
});

MessagePushed (event file)

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

    public $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function broadcastOn()
    {
        return new PresenceChannel('userInAddProduct');
    }
}

Question

How can I receive notification about this event fire? I want to notify my admin users that user x is in page Add Product?

Update

Since I published this question I've made some changes and here is my latest code + questions.

bootstrap.js

window.io = require('socket.io-client');

window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001',
    auth: { // added authentication token (because all my events are private)
        headers: {
            Authorization: localStorage.getItem('access_token'),
        },
    },
});

Add.vue (add product component where event has to be fired)

listenForBroadcast(user) {
    let ui = JSON.parse(localStorage.getItem("user"))
    Echo.join('userInAddProduct')
    .here((users) => {
        console.log('My user data', users)
    })
    .joining((user) => {
        this.$notify({
            title: '',
            message: user + 'joining',
            offset: 100,
            type: 'success'
        });
    })
    .leaving((user) => {
        this.$notify({
            title: '',
            message: user + 'is leaving new product',
            offset: 100,
            type: 'warning'
        });
    })
    .whisper('typing', (e) => {
        this.$notify({
            title: '',
            message: ui.username + 'is adding new product',
            offset: 100,
            type: 'success'
        })
    })
    .listenForWhisper('typing', (e) => {
        console.log(e)
        this.$notify({
            title: '',
            message: ui.username + 'is entered add new product page.',
            offset: 100,
            type: 'success'
        });
    })
    .notification((notification) => {
        console.log('noitication listener: ', notification.type);
    });
},

Then I've made 4 files to handle events:

Event file

class MessagePushed extends Event implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $user;
    public $product;

    public function __construct(User $user, Product $product)
    {
        $this->user = $user;
        $this->product = $product;
    }

    public function broadcastOn()
    {
        return new PresenceChannel('userInAddProduct');
    }
}

Listener file

class ThingToDoAfterEventWasFired implements ShouldQueue
{
    public function handle(MessagePushed $event)
    {
        //Log testing purpose only
        $user = $event->user->username;
        $product = $event->product->name;

        // Real data that should be broadcasts
        $user2 = $event->user;
        $product2 = $event->product;

        // inform all admins and authorized staffs about new product
        $admins = User::role(['admin', 'staff'])->get();
        foreach($admins as $admin) {
            $admin->notify(new UserAddProduct($user2, $product2));
        }

        Log::info("Product $product was Created, by worker: $user");
    }
}

Notification

class UserAddProduct extends Notification implements ShouldQueue
{
    use Queueable;

    protected $product;
    protected $user;

    public function __construct(User $user, Product $product)
    {
        $this->product = $product;
        $this->user = $user;
    }

    public function via($notifiable)
    {
        return ['database', 'broadcast'];
    }

    public function toDatabase($notifiable)
    {
        return [
            'user_id' => $this->user->id,
            'user_username' => $this->user->username,
            'product_id' => $this->product->id,
            'product_name' => $this->product->name,
        ];
    }

    public function toArray($notifiable)
    {
        return [
            'id' => $this->id,
            'read_at' => null,
            'data' => [
                'user_id' => $this->user->id,
                'user_username' => $this->user->username,
                'product_id' => $this->product->id,
                'product_name' => $this->product->name,
            ],
        ];
    }
}

Observer

public function created(Product $product)
{
    $user = Auth::user();
    event(new MessagePushed($user, $product));
}

Questions

  1. How can I return live notifications as soon as event is fired in whole app? currently as my code is placed in add.vue component admins get notify IF they are in same page only :/
  2. How do I get notifications of multiple event? let say I have another event, listener, observer for other page actions I want admin be notified of both product event and other event in whole app.

thanks

Upvotes: 11

Views: 6415

Answers (3)

mafortis
mafortis

Reputation: 7128

Solved

Here is how I done it based on my need and my question (it might be somehow different if your case is different than mine)

In component that activity happens (in my case Add.vue)

Echo.join('userInAddProduct')
.here((users) => {
    console.log('here: ', users);
})
.joining((user) => {
    console.log('joining: ', user);
})
.leaving((user) => {
    console.log('leaving: ', user);
})
.whisper('typing', {data: notf}) // this part sends my data
.notification((notification) => {
    console.log('noitication listener1: ', notification.type);
});

As I needed my notification be visible in all pages to admins I've added my socket emit (i.e. Echo.listenForWhisper) into my navbar component.

mounted() {
  this.notifs();
    window.Echo.join('userInAddProduct')
       // listen to broadcast from Add.vue
      .listenForWhisper('typing', (product) => {
        // add it into my navbar notifications list
        this.notifications.unshift(product);
      });
}

Now each time user adds new product to server admin gets notified.

note To see my other settings for this function to happen please scroll back to my update part and see Event, Listener & Observe files code. (you make those files and all this codes above to your preferred components then you'll have real time notifications).

Upvotes: 1

AndreyBlog
AndreyBlog

Reputation: 9

I recently wrote an article How to use Laravel WebSockets for NuxtJs notifications where I described in sufficient detail including the event setting for Laravel WebSockets. I hope you find it useful.

Upvotes: -1

bornemisza
bornemisza

Reputation: 61

We differentiate channel types like public and private.

Public: Illuminate\Broadcasting\Channel. It works without any authentication, so anonymus users can listen to this broadcast. Like football match scores available for everyone.

Private: Illuminate\Broadcasting\PrivateChannel. It works with an authentication, so only authenticated users can listen to this broadcast. Like admins watching what specific users do or user watching their orders status.

On the otherhand there is another private channel which is Illuminate\Broadcasting\PresenceChannel. Also require authentication. In example would be much like an any chat room. Ex.: only authenticated users can communicate with each other.

So these types will define your method. And you have to follow different paths. I am very suggesting to read the Laravel documentation (https://laravel.com/docs/broadcasting), it will guide you very carefully.

In the routes/channels.php you have to decide or verify if the authenticated user can listen on this particular channel. So you have to return boolean value (true or false). So here, you are not returning any value to the client side.

Every public property in your MessagePushed broadcast event will be serialized and sent to the client side. You have to give every informational data to the MessagePushed constructor and set to property/properties. Now you can handle the received data by like that, ex:

window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001',
    auth: {
      headers: {
        'X-Auth-Token': "<token>"
      }
    }
});

window.Echo.private('userInAddProduct')
   .listen('MessagePushed ', (e) => {
       console.log(e.auction);
    });

I recommend to create custom authentication with token usage. Above I sent token to the server to authenticate user. https://laravel.com/docs/authentication#adding-custom-guards

In your situation I would separate these two mechanism.

  1. check the user for going to the App Product page
  2. notify the admin

For the 2. point would be enough to use the Illuminate\Broadcasting\PrivateChannel. So there is need for websocket on the admin page. The 1. point could be tricky, because it's depends on your requirments. It is enough to notify the admin about user goes to exact App Product page?

  • If yes, then you will trigger this broadcast event in your controller when user goes to that page.

  • If not enough, then you need another websocket on the user page to "poll" your server and send tick broadcast to the admin in every X seconds.

Trigger broadcast event:

broadcast(new App\Events\MessagePushed($user));

My taste of usage is:

Hope, I could help!

Upvotes: 1

Related Questions