Realtime Notifications with Laravel API and AngularJS

NIDABRAHIM Youssef
Jun 25 · 8 min read

Il est de plus en plus courant, dans une application web moderne, d’afficher les informations en temps réel à l’utilisateur sous forme de notifications. On a recours pour cela à des WebSockets qui vont permettre la communication directe entre le serveur et le client.

Le principal problème est de transmettre un événement émis depuis Laravel vers un serveur gérant les WebSockets. Pour cela Laravel dispose d’un système de “Broadcast” qui va permettre de transmettre les évènements au serveur. Deux drivers sont proposés par Laravel :

  • Pusher :

Un service tiers, préconfiguré avec Laravel, très pratique pour effectuer du Publish / Subscribe. Étant hébergé via un serveur tiers, il nous évite de devoir maintenir notre propre serveur WebSocket. Mais il est payant au-delà d’un certain nombre de connexion, nombre de messages par jours, etc…

  • Redis :

Redis (REmote DIctionary Server) est un serveur de base de données clef-valeur mémoire capable de gérer un système de Publish / Subscribe. Il fait partie de la mouvance NoSQL et vise à fournir les performances les plus élevées possibles. L’utilisation est totalement gratuite, mais étant hébergé localement, il faut en assurer soit-même la maintenance.

REDIS recevra les messages reçus par notre application et les stockera en mémoire puis les redistribue à notre serveur de web socket.

Comme Socket.IO et Laravel sont incompatibles, Redis jouera le rôle d’intermédiaire entre le client et le serveur grâce à son mécanisme Publish-Subscribe, mécanisme de publication de messages et d’abonnement à ces derniers dans lequel les diffuseurs ne destinent pas, a priori, les messages à des destinataires. À la place, un canal est associé aux messages émis sans savoir s’il y a des destinataires. De la même manière, les destinataires s’abonnent aux canaux les intéressant et ne reçoivent que les messages correspondants, sans savoir s’il y a des diffuseurs.

Nous avons utilisé Redis pour le fonctionnement de notre site afin de limiter les coûts globaux de l’application. Il faudra cependant rajouter une dépendance à notre backend afin de communiquer avec cette nouvelle base :
composer require predis/predis

Les serveur est lancé avec la commande : redis-server

Il faut aussi modifier le fichier de configuration config/broadcasting.php et le fichier .env afin de renseigner le type de driver à utiliser.

Pour le débogage, nous surveillerons les données diffusées dans Redis en utilisant Redis-cli, qui peut être lancé avec cette commande :
redis-cli monitor

Broadcasting events et Redis

Pour effectuer le “broadcast” d’un évènement sur Laravel, il suffit de créer une classe d’événement avec la commande artisan suivante :

php artisan make:event EventClassName

et d’implémenter l’interface ShouldBroadcast. Si Laravel constate qu’une classe d’événements implémente ShouldBroadcast, il comprend qu’il s’agit d’un événement de diffusion.

L’interface ne déclare qu’une seule méthode broadcastOn qui doit renvoyer un tableau de chaînes de caractères qui sont les channels sur lesquelles cet événement devra-t être diffusé.

Lorsqu’on émette un évènement, il sera alors sérialisé et publié sur Redis.

Laravel fournit une interface pratique pour les commandes de Publish/Subscribe Redis. Ces commandes Redis nous permettent d’écouter des messages sur un canal donné. Nous pouvons publier des messages sur le canal à partir d’une autre application ou même à l’aide d’un autre langage de programmation. Dans notre cas, nous allons utiliser node.js pour émettre les messages vers le WebSocket server.

Laravel publie un message sur le canal Redis pub/Sub. Le script Node.js s’abonne au canal Redis et reçoit des messages puis il émet à travers socket.io le message au bon socketId.

WebSocket

Le protocol de WebSocket passe par deux grandes étapes :

  • Chaque client envoie une requête séparée pour initialiser la communication WebSocket.

Maintenant qu’on est capable d’émettre un évènement, il faut être capable de le renvoyer en temps réel au client. Pour cela on doit créer un serveur de websocket qui va s’abonner à Redis.

Laravel-echo-server est un serveur NodeJS spécifiquement conçu pour gérer le système d’évènement mis en place par Laravel.

On voulait, au début, utiliser Laravel echo, mais malheureusement, nous n’avons pas pu l’utiliser et cela en raison de certaines limitations de notre front end Angular.js. En effet Laravel-echo doit être compilé en utilisant ES6 et notre script frontend est écrit en utilisant ES5. Nous avons opté, dans notre cas, à créer notre propre serveur WebSocket avec socket.io.

Socket.io côté client

Tout d’abord, il faut charger la librairie socket.io depuis notre serveur websockets. Avant de pouvoir se connecter au socket.io server, on doit s’authentifier en utilisant JWT.

