> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Au cœur du driver de broadcasting Mercure

> Une plongée au niveau du code source dans le driver de broadcasting Mercure ajouté au framework Laravel : son architecture HTTP/SSE, ses JWT publish/subscribe séparés, son autorisation de canal par cookie unique et ses canaux chiffrés de bout en bout.

<Warning>
  Le `MercureBroadcaster` présenté ici a été ajouté dans la [PR #61474 de laravel/framework](https://github.com/laravel/framework/pull/61474) (mergée le 10 septembre 2026). Au moment de la rédaction, il ne fait pas encore partie d'une release taguée et n'est pas mentionné dans la [documentation officielle](https://laravel.com/docs/broadcasting). Cette page est un aperçu basé sur le code source et les docblocks — vérifiez le changelog de `laravel/framework` avant de vous y fier en production.
</Warning>

## Vue d'ensemble

La couche broadcasting de Laravel a longtemps proposé [Reverb](/fr/broadcasting), Pusher et Ably comme drivers « WebSocket natifs ». Une nouvelle valeur `mercure` vient d'être ajoutée à `config/broadcasting.php`.

[Mercure](https://mercure.rocks) est un protocole temps réel bâti sur Server-Sent Events (SSE) plutôt que sur une connexion WebSocket bidirectionnelle. Il se comporte davantage comme du long-polling HTTP/1.1 ou HTTP/2, ce qui lui confère plusieurs propriétés distinctes :

* Il traverse de manière transparente l'infrastructure HTTP habituelle (reverse proxies, CDN, load balancers).
* Les clients peuvent être implémentés uniquement avec l'API `EventSource` du navigateur — aucune bibliothèque cliente dédiée n'est nécessaire.
* [FrankenPHP](/fr/blog/laravel-cloud) embarque un hub Mercure intégré, donc aucune infrastructure supplémentaire n'est requise pour l'essayer.

```mermaid theme={null}
flowchart LR
    A["Serveur<br>broadcast(event)"] --> B["MercureBroadcaster"]
    B --> C["Hub Mercure<br>(Hub / FrankenPhpHub)"]
    C -->|"SSE (EventSource)"| D["Navigateur<br>Laravel Echo"]
    D --> E["Mise à jour<br>temps réel de l'UI"]
```

## La nouvelle valeur `driver`

Le commentaire listant les drivers supportés en tête de `config/broadcasting.php` mentionne désormais `mercure` :

```php theme={null}
// Supported: "reverb", "pusher", "ably", "mercure", "redis", "log", "null"
```

Un exemple de configuration de connexion est également fourni :

```php theme={null}
'mercure' => [
    'driver' => 'mercure',
    'url' => env('MERCURE_URL'),
    'public_url' => env('MERCURE_PUBLIC_URL'),
    'secret' => env('MERCURE_JWT_SECRET'),
    'encryption_key' => env('MERCURE_ENCRYPTION_KEY'),
    'claims' => [
        'iss' => env('MERCURE_JWT_ISSUER'),
        'client_id' => env('APP_NAME'),
    ],
    'cookie_name' => env('MERCURE_COOKIE_NAME'),
    'subscribe_expiration' => (int) env('MERCURE_SUBSCRIBE_EXPIRATION', 5),
],
```

Si `url` est omis, le driver bascule automatiquement sur le hub Mercure intégré à FrankenPHP (la fonction `mercure_publish()`). Cette décision est prise dans `CreatesMercureDrivers::mercure()` :

```php theme={null}
public function mercure(array $config)
{
    if (empty($config['url'])) {
        return $this->frankenPhpMercure($config);
    }

    // ...
}
```

Lorsque vous utilisez un hub externe, `url` doit pointer vers l'API d'administration utilisée pour la publication, tandis que `public_url` correspond à l'URL à laquelle le navigateur se connecte. Cette séparation permet des configurations où la publication passe par un réseau interne (par exemple Docker Compose) alors que seule l'URL publique est exposée au navigateur.

## Tokens de publication et d'abonnement séparés

Mercure est un protocole de contrôle d'accès basé sur JWT. Le trait `CreatesMercureDrivers` construit **deux fabriques de tokens distinctes** : l'une pour la publication (serveur → hub), l'autre pour l'abonnement (navigateur → hub) :

* `secret`, `publish_secret` et `subscribe_secret` peuvent chacun être configurés indépendamment, avec un repli sur `secret`.
* `algorithm`, `publish_algorithm` et `subscribe_algorithm` peuvent de la même façon être définis par côté (valeur par défaut `HS256`).
* HS256 exige un secret d'au moins 32 octets, HS384 au moins 48 et HS512 au moins 64 ; dans le cas contraire, une `InvalidArgumentException` est levée.

```php theme={null}
protected function mercureSecret(array $config, string $side)
{
    $secret = ($config[$side.'_secret'] ?? null) ?: ($config['secret'] ?? null);

    // ...

    $minimumLength = ['HS256' => 32, 'HS384' => 48, 'HS512' => 64][$algorithm] ?? 0;

    if (strlen($secret) < $minimumLength) {
        throw new InvalidArgumentException(/* ... */);
    }

    return $secret;
}
```

Le token de publication porte `Grant::ACTION_PUBLISH` pour tous les topics (`*`) et est mémoïsé par `CachingTokenProvider`, afin de ne pas régénérer un nouveau JWT à chaque appel de broadcast.

## Autorisation de canal via un cookie unique

La plus grande différence architecturale par rapport aux drivers WebSocket est que l'autorisation côté abonné est gérée par **un seul cookie**. `MercureBroadcaster::auth()` accepte un tableau `channel_names` (plafonné à 100 par requête), évalue l'autorisation canal par canal, puis émet un unique cookie d'autorisation qui les couvre tous :

```php theme={null}
public function auth($request)
{
    $channelNames = (array) $request->input('channel_names', []);

    if ($channelNames === [] ||
        count($channelNames) > 100 ||
        $channelNames !== array_filter($channelNames, 'is_string')) {
        throw new AccessDeniedHttpException;
    }

    // Évaluer l'autorisation canal par canal et accumuler les grants...

    return (new JsonResponse([/* ... */]))
        ->cookie($this->makeAuthorizationCookie($request, $grants, $user));
}
```

Point crucial : un seul canal refusé ne fait pas échouer la réponse entière — il est marqué `denied: true` dans la charge utile pendant que les autres canaux autorisés continuent de fonctionner. Cela compte parce que Mercure multiplexe de nombreux topics sur une seule connexion EventSource : les canaux peuvent donc être ajoutés ou retirés en cours de session sans détruire la connexion.

Les canaux Presence reçoivent une forme de `Grant` différente de celle des canaux `private`/`private-encrypted` classiques, ciblant le pattern d'URL d'abonnement utilisé par l'[API Subscription](https://mercure.rocks/docs/hub/concepts/active-subscriptions) de Mercure.

## Canaux chiffrés de bout en bout

Les canaux préfixés par `private-encrypted-` sont traités comme chiffrés de bout en bout (E2EE) — ce qui signifie que même le hub Mercure lui-même ne voit jamais la charge utile. Cette capacité n'existe pas dans les drivers Pusher et Reverb actuels.

Définir une `encryption_key` de 32 octets encodée en base64 active le `ChannelEncrypter`, qui empaquette le nom de l'événement, la charge utile et le socket ID dans un JWE (JSON Web Encryption) avant l'envoi par `broadcast()`. Le hub ne relaie donc que des octets chiffrés.

```php theme={null}
'encryption_key' => env('MERCURE_ENCRYPTION_KEY'),
```

```shell theme={null}
php -r "echo base64_encode(random_bytes(32));"
```

Les abonnés déchiffrent côté navigateur à l'aide du champ `jwk` (JSON Web Key) renvoyé dans la réponse de `auth()` — un échange de clé hors bande recommandé par la spécification Mercure, de sorte que le hub n'apprend jamais la clé.

<Info>
  Les canaux Presence ne peuvent pas être chiffrés, puisque leur liste de membres transite par conception via l'API Subscription du hub — ce qui est incompatible avec l'E2EE.
</Info>

## Whispers (messages directs entre clients)

Lorsque `client_events` est activé (valeur par défaut), chaque canal protégé se voit attribuer un « topic whisper » dédié auquel les abonnés peuvent publier :

```php theme={null}
if ($this->clientEvents && $whisperTopics !== []) {
    $grants[] = new Grant([Grant::ACTION_SUBSCRIBE, Grant::ACTION_PUBLISH], $whisperTopics);
}
```

Le topic propre du canal reste réservé au serveur, de sorte qu'un client ne peut jamais forger un événement paraissant venir du serveur. Cela reprend l'idée des « client events » de Pusher, mais garde l'autorisation clairement séparée grâce à des topics distincts.

## Nommage des topics

Comme les hubs Mercure sont souvent partagés entre plusieurs applications, les topics sont placés sous un espace de noms `topic_prefix` afin d'éviter les collisions. La valeur par défaut est :

```php theme={null}
protected string $topicPrefix = 'https://laravel.alt/echo/',
```

`.alt` est un suffixe DNS réservé et non résolvable ([RFC 9476](https://www.rfc-editor.org/rfc/rfc9476.html)), il ne peut donc jamais entrer en collision avec un domaine réel. Les noms de canaux sont encodés URL selon la [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986.html) dans un unique segment de chemin :

```php theme={null}
protected function channelTopic($channelName)
{
    return $this->topicPrefix.'channel/'.rawurlencode($channelName);
}
```

## Domaine de cookie et messages d'erreur

Un hub Mercure tourne fréquemment sur un sous-domaine différent de celui de l'application (par exemple `mercure.example.com` vs `app.example.com`) ; l'échec à résoudre un domaine de cookie commun lève donc une exception descriptive :

```php theme={null}
throw new BroadcastException(sprintf(
    'Mercure error: %s. Adjust the Mercure "public_url" configuration value so the hub [%s] shares a registrable domain with the application host [%s].',
    rtrim($e->getMessage(), '.'), $this->hub->getPublicUrl(), $request->getHost()
), 0, $e);
```

Si vous utilisez un nom de cookie préfixé par `__Secure-` ou `__Host-`, le driver vérifie également au démarrage que `public_url` est bien en HTTPS :

```php theme={null}
if (str_starts_with($hub->getCookieName(), '__') &&
    parse_url($hub->getPublicUrl(), PHP_URL_SCHEME) === 'http') {
    throw new InvalidArgumentException(/* ... */);
}
```

## Choisir entre Reverb, Pusher/Ably et Mercure

| Driver        | Transport  | Infrastructure                           | Chiffrement E2E            |
| ------------- | ---------- | ---------------------------------------- | -------------------------- |
| Reverb        | WebSocket  | Serveur auto-hébergé requis              | Non                        |
| Pusher / Ably | WebSocket  | SaaS                                     | Non                        |
| Mercure       | SSE (HTTP) | Hub auto-hébergé ou intégré à FrankenPHP | Oui (`private-encrypted-`) |

Mercure convient bien lorsque vous n'avez pas besoin d'une connexion bidirectionnelle persistante — notifications, indicateurs de progression ou cas d'usage de type chat, essentiellement du serveur vers le client. Si vous utilisez déjà FrankenPHP, il fonctionne sans aucune infrastructure supplémentaire.

## Pages liées

* [Bases du broadcasting](/fr/broadcasting)
* [Laravel Cloud](/fr/blog/laravel-cloud) (runtime basé sur FrankenPHP)


## Related topics

- [Broadcasting](/fr/broadcasting.md)
- [Illuminate\Support\Manager — anatomie du système de drivers](/fr/advanced/manager.md)
- [Hooks de session](/fr/packages/laravel-copilot-sdk/hooks.md)
- [Pattern Pipeline](/fr/advanced/pipeline.md)
- [Trait Macroable](/fr/advanced/macroable.md)
