How to get a PWA app working with Filament PHP and Laravel Reverb

Progressive Web Applications

Update: Below is an article I wrote out of sheer frustration. Having struggled with web sockets and Laravel in the past I decided to detail my journey trying to get this working again, from scratch. If you read it you will cringe - you will see how an engineer loose it completely because they don't know why something isn't working. Around the point of frustration, right at the end, I decided to Google it once more.

With the greatest of luck I found a course on Laracasts. Not only did I find a course, but when I saw the introduction of Joe Dixon stating he is the creator of Reverb I got some hope.
https://laracasts.com/series/get-real-with-laravel-reverb/episodes/2?page=1

The second episode where he installs it, he made it look completely seamless. So I did what every Laravel engineer should do. I launched a new clean project with nothing, and just installed broadcasting. At first, I couldn't get it work. Next, I enabled HTTPS. Low and behold, all of a sudden, I started seeing the ping pong. Let me etch these configs in stone for the rest of my life.

.env:

REVERB_APP_ID=185422
REVERB_APP_KEY=repot0kbiowd7w1hjfvn
REVERB_APP_SECRET=unjy8x754jqo06lkzbwr
REVERB_HOST="reverb2.test"
REVERB_PORT=8080
REVERB_SCHEME=https

Artisan command:

art reverb:start --debug --hostname="reverb2.test"

Inspect element output:

Network Tab, WS Output. Wait around 30 seconds:

image.png 58.91 KB
Next, I am going to try and get it working in my application again. I will not be posting the frustration again.

Back to my old application, I find that testing this inside Filament doesn't yield good debug results, but simply navigating to the home page gives me more. What I see is a lot of ping and no pong

image.png 65.65 KB
I also see the dreaded message:

WebSocket is closed before the connection is established.

I have new tangible data to work with, but where to start?

I restart NPM and artisan, in that order. And what! It's working???

What a miracle.

image.png 37.03 KB
Next I move on to my application. It still isn't working. Let's ask Povilas.

https://laraveldaily.com/post/configure-laravel-reverb-filament-broadcasting

php artisan vendor:publish --tag=filament-config


Next uncomment echo block:

// ... 

'echo' => [    
   'broadcaster' => 'reverb',    
   'key' => env('VITE_REVERB_APP_KEY'),    
   'cluster' => env('VITE_REVERB_APP_CLUSTER'),    
   'wsHost' => env('VITE_REVERB_HOST'),    
   'wsPort' => env('VITE_REVERB_PORT'),    
   'wssPort' => env('VITE_REVERB_PORT'),    
   'authEndpoint' => '/broadcasting/auth',    
   'disableStats' => true,    
   'encrypted' => true,    
   'forceTLS' => false,

], 

// ...

I notice the many differences between Povilas and the stock one! But low and behold:

image.png 57.86 KB

If you use the stock one, you get at least this error:

Uncaught You must pass your app key when you instantiate Pusher.

And this one:

{"event":"pusher:error","data":{"code":4001,"message":"App key obu0jscolna0lo2srxxm not in this cluster. Did you forget to specify the cluster?"}}

Now that we have some grasp of it working on the local workstation, we have to try and get it working on Forge.

Next, we remember that although Reverb is running at example.com, we actually started off by installing it at subdomain.example.com. Low and behold, we find at subdomain.example.com we have ws.subdomain.example.com, but reverb radio button isn't clicked anymore. This won't do. Let's fix this mess.

1. Remove the button from example.com

We get this:

Remove Reverb Daemon?

Your application will no longer be able to serve WebSocket connections.

You should remove the $FORGE_PHP artisan reverb:restart line from your deployment script.

2. Add a new button on a more stable site than example.com and subdomain.example.com, something that we can use into perpetuity.

First, let's publish the working Filament application from localhost:

npm run build

git add .;git commit -m "implement web sockets";git push origin main;

On the Forge server, remove package-lock.json which is somehow made it ways there.

Press refresh button to make Forge see Reverb.

image.png 66.16 KB
With God's mercy we will now have it working on the server.

No it's not working. First problem is this:

WebSocktet connection to /ws.example.com:80/app/zffcetkeeijwkcvatcnl?protocol=7&client=js&version=7.6.0&flash=false' failed:
| createWebSocket | @ | echo.js?v=3.2.124.0:2

Well, we know, we don't have a ws.example.com record. Let's update our DNS.