Une authentification échouée entraîne une erreur de connexion et le client ne peut alors recevoir aucun message transmis par Socket.io server.

Chaque client va s’abonner à son propre channel et va commencer par la suite à recevoir les messages.

Pour des questions de performance et de réutilisabilité, nous avons créé une factory qui met à notre disposition les fonctions nécessaires pour communiquer via socket.io. On peut utiliser cette factory n’importe où dans notre application AngularJs. Après avoir créé la factory, les contrôleurs qui l’utiliseront devront avoir la dépendance injectée.

Pour qu’une même notification ne soit pas affichée plusieurs fois, il faut appeler les listeners des événements dans le contrôleur après avoir être authentifié, cela évitera de créer d’autres listeners à chaque fois qu’on revisite notre contrôleur.

Socket.io côté serveur

Socket.io server s’abonne aux événements entrants avec Redis, qui publie des événements sur des canaux. Le serveur s’abonnera à ces canaux et diffusera ces messages via socket.io.

Le Socket.io server étant dans le backend, nous utilisons la même configuration entre l’application Laravel et le Socket.io server. Nous partageons donc le fichier .env pour la configuration.

Nous avons utilisé les paquets Node.js suivants :

  • Express

Pour installer toutes ces dépendances:

npm install express socket.io socketio-jwt dotenv ioredis — save

Authentification avec JWT

JWT offre une méthode uniforme pour simplifier le processus d’authentification. Cela nous permet de générer des jetons dans Laravel, de les présenter au navigateur via une API et de laisser le navigateur utiliser ce jeton pour se connecter à Node.js / socket.io.

Voici le flux que nous avons réalisé :

  • Le navigateur client demande un jeton à Laravel une fois connecté, Laravel générera un jeton et le renverra.

Pour rendre les choses plus sûres, il faut ajouter la ligne suivante à votre fichier ‘.env’ de Laravel et assurer que la clé est dure et unique. C’est la clé partagée que Laravel et Node.js utiliseront pour générer et valider les jetons JWT, par exemple :

JWT_SECRET=B1Bbr8EnAdJASz3l7Wl0U69f4UHsdtDX

Pour le côté NodeJs, nous allons recevoir le token et nous allons avoir besoin de le vérifier, pour cela nous avons utilisé la librairie socketio-jwt.

Cette librairie possède une méthode authorize qui permet de vérifier le token et qui renvoie le contenu du payload décodé qui contient des informations sur l’utilisateur connecté qui vont nous servir par la suite pour l’autorisation.

Autorisation

A chaque fois qu’on reçoit un message depuis Redis, avant de le diffuser, il faut bien vérifier que l’utilisateur connecté est autorisé à recevoir ce type de message.

Pour cela, chaque utilisateur possède une liste paramétrable de type de notification qu’il peut recevoir.

Voici un schéma qui résume notre flux.

Un événement est déclenché lorsque nous devons diffuser quelque chose. Cet événement publie ensuite des données à Redis pubsub. Le Socket.io server qui écoute et s’abonne au canal Redis pubsub, reçoit les données, puis les émet via un web socket. Le Socket.io client qui écoute le Socket.io server reçoit la diffusion et fait quelque chose avec les données. Dans notre cas, on va notifier l’utilisateur.

Offline notification

En tant que tel, un utilisateur ne peut recevoir de notifications que s’il est connecté à l’application (via socket.io).Toutes les notifications envoyées pendant qu’il est déconnecté sont perdues.

Pour remédier à ce problème, nous avons utilisé le système de notifications de Laravel en l’intégrant avec notre solution présentée précédemment.

Nous avons besoin pour cela d’avoir une trace de toutes les notifications envoyées en mode offline. Nous avons donc opté pour stocker les notifications dans la base de données en attendant leur lecture. A chaque fois qu’un utilisateur se connecte, on peut récupérer toutes les notifications non lues de l’utilisateur avec :

$user->unreadNotifications

et en même temps il continue à recevoir les nouvelles en temps réel.

Pour que cela fonctionne, il faut ajouter une table spécifique aux notifications avec cette commande d’artisan :

php artisan notifications:table

php artisan migrate

Les champs de cette table :

  • id : un identifiant unique

Pour créer une notification :

php artisan make:notification DemandeCongesSent

Dans la fonction via on définit le canal, donc dans notre cas la base de données (database), et dans la fonction toDatabase on définit les données transmises dans la base.

L’envoie de la notification est déclenché par le lancement d’un événement via un handler ou listener.

Pour créer un listener, on utilise cette commande :

php artisan make:listener DemandeCongesEventHandler — event demandeCongesEvent

On établit la liaison entre l’événement et le listener dans EventServiceProvider.

NIDABRAHIM Youssef

Written by

Software engineer | AI Enthusiast