Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

theUnforgiven's avatar

VueJS chat, broadcasting doesn't seem to be working....

Hi all,

Looking for an extra pair of eyes here... I can't seem to figure out why the message sent from one user account doesn't show up on the other other user account of which they are having a conversation with.

Here's what I have, I've put everything in one code block and comments on, also the typing indicator doesn't work either, but I can see the client events and API message(s) been sent when I login to my Pusher account.

// EVENT

<?php

namespace App\Events;

use App\Models\Conversation;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

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

    public $conversation;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct(Conversation $conversation)
    {
        $this->conversation = $conversation;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return Channel|array
     */
    public function broadcastOn()
    {
        return new Channel('groups.'.$this->conversation->group->id);
    }

    public function broadcastAs()
    {
      return 'NewMessage';
    }

    public function broadcastWith()
    {
        return [
            'message' => $this->conversation->message,
            'user' => [
                'name' => $this->conversation->user->name,
            ],
            'date' => $this->conversation->created_at
        ];
    }
}


// Controller for storing message
try {
    $conversation = Conversation::create([
        'message' => request('message'),
        'group_id' => request('group_id'),
        'user_id' => auth()->user()->id,
    ]);
    $date_sent = now();

    // $to = Carbon::createFromFormat('d/m/Y, H:s:i', $date_sent);
    $from = Carbon::createFromFormat('Y-m-d H:s:i', $conversation->created_at);

    $diff_in_minutes = $date_sent->diffInMinutes($from);

    $conversation->response_time = $diff_in_minutes;
    $conversation->update();
    $conversation->load('user');

    broadcast(new NewMessage($conversation))->toOthers(); //Should broadcast to others, but doesn't

    return response()->json($conversation);

} catch(\Exception $e) {
    \Log::info($e->getMessage() . ' on line ' . $e->getLine() . ' within ' . $e->getFile());

}

// Vue for the chat page

<template>
<div>
    <div class="card body">
    <div class="card mb-3 d-flex justify-content-center">
        <div class="accordion" id="accordionExample">
            <a class="btn btn-link "
                data-toggle="collapse"
                :href="`#collapse-` + group.id"
                @click="read(group)"
            >
                <span class="row">
                    <span class="col-md-12">
                        <span class="mr-2 badge badge-danger badge-pill font-weight-bold" style="font-size: 13px;" v-if="unReadCount">{{ unReadCount }}</span>

                        <div v-if="group.site">
                            <h6>Site Name: <span class="text-muted">{{ group.site | capitalize }}</span> - Block Name: <span class="text-muted">{{ group.block | capitalize }}</span> - Unit No: <span class="text-muted">{{ group.unit }}</span> </h6>
                        </div>
                        <small>
                            <strong>Conversation party:</strong>
                            <span v-for="(user, index) in group.users" :key="index">
                               ({{ user.account_type }}) {{ user.name }}
                            </span>
                        </small>
                        <hr />
                        <small><strong>Subject:</strong> {{ group.subject }} - <strong>Message ID:</strong> {{ group.unique_id }}</small>
                        <br />
                        <small v-if="group.property">
                            <strong>Property:</strong> {{ group.property.name_number }}, {{ group.property.town }}, {{ group.property.postcode }}
                        </small>
                        <span v-show="group.urgency">
                            <strong>Urgency</strong> <span class="badge badge-info">{{ group.urgency | capitalize }}</span>
                        </span>
                    </span>
                    <span class="col-md-12">
                        <a data-toggle="collapse" :href="`#collapse-` + group.id" aria-expanded="false" aria-controls="collapseExample">
                            <i class="fas fa-chevron-down"></i>
                            <i class="fas fa-chevron-up"></i>
                        </a>
                    </span>
                </span>
            </a>

            <div :id="`collapse-${group.id}`" class="collapse" aria-labelledby="headingOne" data-parent="#accordionExample" v-cloak>
                <div class="card-body col-md-12">
                    <div class="panel-body chat-panel border border-light border-bottom-0" :class="`chat-${group.id}`">
                        <ul class="list-unstyled" v-cloak>
                            <li class="media" v-for="(conversation,index) in messages" :key="index">
                                <i class="far fa-user mr-3 img-thumbnail"></i>
                                <div class="media-body">
                                    <h5 class="mt-0 mb-1">
                                        <span v-if="conversation.user.id == currentUser.id">Me</span>
                                         <span v-else>{{ conversation.user['name'] }}</span>
                                    </h5>
                                    <p class="font-md text-secondary">
                                        {{ conversation.message }}<br />
                                        <small class="text-muted pt-1">at {{ human(conversation.updated_at) }}</small>
                                    </p>
                                </div>
                            </li>
                        </ul>
                    </div>
                    <div class="panel-footer">
                        <div class="input-group">
                            <input
                                type="text"
                                class="form-control input-sm chat-input"
                                placeholder="Type your message here..."
                                v-model="message"
                                @keydown="isTyping" 
                                
                            >
                            <span class="input-group-btn">
                                <button class="btn btn-success"
                                        style="border-radius: 0px 10px 10px 0px!important;"
                                        @click="sendMessage"
                                >
                                <i class="far fa-paper-plane"></i> Send
                                </button>
                            </span>
                        </div>
                        <span v-show="typing" class="mt-1 mb-3 typing">
                            {{ typing }}
                        </span>

                        <br /><br />
                        <small class="alert alert-info">If the conversation is finished, you can can archive by clicking <a href="#" @click.prevent="archive(group.id)">here</a></small>
                    </div>
                </div>
            </div>
        </div>
    </div>
    </div>
