Adding Whispers to Your Laravel App

Darrin Deal -

Last week I released The Art of Laravel’s first full length tutorial. You can walk through that here. In that tutorial we created a chat application using the new Laravel Reverb WebSocket package. Partnering that with the broadcasting system in Laravel we can create real time features.

In this tutorial we are going to cover another feature of broadcasting. This feature is called whispers. We will be creating a simple Alpine component that shares with the other users that someone is typing. Let’s dive in.

Getting Started

To get started please complete the Getting Started with Laravel Reverb tutorial as this content will build on top of the project that was completed there. Once you have that project up and running you can continue on with adding this Alpine component.

What are whispers?

Before we get to far into the code, lets answer what is a whisper? A Whisper is a broadcast message that is sent from the client side of your application. In the previous tutorial we created a chat application where the event was broadcast from the server side.

There are times where we may want to broadcast a message to other users on our channel but a full request to the server is not needed. Whispers allow us to send these messages directly to our WebSocket server, by passing a full HTTP request. We can use this to build our component.

Someone is Typing Component

In you app.js file in the resources/js directory add the following code.

// 1
document.addEventListener('alpine:init', () => {

		// 2
    Alpine.data('isTyping', (initalMessageGroup = 0, initalName = "", initalMessage = "") => ({
        messageGroup: initalMessageGroup,
        name: initalName,
        message: initalMessage,
        showTypingMessage: false,
        typingMessage: "",
        isTyping: false,
        init(){
        
		        // 3
            this.$watch('message', (value) => {
                if ([0, 1].includes(value.length) && this.isTyping !== value.length > 0){
                    Echo.private(`message-group.${this.messageGroup}`)
                        .whisper('typing', {
                            name: this.name,
                            isTyping: value.length > 0
                        });
                        
                    this.isTyping = value.length > 0
                }
            })

						#4
            Echo.private(`message-group.${this.messageGroup}`)
                .listenForWhisper('typing', (e) => {
                    this.showTypingMessage = e.isTyping
                    this.typingMessage = `${e.name} is typing...`
                });
        },

    }))
})

  1. We are waiting for Alpine to initialize before creating our component.
  2. Once init is complete we can create out component. The name is isTyping and we pass in some default properties needed for our component.
  3. Here a watch is setup on the message property. Anytime the message property is updated we are going to see if conditions are right to send. We are only going to send a whisper if the length of the message property is 0 or 1. This acts a a toggle for our message.
  4. We also want to setup listener to receive the typing message on our message group’s channel.

Understanding Sending and Receiving Whispers

So looking at this code we have quite a bit of Alpine and a small portion of the whisper code. Let’s pull the whisper code out and see what’s going on.

There are two parts: sending whispers and receiving whispers. The following is how we are sending a whisper.

Echo.private(`message-group.${this.messageGroup}`)
    .whisper('typing', {
        name: this.name,
        isTyping: value.length > 0
    });

If you remember back to the Getting Stated with Reverb tutorial we used a private channel to broadcast our messages on. This is to make sure only users in the group can chat back and forth. So on our private channel we call the whisper method. This method accepts two parameters.

  1. A name of our whisper event. We will use this later on.
  2. Properties for our specific event.

This sends the message but now we need to listen for the message. The following code is the listener.

 Echo.private(`message-group.${this.messageGroup}`)
     .listenForWhisper('typing', (e) => {
         this.showTypingMessage = e.isTyping
         this.typingMessage = `${e.name} is typing...`
     });

On the same private channel we use the listenForWhisper method that also accepts two parameters.

  1. We pass in the message we are listening for on the channel. We use the same name from where we sent our message.
  2. Finally, we pass in a callback that has a parameter of the event. This is the data that we sent across.

Using Our Component

All that is left to do is wire up our component. To do this we will go to this file resources/views/livewire/message-group-page.blade.php. This file is the template for sending our messages back and forth. You will find the following section of code towards the bottom of the file.

<div class="w-full fixed bottom-0">
    <div class="max-w-2xl mx-auto p-4 bg-white mb-2 rounded-full shadow">
        <div class="flex justify-between items-center">
            <div class="flex w-full">
                <x-text-input wire:model.blur="message" type="text" id="message-input" class="w-full border-0 !shadow-none focus:ring-none" placeholder="Your message here..." />
            </div>
            <div class=" flex justify-end">
                <div wire:click="send" class="pl-4">
                    <svg xmlns="<http://www.w3.org/2000/svg>" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M17.25 8.25 21 12m0 0-3.75 3.75M21 12H3" />
                    </svg>
                </div>
            </div>
        </div>
    </div>
</div>

This is our message box. To add our component to this section of code change the top div to be this.

<div x-data="isTyping({{ $messageGroup->id}}, '{{ auth()->user()->name }}')" class="w-full fixed bottom-0">

This says that we want to use our component in this block of HTML. When our page renders it will look something like this.

<div x-data="isTyping(123, 'Test User')" class="w-full fixed bottom-0">

The data we pass in will be used to build our channel name and the name that will be sent in the whisper.

Now we need to connect our message property to the text box. This is because we want to listen for the length of the what is typed. Replace the text input with the following.

<x-text-input x-model="message" wire:model.blur="message" type="text" id="message-input" class="w-full border-0 !shadow-none focus:ring-none" placeholder="Your message here..." />

Finally we can add our typing message. Just inside the div that we put the x-data attribute on add the following.

<div x-show="showTypingMessage" class="max-w-2xl mx-auto px-8 mb-2">
	 <p x-text="typingMessage"></p>
</div>

When someone is typing we will show this div and pass the typing message to a p tag.

That should be it! if you have two windows opened and logged into two separate users on the user that isn’t typing you should see the following!

someone-is-typing.png 29.88 KB

Where to Go From Here?

In this article we built upon the principles that we previously learned in the Getting Started with Laravel Reverb tutorial. We added a someone is typing message to our chat application using Alpine and Whispers in Laravel Echo.

Be sure to follow me on X to stay up with the latest tutorials and to let me know what you would like to learn about next!


Other articles you might enjoy...