We also know our implementation isn't running on port 80, so WTF?

We look at the .env:

REVERB_APP_ID=a
REVERB_APP_KEY=b
REVERB_APP_SECRET=c
REVERB_HOST=ws.example.com
REVERB_PORT=80
REVERB_SCHEME="http"

Mmm interesting. It seems Forge added the correct non-existing DNS record, but port 80. Let's fix that.

Small changes. This might break again beyond control.

Next, we get our favourite message of all time:

WebSocket connection to 'wss://ws.example.com:8080/app/abc?protocol=7&client=js&version=7.6.0&flash=false' failed: WebSocket is closed before the connection is established.

Let's head back to the Forge documentation.

But let's first restart the Daemon. Nope, no difference. WebSocket connection is closed.

We find this bit of interesting:

Forge ensures the hostname provided during Reverb’s installation process is publicly accessible by adding a new server block to your existing site’s Nginx configuration. This server block is contained within a new file and is not available to edit from the Forge UI dashboard.

Examining this line:

cat /etc/nginx/forge-conf/example.com/after/reverb.conf

[::]:80;

What? It listens on port 80?

We change 8080 back to 80.

We cry. After struggling for hours to get this working on localhost with Herd, we are now back to square one on the Forge server.

We continue reading the documentation. Maybe we're back to this HTTP versus HTTPS problem. Let's request a cert.

"If Reverb is installed before a valid certificate is available, you may request a new certificate for Reverb’s configured hostname from your site’s “SSL” tab. Forge will automatically configure secure WebSockets for Reverb as soon as the certificate is activated."

Maybe that should state:

If Reverb is installed before a valid certificate is available, you ***MUST*** request a new certificate for Reverb’s configured hostname from your site’s “SSL” tab. Forge will automatically configure secure WebSockets for Reverb as soon as the certificate is activated."

I'm not sure, but I sure sound desperate.

The nightmare continues. Now I have Let's Encrypt and apparently SSL, but I have nothing else.

According to Forge, I need this:

REVERB_PORT=443
REVERB_SCHEME=https

VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

MIX_REVERB_PORT="${REVERB_PORT}"
MIX_REVERB_SCHEME="${REVERB_SCHEME}"

Huh? VITE_REVERB_PORT? This is news and new.

But neither does it work. 

The WebSocket connection is closed.

I shed another tear. My tenacity is being tested.

We take a break for an hour. Next, we change app from production to local and set debug mode. On Forge. What a waste.

Fuck this. Let's google or use Chat. Well, both Claud and ChatCPT presents old random answers.

Let's uninstall Reverb. WTF! After adding it back again, it just works in the console.

The application is supposed to broadcast, but I don't see that yet but getting the console working is a major boost to confidence.

This doesn't make sense. After hours of struggling first on localhost, and then publishing to the server, broadcasting works in inspect element. But broadcasting doesn't work in Filament. WTF.

I find this random piece of shit configuration variable on localhost, transfer it to Forge, and see if it makes a difference:

VITE_PUSHER_APP_KEY="${REVERB_APP_KEY}"

No, no difference. Let's uncomment it on localhost and see if localhost still works.

No, no difference. Localhost still works. So there you have it, VITE_PUSHER_APP_KEY is a piece of shit.

I decide to stop for now. Next steps is deploy a fresh Filament app on localhost, get it working.

Deploy this same fresh Filament app to Forge, and hook it up to ws.example.com, and see if it's working in inspect element.

Then proceed to test broadcasting from Filament again.

Old  article

The complexity is you have server side and your testing workstation. Just because it's working locally won't mean it's working on the server.

Server Side:

First of all, you have to have Reverb running. Here is an icon if it's running:

image.png 4 KB
The problem is where is it running on Forge? Next, you explore here:

Server => Daemons

There you may find a line like this:

php8.3 artisan reverb:start --no-interaction --port=8080

Hurah! You now know it's running on port 8080. This is a good thing because you will set up your workstation to also use 8080.

But what domain? What did the instructions say again? Here are those pesky complex instructions again:

https://forge.laravel.com/docs/sites/applications#laravel-reverb

Here are some catches: You need a certificate.

Next caveat, 100s of small ENV settings, including this from the docs.

After activating SSL on a Reverb-enabled site, you should ensure the following environment variables are properly defined before redeploying your site:

REVERB_PORT=443
REVERB_SCHEME=https

VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