</div>
</template>

<script>
import moment from 'moment';

export default {

    props: ['group_data', 'user'],

    data() {
        return {
            message: '',
            messages: [],
            typing: '',
            group: [],
            unReadCount: '',
            currentUser: [],
            groupId: '',
        }
    },

    mounted() {
        this.group = this.group_data;
        this.currentUser = this.user;
        this.messages = this.group_data.messages;

        this.listenForNewMessage();
        this.unReadCount = this.filterUnreadCount();
    },

    created() {
        let _this = this;

        Echo.private('groups.' + this.group.id)
            .listenForWhisper('typing', (e) => {
            this.typing = this.currentUser.name + ' is typing...';

            // remove is typing indicator after 0.9s
            setTimeout(function() {
                _this.typing = false
            }, 900);
        });
    },

    methods: {
        human : function (date) {
            return moment(date).format('Do MMMM YYYY h:mm:ss');
        },

        filterUnreadCount() {
                return this.group.messages.filter((message) => {
                    return message.read_at === null && Laravel.user.id != message.user_id;
                }).length;
            return 0;

        },

        read(group) {
            axios.post('/conversations/markAsRead/' + group.id)
                .then((response) => {
                    this.unReadCount = 0;
                });
            this.groupId = group.id;
        },  

        isTyping() {
            let channel = Echo.private('groups.' + this.group.id);

            setTimeout(function() {
                channel.whisper('typing', {
                user: this.currentUser.name,
                    typing: true
                });
            }, 300);
        },

        sendMessage() {
            if(this.message == '') {
                return;
            }
            axios.post('/conversations', {
                message: this.message,
                group_id: this.group.id,
                // date: new Date().toLocaleString()
            }).then((response) => {
               this.messages.unshift(response.data);
               this.message = '';
               this.typing = '';
            }).catch((error) => {
                swal({
                    title: 'Sorry, there is a problem!',
                    text: 'There are errors on the page, please check and try again \n',
                    icon: 'error',
                    button: 'Continue',
                });
                this.errors = error.response.data.errors;
            });
        },

        listenForNewMessage() {    
                Echo.private('groups.' + this.group.id)
                .listen('NewMessage', (e) => {
                    console.log(e);
                    this.messages.unshift({
                        message: e.message,
                        user: e.user.name
                    });
                });
        },

        archive(group_id) {
            axios.post('/conversations/archive/' + this.group.id)
            .then((response) => {
                swal({
                    title: 'Success!',
                    text: 'Chat now archived.',
                    icon: 'success',
                    buttons: false,
                });
                setTimeout(() => {
                    location.reload();
                 }, 3000);
            });
        }
    }
};
</script>

