Chapter 14 of 15

Laravel's notifications on the desktop

When a colleague mentions Freddy in a note, the Laravel app already sends him a notification. Now it also reaches his desktop: in the OS when the app is in the background, as a toast when it is in front, with a count on the icon, and read once wherever he reads it.

Turn it on

App::new().backend(
    Backend::new("https://notes.example.com")
        .broadcasting(Broadcasting::pusher(key))
        .notifications(),   // App.Models.User.{id}, and /api/notifications
)

It needs chapter 12's broadcasting, and the app will not start without it. On the Laravel side, Freddy's notification goes through the database and broadcast channels and returns a title, a body and a url, by convention:

class MentionedInNote extends Notification
{
    public function via(object $notifiable): array
    {
        return ['database', 'broadcast'];
    }

    public function toArray(object $notifiable): array
    {
        return ['title' => 'You were mentioned', 'body' => 'Ada mentioned you in "Train ideas".', 'url' => '/team-notes/12'];
    }
}

MentionedInNote is Freddy's; the guide's example is an InvoicePaid with the same shape.

The life of one notification

  • Heard on the signed-in user's private channel, Laravel's App.Models.User.{id}, authorized by the backend like any private channel. Signing in as someone else follows theirs.
  • Shown: with no window of the app in front, the OS shows it, which on macOS wants chapter 8's bundled app; with one in front, it is for the app to toast. .notification_previews(false) shows only "New notification", for a locked screen.
  • Clicked: the app comes forward, the notification is read, and its url opens when it is a path in the app, /team-notes/12. A full URL is never opened by a click; the app's own listener can choose to open one.
  • Counted: the unread count, the server's, is on the dock on macOS, the launcher on Linux, or the taskbar button on Windows, and in the tray's tooltip. Signing out clears it.
  • Listed and read: Notifications::refresh() reads GET /api/notifications, Laravel's paginator with unread beside it. mark_read and mark_all_read count down at once and post to the server, through chapter 13's outbox when offline. The list is read again when a window gets focus, and when the web broadcasts NotificationsRead, so reading it in the browser reads it on the desktop too.
  • Missed while offline or closed: when the connection is back, or at the start, what came meanwhile is shown, one as itself, more as "4 new notifications" that opens /notifications. A first sign-in on a machine shows nothing, so a new laptop does not greet Freddy with a year of mentions.

In the frontend

<script>
  import { notifications, onNotification, toast } from "@elyra/runtime";
  onNotification((e) => e.shown === "toast" && toast(e.presented.title));
</script>

<a href="#/notifications">Notifications {$notifications.unread}</a>
{#each $notifications.items as n (n.id)}
  <button onclick={() => notifications.markRead(n.id)}>{n.data.title}</button>
{/each}

The notifications store, { unread, items, hasMore }, is read from the server when first subscribed and then kept up to date. It all goes through the auth capability, and the page may read the list and mark it read, nothing else.

rata make:notifications

Freddy does not write that page by hand either. rata make:notifications writes the page at #/notifications with Mark all read, More and the offline banner, a nav item with the unread count, and the toasts and click handling, set up once for the whole app. It also prints the Laravel side: the notifications table, a NotificationController with index, read and readAll, its routes under auth:sanctum, and a NotificationsRead event broadcast ->toOthers() when the web reads them.

In Rust, Notifier shows notifications from the container, and the frontend's notify(..) from chapter 8 goes through it, so a test can use Notifier::fake() to record them instead of showing them. The guide is the notifications section of A Laravel backend, and the design is RFC 0008.

What you learned

  • .notifications(), on top of broadcasting, brings what $user->notify() sends through database and broadcast to the desktop.
  • Shown by the OS in the background and toasted in front, a click opens a path in the app and never a full URL by itself.
  • The unread count is the server's, on the icon and in the tray, and reading in one place reads it everywhere.
  • What came while offline or closed is caught up, and rata make:notifications writes the page and prints the Laravel side.
Next: in Chapter 15 Freddy tests the app and ships it.