MIX_REVERB_PORT="${REVERB_PORT}"
MIX_REVERB_SCHEME="${REVERB_SCHEME}"

At this point of the manual, if you have already deployed revert two months ago, you'll be at a loss because the config is....gone.

Let's ignore Forge's incomplete information and move on to Filament.

What you need is broadcast notifications, see here:

https://filamentphp.com/docs/3.x/notifications/broadcast-notifications#overview

From their manual:

"We have a native integration with Laravel Echo. Make sure Echo is installed, as well as a server-side websockets integration like Pusher."

So you need SERVER SIDE websocks like Reverb.

Let's delve into the third manual now, Laravel.

https://laravel.com/docs/11.x/broadcasting#server-side-installation

By default, broadcasting is not enabled in new Laravel applications. You may enable broadcasting using the install:broadcasting Artisan command:

php artisan install:broadcasting

This looks promising. But will it work? Probably not. Too many settings. Let's tackle them one by one.

First let's cover all the countless files you have now that's new:

  • app.php modifications
  • bootstrap.js modifications
  • broadcasting.php
  • channels.php
  • echo.js
  • reverb.php

Now you can see why installing this is such a fucking nightmare. Get one setting wrong, and you'll end up with obscure messages and troubleshooting for hours, days, or even weeks. And good luck replicating the install.

But since we're following the manual, and we have some useful information about "what to change" from Forge, let's look at that.

The manual continues:

Once the package is installed, you may run Reverb's installation command to publish the configuration, add Reverb's required environment variables, and enable event broadcasting in your application:

php artisan reverb:install

That farms you off to another part of the manual.

But will it work? Probably not. You haven't even done a single setting yet.

Now let's go to the next set of documentation, Reverb.

https://laravel.com/docs/11.x/reverb

In order to establish a connection to Reverb, a set of Reverb "application" credentials must be exchanged between the client and server. These credentials are configured on the server and are used to verify the request from the client. You may define these credentials using the following environment variables:

REVERB_APP_ID=my-app-id
REVERB_APP_KEY=my-app-key
REVERB_APP_SECRET=my-app-secret

This looks familiar. We're using Herd and Herd seems to give us some arbitrary settings for this. Let's check out Herd.

Herd => Services => Reverb

1.x Port: 8080

Environment Variables.

REVERB_APP_ID=1001
REVERB_APP_KEY=laravel-herd
REVERB_APP_SECRET=secret
REVERB_HOST="reverb.herd.test"
REVERB_PORT=443
REVERB_SCHEME=https

WTF? Reverb Host? Port 443 instead of 8080? Will HTTPS even work with all the nightmare's and sockets and SSL??

Let's give it a shot.

Let's first look at localhost .env:

REVERB_APP_ID=400035
REVERB_APP_KEY=obu0jscolna0lo2srxxm
REVERB_APP_SECRET=9rpi55btwyc0v55hgh0q
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=http

WTF? Now we're on port 8080 here? Where do the fucking random credentials come form?

In Linux you get dependency hell - in Web Sockets you get configuration hell.

Do we now copy our local settings to Herd? Do we copy from Herd to Local?? What about the 443 and 8080 "problem"?

Gosh, we haven't even started with the server yet or run a single test. WTF, let's move on.

Did anyone mention Vite yet? There's another bomb coming soon, and guess what, Vite will be your enemy.

Let's fire up npm  run dev.

And pray.

Let's fire up Reverb

php artisan reverb:start

   RuntimeException

  Failed to listen on "tcp://0.0.0.0:8080": Address already in use (EADDRINUSE)

  at vendor/react/socket/src/TcpServer.php:188
    184▕                 // @link https://3v4l.org/3qOBl
    185▕                 $errno = SocketServer::errno($errstr);
    186▕             }
    187▕
  ➜ 188▕             throw new \RuntimeException(
    189▕                 'Failed to listen on "' . $uri . '": ' . $errstr . SocketServer::errconst($errno),
    190▕                 $errno
    191▕             );
    192▕         }

      +16 vendor frames

  17  artisan:13
      Illuminate\Foundation\Application::handleCommand(Object(Symfony\Component\Console\Input\ArgvInput))

Huraah! First error.

Is it Herd? Is it something else? Is it a plane?  Is it a bird?

Did anyone mention App URL yet? 

So we stop Herd. We find that now Port 8080 can be start with artisan run.

So WTF do we need Herd, or not?

 php artisan reverb:start

   INFO  Starting server on 0.0.0.0:8080 (localhost).