0 likes
43 replies
mkshingrakhiya's avatar

You need to prepend '.' to the listener's name if you're using broadcastAs.

Echo.private('groups.' + this.group.id)
    .listen('.NewMessage', (e) => {
        console.log(e);
    });

Did you enable client events in the Pusher settings?

One more thing I noticed is that you're listening on the private channel while you're broadcasting on the public one. Make sure to authorize if you're using private channels.

theUnforgiven's avatar

Yes all good in Pusher and yes I forgot to change it back to Private

BryanK's avatar

In Vue, created() is fired before mounted(), so you need to set your vars in data or above your Echo listener in created().

I would do it like this so that the data is set before you try to use it:

data() {
        return {
            message: '',
            messages: this.group_data.messages,
            typing: '',
            group: this.group_data,
            unReadCount: '',
            currentUser: this.user,
            groupId: this.group_data.id,
        }
    },
theUnforgiven's avatar

I just changed this as you commented, but still not posting to other user

mkshingrakhiya's avatar

Did you authorize the channel in channels.php? In order to listen for typing event, you need to return data from the authorization instead of true.

theUnforgiven's avatar

In api.php I have

Route::post('broadcasting/auth', function ($data) {
    return true;
});

Then channels.php I have:

Broadcast::channel('users.{id}', function ($user, $id) {
    return (int) $user->id === (int) $id;
});
Broadcast::channel('groups.{group}', function ($user, Group $group) {
    return $group->hasUser($user->id);
});
mkshingrakhiya's avatar

Whispering won't work if you're returning boolean values. You need to return some data.

theUnforgiven's avatar

I need to work out what's in data as dump and dd isn't doing anything nor am I seeing it within the network tab

mkshingrakhiya's avatar

When the other user joins the group, do you see any event on Pusher console?

theUnforgiven's avatar

There isn't a join function, this is all setup from creating a group where both users are added.

theUnforgiven's avatar

Looking at the $data on the broadcasting/auth this is all I see

{
"socket_id": "128551.16249636",
"channel_name": "private-groups.2"
}
BryanK's avatar

Can you post your API message from pusher?

BryanK's avatar

Can you comment out the Echo listenForWhisper section in created() and then move this.listenForNewMessage() from mounted() into created()?

See if that gets it initialized.

theUnforgiven's avatar

Just doesn't make any sense as to why the message isn't been broadcast to the other user

theUnforgiven's avatar
public function boot()
    {
       Broadcast::routes(['middleware' => ['auth:api']]);

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

Does the console.log you have for the event listener ever fire?

theUnforgiven's avatar

@bryank No, all I see is the actual axios post of the new message and within Pusher the API messages as I previously posted.

theUnforgiven's avatar

I do see, this

that;'s the only error

Which looks like it's user: this.currentUser.name, within the whisper function

mkshingrakhiya's avatar

I think this is where your issue is. I also once stuck here. If I remember correctly, you will need to send the authentication token when it tries to authorize the channel. If you need to test if this has the problem is to try to route the request in one of the controllers which implement web middleware and remove the parameter from Broadcast::routes();.

theUnforgiven's avatar

@mkshingrakhiya Moving to just Broadcast::routes(); seems to have worked and shows the message on the other user now in realtime, however the name of the user that sent it, isn't been displayed.

theUnforgiven's avatar

In pusher it shows as:

{
  "message": "hi there",
  "user": {
    "name": "jhg wertyu"
  },
  "date": "2020-06-02T14:55:40.000000Z"
}
BryanK's avatar

Actually, looking again at your API message, I don't see the private word on the channel.

It should say Channel: private-groups.2 if private.

Try Echo.channel('groups.' + this.group.id)

mkshingrakhiya's avatar
Echo.private('groups.' + this.group.id)
    .listen('.NewMessage', (e) => {
        console.log(e); // What do you see here
    });
Next

Please or to participate in this conversation.