En cliquant sur "Accepter", vous acceptez que des cookies soient stockés sur votre appareil afin d'améliorer la navigation sur le site. Consultez notre politique de confidentialité pour plus d'informations.
Home
/
Blog
/
Article

Chat en temps réel avec Laravel et Pusher

Publié le
07 Novembre 2019
-
5 min
de lecture

Partager l'article

Si vous souhaitez créer le prochain concurrent de Slack ou diffuser plus humblement des événements à plusieurs utilisateurs et éventuellement à une autre application telle qu'une application React Native, vous devrez peut-être envisager d'utiliser Pusher. Pusher est un service très pratique pour effectuer du pub / sub. Il vous évite de devoir maintenir votre propre serveur WebSocket utilisant un pilote Redis ou SNS.

Dans cet article, je vais vous montrer comment gérer la publication de messages de conversation dans Pusher et comment vous abonner à ces messages de manière sécurisée en frontend à l'aide de canaux privés.

Introduction

Nous allons supposer que notre application Laravel est simplement une API REST, avec une authentification Passport, et que nous souhaitons implémenter l’interface du chat dans une application JS externe.

Premièrement, nous allons installer le SDK Pusher avec $ composer require pusher/pusher-php-server "~3.0" et le configurer dans config/broadcast.php.

Supposons maintenant que nous ayons les modèles Conversation etMessage suivants:


namespace App\Models;

use App\User;
use Illuminate\Database\Eloquent\Model;

class Conversation extends Model
{
    /**
     * Users associated to this conversation.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function users()
    {
        return $this->belongsToMany(User::class);
    }

    /**
     * Messages in this conversation.
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function messages()
    {
        return $this->hasMany(Message::class);
    }
}

Le modèle Message:


namespace App\Models;

use App\User;
use Illuminate\Database\Eloquent\Model;

class Message extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'conversation_id',
        'sender_id',
        'content',
    ];

    /**
     * Conversation associated to this message.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function conversation()
    {
        return $this->belongsTo(Conversation::class);
    }

    /**
     * Sender of the message.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function sender()
    {
        return $this->belongsTo(User::class);
    }
}

Quelque part dans votre code, vous allez gérer le stockage des messages de conversation.

Lorsqu'un nouveau message est ajouté à une conversation, vous allez déclencher un événement:


event(new MessageWasPosted($message));

Canaux privés

Le but de l'application est de diffuser des messages de conversation vers l'application frontend, de manière sécurisée.

Nous ne voulons pas que les autres utilisateurs puissent voir les messages des autres conversations. Nous allons donc diffuser les événements de message sur un canal de notification privé.

Chaque conversation aura son canal dédié appelé private-conversation.{id}.

Le préfixe private- est une convention Pusher pour nommer les canaux privés

Sur ces canaux, nous allons diffuser un événement message.posted lorsqu'un nouveau message est posté dans une conversation, afin que notre frontend puisse l'écouter et mettre à jour l'interface utilisateur en conséquence.

Backend

Pour cela, nous allons implémenter notre événement MessagePosted qui est déclenché à chaque fois qu'un nouveau message est posté:


// /app/Events/MessageWasPosted.php
namespace App\Events;

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

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

    /**
     * The message.
     *
     * @var Message
     */
    public $message;

    /**
     * Constructor.
     *
     * @param Message $message
     */
    public function __construct(Message $message)
    {
        $this->message = $message;
    }

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

    /**
     * The event's broadcast name.
     *
     * @return string
     */
    public function broadcastAs()
    {
        return 'message.posted';
    }
}

Nous devons également spécifier qui peut rejoindre ce canal, dans notre cas, seuls les utilisateurs appartenant à la conversation. Pour cela, nous allons créer une classe Channel personnalisée et implémenter la méthode join():


// /app/Broadcasting/ConversationChannel.php

    namespace App\Broadcasting;

    use App\Models\Conversation;
    use App\User;

    class ConversationChannel
    {
        /**
         * Authenticate the user's access to the channel.
         *
         * @param User         $user
         * @param Conversation $conversation
         *
         * @return array|bool
         */
        public function join(User $user, Conversation $conversation)
        {
            return $conversation->users->contains($user);
        }
}

Une fois que c'est fait, nous devons configurer notre route, en utilisant cette nouvelle classe.


// /routes/channels.php
use App\Broadcasting\ConversationChannel;

Broadcast::channel('conversation.{conversation}', ConversationChannel::class);

La diffusion sur des canaux privés nécessite une authentification, mais grâce au package Broadcast de Laravel, le contrôleur d’authentification est déjà géré pour nous. Nous devons simplement configurer ces routes dans le service provider:


// /app/Providers/BroadcastServiceProvider.php
Broadcast::routes(['middleware' => ['auth:api']]);

Notez que j'utilise le middleware auth:api ici, car c'est le middleware que j'utilise pour authentifier les utilisateurs d'API. L'utilisation de canaux privés requiert une authentification. Vous devez donc utiliser le middleware approprié en fonction de la méthode d'authentification utilisée pour votre API. Si votre application web ne se trouve pas sur le même domaine, par exemple une application React Native, vous devez configurer un middleware CORS pour ces routes.

Vous pouvez installer https://github.com/barryvdh/laravel-cors et mettre à jour votre $routeMiddleware en ajoutant:


// /app/Http/Kernel.php
'cors' => \Barryvdh\Cors\HandleCors::class,

Ensuite, mettez à jour les routes pour utiliser le nouveau middleware:


// /app/Providers/BroadcastServiceProvider.php
Broadcast::routes(['middleware' => ['auth:api', 'cors']]);

Si l'application front se trouve sur un domaine différent, vous pouvez également ne pas avoir accès aux jetons CSRF. Par conséquent, vous désactiverez probablement le contrôle CSRF pour la route d'authentification:


// /app/Http/Middleware/VerifyCsrfToken.php
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        'broadcasting/auth'
];

Frontend

Sur le frontend, nous devons donner à Pusher l'endpoint pour l'authentification à l'aide de la propriété authEndpoint et éventuellement ajouter des en-têtes supplémentaires pour l'authentification. Par exemple, si vous utilisez Passport, vous passerez un en-tête Authorization contenant le jeton récupéré précédemment au cours du processus d'authentification. L'id de la conversation sera également récupéré plus tôt, par exemple lorsque l'utilisateur ouvre la conversation. Notre pseudo-code pour s'abonner aux messages de conversation devient:


  var pusher = new Pusher('*****', {
    authEndpoint: 'https://project.local/broadcasting/auth',
    cluster: 'us2',
    encrypted: true,
    auth: {
      headers: {
        Authorization: 'Bearer ****'
      }
    }
  });

  var channel = pusher.subscribe('private-conversation.1');

  channel.bind('message.posted', function (data) {
    alert(JSON.stringify(data));
});

Pusher utilisera maintenant l'endpoint spécifié pour effectuer l'authentification des utilisateurs à l'aide des en-têtes que nous avons configurés. Seuls les utilisateurs appartenant à la conversation pourront s'abonner et recevoir des événements lorsqu'un nouveau message est publié. C'est tout! Vous pouvez maintenant créer d'autres événements et les diffuser sur ce canal, par exemple, lorsqu'un message est supprimé ou lorsque l'utilisateur est en train d'écrire.

Revenir au blog