Next, it seems we have a whole SSL study guide: https://laravel.com/docs/11.x/reverb#ssl

All spare you the gory introductory paragraph because it makes using Herd or not clear as  mud.

Next we find a better command. Debug. You will need that.

php artisan reverb:start --debug --host="0.0.0.0" --port=8080 --hostname="backupdashboard.test"

Does it work yet? Can we test? Let's move on to that:

Now you have to find another manual. The Broadcasting manual at Laravel is a long long joke doc, so good luck.

Let's see if Filament has something usable.

Let's give this URL a whirl:

Route::get('/broadcast', function () {
    $recipient = auth()->user();

    Filament\Notifications\Notification::make()
        ->title('Saved successfully')
        ->broadcast($recipient);
});

cURL error 1: Received HTTP/0.9 when not allowed (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for http://localhost:8080/apps/400035/events?auth_key=obu0jscolna0lo2srxxm&auth_timestamp=1732725986&auth_version=1.0&body_md5=c6191dfb464338357300bc48cbbf2c83&auth_signature=724a378efa250bebf4e12f3b5bb614848aadab36c8876ec29bfc7eca0bdeb047

Hale Mary Bloody Mary. WTF? Localhost? 8080? Is this it then?

And WTF is 8080/apps/400035/events???

We go back to our .env and see some suspects:

REVERB_APP_ID=400035
REVERB_APP_KEY=obu0jscolna0lo2srxxm
REVERB_APP_SECRET=9rpi55btwyc0v55hgh0q
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=http

Maybe let's start with REVERB_HOST.

Hail Mary! Progress

cURL error 1: Received HTTP/0.9 when not allowed (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for http://backupdashboad.test:8080/apps/400035/events?auth_key=obu0jscolna0lo2srxxm&auth_timestamp=1732726154&auth_version=1.0&body_md5=4b620f7c7a1372536ca112de4b9cf887&auth_signature=9e1a619c67f702ad9f4718017a74c1435e44c0ceec4ff1489449717d1811e70d

Next we trry REVERB_SCHEME to https

Hail mary! More progress, and lovely, here we have the dreaded SSL problem:

cURL error 60: SSL: no alternative certificate subject name matches target hostname 'backupdashboad.test' (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for https://backupdashboad.test:8080/apps/400035/events?auth_key=obu0jscolna0lo2srxxm&auth_timestamp=1732726178&auth_version=1.0&body_md5=db8299c6ca19dfab028f4eb085768c00&auth_signature=19cefa1ac4d53abc81dfc30067428cea35515554abeb46b3c7e634022fad37da

WTF. Do we really have to specialise in SSL to get this shit working?

At this point we're fucking stuck.

I don't even know where to start next.

Maybe let's head back to Herd.

First stop artisan

Then start Herd.

Next, a long delay  (10 seconds), then this shiner:

Pusher error: cURL error 28: Connection timed out after 10002 milliseconds (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for https://reverb.herd.test:8080/apps/400035/events?auth_key=obu0jscolna0lo2srxxm&auth_timestamp=1732726329&auth_version=1.0&body_md5=3bd85edde17162c49b554223b1583101&auth_signature=82b15b06c95ac41c9b8672bdc5f2d365ab603166c19e66c84fee83efd5c3260b.

Ok, let's use the Herd Username instead.

Praise the lord. Maybe fucking Pusher is fucking fucked. 

But where do we look? Where do we even start?

We find this  in broadcasting:

'default' => env('BROADCAST_CONNECTION', 'null'),

But we find this in .env

BROADCAST_CONNECTION=reverb

So what now? Where next? Why is the fucking piece of shit timing out?

Oh wait? Herd has this:

REVERB_APP_ID=1001
REVERB_APP_KEY=laravel-herd
REVERB_APP_SECRET=secret
REVERB_HOST="reverb.herd.test"
REVERB_PORT=443
REVERB_SCHEME=https

WTF? Port = 443???

Finally the page loads, but it's blank. We need a better test.

So Herd is running on port 8080. But reverb needs 443. Or Herd needs 443. Or Pusher needs 443. WTF actually needs 443 seems like the little mystery after this long and arduous process.

Fuck HTTPS for localhost. Let's see if we can make this shit working with HTTP on localhost.

Next we stop Herd and start artisan.

Next we feel like we're moving backwards and stop working. This is a fucking nightmare.