PHPackages                             raneomik/nette-mercure - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. raneomik/nette-mercure

ActiveLibrary[API Development](/categories/api)

raneomik/nette-mercure
======================

🚀 Nette Mercure Extension: nette wrapper around https://github.com/symfony/mercure

v0.4.2(2mo ago)05[1 PRs](https://github.com/raneomik/nette-mercure/pulls)MITPHPPHP &gt;=8.3CI passing

Since Jan 28Pushed 1mo agoCompare

[ Source](https://github.com/raneomik/nette-mercure)[ Packagist](https://packagist.org/packages/raneomik/nette-mercure)[ Docs](https://github.com/raneomik/nette-mercure)[ RSS](/packages/raneomik-nette-mercure/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (7)Dependencies (16)Versions (10)Used By (0)

Nette Mercure Extension
=======================

[](#nette-mercure-extension)

[![.github/workflows/ci.yml](https://github.com/raneomik/nette-mercure/actions/workflows/ci.yml/badge.svg?style=flat-square)](https://github.com/raneomik/nette-mercure/actions/workflows/ci.yml)[![.github/workflows/coverage.yml](https://github.com/raneomik/nette-mercure/actions/workflows/coverage.yml/badge.svg?style=flat-square)](https://github.com/raneomik/nette-mercure/actions/workflows/coverage.yml)[![Mutation Testing](https://github.com/raneomik/nette-mercure/actions/workflows/mutation-test.yml/badge.svg)](https://github.com/raneomik/nette-mercure/actions/workflows/mutation-test.yml)[![codecov](https://camo.githubusercontent.com/0854a7f79c443be5deab1256c824b1a3b71e06ec02ccb42608f5f87d4c0e7d0c/68747470733a2f2f636f6465636f762e696f2f67682f72616e656f6d696b2f6e657474652d6d6572637572652f67726170682f62616467652e7376673f746f6b656e3d426332334a4a54464c30267374796c653d666c61742d737175617265)](https://codecov.io/gh/raneomik/nette-mercure)[![Mutation testing badge](https://camo.githubusercontent.com/a9cf454219ef255a661eca08626ca190e2ca11690bdb21bc2d8bd6be3f03cfef/68747470733a2f2f696d672e736869656c64732e696f2f656e64706f696e743f7374796c653d666c61742675726c3d687474707325334125324625324662616467652d6170692e737472796b65722d6d757461746f722e696f2532466769746875622e636f6d25324672616e656f6d696b2532466e657474652d6d6572637572652532466d61696e)](https://dashboard.stryker-mutator.io/reports/github.com/raneomik/nette-mercure/main)

*Work In Progress*

🚀 Nette Mercure Extension: wrapper for [symfony/mercure](https://github.com/symfony/mercure) to use Mercure in [Nette framework](https://nette.org)

> Mercure is a protocol allowing to push data updates to web browsers and other HTTP clients in a convenient, fast, reliable and battery-efficient way. It is especially useful to publish real-time updates of resources served through web APIs, to reactive web and mobile apps.

Getting Started
---------------

[](#getting-started)

```
$ composer require raneomik/nette-mercure

```

### Configuration

[](#configuration)

*JWT options to set. Secret, publish &amp; subscribe can be configured at [jwt.io](https://www.jwt.io/#token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdfX0.iHLdpAEjX4BqCsHJEegxRmO-Y6sMxXwNATrQyRNt3GY)*

```
# Configure one default Mercure hub (e.g.:  hub is confgigured to be on same host in frankenphp environment)
mercure:
	url: '%baseUrl%/.well-known/mercure'
	jwt:
		secret: n3tt3-m3rcµr3-fr4nk3nphP-jwT-s3cr3t-k3y # Must be at least 32 characters long
		publish: ['test-topic'] # Optional, default is ['*']. Topics to narrow in JWT validation.
		subscribe: ['test-topic'] # Optional, default is ['*']. Topics to narrow in JWT validation.
		algorithm: HS256 # Optional, default is HS256. @see Symfony\Component\Mercure\Jwt\LcobucciFactory::SIGN_ALGORITHMS
		# You can implement your own Symfony\Component\Mercure\Jwt\TokenFactoryInterface
		factory:  # Optional, default is Symfony\Component\Mercure\Jwt\LcobucciFactory
        useQueryParam: # false by default, to use JWT token in "authorization" query parameter when using {mercure()} function (https://mercure.rocks/spec#uri-query-parameter)
        lifetime: # Optional, default is 3600 (1 hour).
    # following options depends on request parameters
    # - "hub" or "hubName" if several hubs are defined in configuration,
    # - "topics"
    # [- "claims" to pass addtional data to cookie, e.g.: "claims=[exp=1800]" to specificaly set cookie lifetime to 30 minutes]
    useCookie: # true by default, to set JWT token in cookie (https://mercure.rocks/spec#cookie). SSL/Https required client-side
    autoDiscovery: # true by default, to add Link header for Mercure hub discovery (https://mercure.rocks/spec#discovery)

# several Mercure hubs
mercure:
	one
		url: 'https://hub1.mercure.dev/.well-known/mercure'
		jwt:
			secret: n3tt3-m3rcµr3-fr4nk3nphP-jwT-s3cr3t-k3y

	two
		url: 'https://hub2.mercure.dev/.well-known/mercure'
		jwt:
			secret: n3tt3-m3rcµr3-fr4nk3nphP-jwT-s3cr3t-k3y

	# ...
```

### Publish messages

[](#publish-messages)

```
use Raneomik\NetteMercure\BroadcasterInterface;
use Raneomik\NetteMercure\Core\Publish\Latte\TurboStream\Action;

final class SomeService
{
	public function __construct(
		private BroadcasterInterface $broadcaster,
	) {
	}

	public function someAction(): void
	{
		// ...

		// minimalist broadcast to default hub
		$this->broadcaster->broadcast(
			data: 'Hello Nette from Mercure!', // ['message' => 'message'] / new Class('message')
			topics: 'test-topic' // ['test-topic']),
		);

		// broadcast to specific hub
		$this->broadcaster->broadcast(
			data: 'Hello Nette from Mercure!',
			topics: ['test-topic'],
			template: 'test.latte', // existing template
			options: [
				'hub' => 'two' // hub name defined in configuration and where to publish, default is first found hub
			],
		);

		// broadcast to all hubs
		$this->broadcaster->broadcast(
			data: 'Hello Nette from Mercure!',
			topics: ['test-topic'],
			template: 'test.stream.latte',
			options: [
				'action' => Action::Update  // for turbo streams or block organisation in same template. Template must have Action blocks
			],
			toAll: true,
		);

		// ...
	}
}
```

### Subscribe to updates

[](#subscribe-to-updates)

Generate mercure url in Latte templates, setup your JavaScript client to listen to Mercure updates and render them in selected containers :

*When working with JWT token in Authorisation Header, you may need a [polyfill](https://github.com/Yaffle/EventSource).*

```

    Waiting for updates...

    /**
     * use mercure(array|string|null $topics, ?string $hub = null) function to render mercure URL.
     * - "hub" param defines the hub to subscribe to if multiple hubs are defined in configuration, default is first found hub
     * - "addJwt" option adds jwt token in query url and overrides "useQueryParam: false" (default) configuration option
     */
    const eventSource = new EventSource({mercure('test-topic', hub: 'hubName', [addJwt => true])});

    const containers = document.querySelectorAll('.mercure-container');
    eventSource.onmessage = event => {
        for (const container of containers) {
            container.textContent = event.data;
        }
    }

    // or using polyfill with jwt token as Auth Bearer

    import { EventSourcePolyfill } from 'event-source-polyfill';

    const es = new EventSourcePolyfill({mercure('test-topic')},
        headers: {
// use mercureJWTToken(array|string|null $subscribe = ['*'], array|string|null $publish = ['*'], ?string $hub = null) function to render mercure JWT token
            'Authorization': 'Bearer: ' + {mercureJWTToken('test-topic')}
        }
    );

    eventSource.onmessage = event => {
        for (const container of containers) {
            container.textContent = event.data;
        }
    }

```

### Subscribe using discovery

[](#subscribe-using-discovery)

You can subscribe to specific topic(s) and hub using [Discovery mechanism](https://symfony.com/doc/current/mercure.html#discovery).

- Setup a `/subscribe` endpoint :

```
use Nette;
use Nette\Application\Attributes\Parameter;
use Raneomik\NetteMercure\SubscriberInterface;

final class SubscribePresenter extends Nette\Application\UI\Presenter
{
    #[Parameter]
    public ?string $hub = null;

    #[Parameter]
    public string|array $topics = ['*'];

    public function __construct(
        private readonly SubscriberInterface $subscriber,
    ) {
    }

    public function renderDefault(): void
    {
        if (!$this->isAjax()) {
            return;
        }

        $this->sendJson(
            $this->subscriber->subscribe($this->hub, $this->topics),
        );
    }
}
```

- Setup the listening client :

```
import { EventSourcePolyfill } from 'event-source-polyfill';

fetch('/subscribe?topics=/* topic(s) to define. "['*']" by default */&hub=/* hubname if multiple hubs configured, first by default */') // Has header Link: ; rel="mercure"
    .then(response => {
        // Extract the hub URL from the Link header
        const hubUrl = response.headers.get('Link').match(/]+)>;\s+rel=(?:mercure|"[^"]*mercure[^"]*")/)[1];

        // Append the topic(s) to subscribe as query parameter
        const hub = new URL(hubUrl, window.origin);
        hub.searchParams.append('topic', /*topic(s)*/);

        const jwtToken = response.json().jwtToken;

        const es = new EventSourcePolyfill(hub,
        headers: {
            'Authorization': `Bearer: ${jwtToken}`
        };

        es.onmessage = event => {
            for (const container of containers) {
                container.textContent = event.data;
            }
        };
    );
});
```

### Broadcast &amp; Subscribe to turbo-streams

[](#broadcast--subscribe-to-turbo-streams)

You can also subscribe to [turbo-streams](https://turbo.hotwired.dev) :

- Server side :

```
//...
		$this->broadcaster->broadcast(
			data: 'Hello Nette from Mercure!',
			topics: ['test-topic'],
//to activate "text/vnd.turbo-stream.html" content type and "turbo-stream" mercure event type to listen to, template name must end with ".stream.latte" / "Stream.latte" and have matching "action" blocks
			template: 'test.stream.latte',
			options: [
/** @see Raneomik\NetteMercure\Core\Publish\Latte\TurboStream\Action for available action blocks */
				'action' => Action::Update
				'target' => 'stream-container' // target container id to update in client side. Default is "stream-container"
			],
		);
//...
```

```

{contentType $contentType ?? 'text/html'}

{block update}

            {$data}

{/block}
```

- Client side :

```
    import * as Turbo from '@hotwired/turbo'; //npm install @hotwired/turbo

    const eventSource = new EventSource($mercureUrl);

    const containers = document.querySelectorAll('.mercure-container');
        eventSource.addEventListener('turbo-stream', event => {
            Turbo.renderStreamMessage(event.data);
        });

        eventSource.onerror = event => {
            console.error("Mercure connection error: ", event);

            Turbo.disconnectStreamSource(eventSource);
        }

        document.onclose = () => {
            console.info("Bye !");
            Turbo.disconnectStreamSource(eventSource);
            eventSource.close();
        };
    }
```

Resources
---------

[](#resources)

- Based on [symfony/mercure](https://github.com/symfony/mercure) / [Documentation](https://symfony.com/doc/current/mercure.html)
- [![](https://camo.githubusercontent.com/9402b57d668e2dcbf18a6851730f06c5816e93d14dde89944b9877283073d9ea/68747470733a2f2f6d6572637572652e726f636b732f66617669636f6e2e69636f) Mercure](https://mercure.rocks)
- [![](https://camo.githubusercontent.com/e9b1659cabe58da349fe6e5ea1070fea8f320511f8c44b7ce2fc5d906082c553/68747470733a2f2f6672616e6b656e7068702e6465762f66617669636f6e2e69636f) FrankenPHP Real-time](https://frankenphp.dev/docs/mercure/)and [Hot-Reload](https://frankenphp.dev/docs/hot-reload/)
- [![](https://camo.githubusercontent.com/4173fb1ce3f3ba8d26b028d7dc27d07465b88495756273d95d8fe421b3d59adc/68747470733a2f2f6e657474652e6f72672f66617669636f6e2e69636f) Nette](https://nette.org), [![](https://camo.githubusercontent.com/741603ec6af74e601eea3bef67aea54860a6086829d640837801ac84cd26a757/68747470733a2f2f6c617474652e6e657474652e6f72672f66617669636f6e2e69636f) Latte](https://latte.nette.org), [![](https://camo.githubusercontent.com/ce36494e61ae72d38e713e5e9c626d5ba0f8ec37288237735226673703fe3544/68747470733a2f2f7465737465722e6e657474652e6f72672f66617669636f6e2e69636f) Tester](https://tester.nette.org/en), [![](https://camo.githubusercontent.com/6a2ca3092786f3d4721779b687e692bd6172057fda63e610de635833d3c510ef/68747470733a2f2f74726163792e6e657474652e6f72672f66617669636f6e2e69636f) Tracy](https://tracy.nette.org/en/)

Known issues
------------

[](#known-issues)

- "anonymous" option for mercure in Caddy configuration seems to work only with Symfony\\Mercure\\FrankenPhpHub and the FrankenPHP built-in `mercure_publish` function. HttpClient shows errors such as "405 Method Not Allowed" in this case.
- [TODO](TODO.md)

###  Health Score

39

—

LowBetter than 85% of packages

Maintenance93

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity44

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 96.6% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

7

Last Release

88d ago

PHP version history (2 changes)v0.1.0PHP &gt;=8.4

v0.2.0PHP &gt;=8.3

### Community

Maintainers

![](https://www.gravatar.com/avatar/ef442308d55301a56114936c5389fbb4ab31f3cf7ac4b9b6f3a583bc3b9f9b8c?d=identicon)[raneomik](/maintainers/raneomik)

---

Top Contributors

[![raneomik](https://avatars.githubusercontent.com/u/19893310?v=4)](https://github.com/raneomik "raneomik (28 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (1 commits)")

---

Tags

mercurenette-extensionssepushupdatesssemercureBroadcastingnette-extension

###  Code Quality

Static AnalysisRector

Code StyleECS

### Embed Badge

![Health badge](/badges/raneomik-nette-mercure/health.svg)

```
[![Health](https://phpackages.com/badges/raneomik-nette-mercure/health.svg)](https://phpackages.com/packages/raneomik-nette-mercure)
```

###  Alternatives

[symfony/mercure-bundle

Symfony MercureBundle

30813.5M53](/packages/symfony-mercure-bundle)[symfony/mercure

Symfony Mercure Component

43915.1M28](/packages/symfony-mercure)[mvanduijker/laravel-mercure-broadcaster

Mercure broadcaster

16866.5k](/packages/mvanduijker-laravel-mercure-broadcaster)[norkunas/onesignal-php-api

OneSignal API for PHP

2441.8M21](/packages/norkunas-onesignal-php-api)[pubnub/pubnub

This is the official PubNub PHP SDK repository.

1314.6M17](/packages/pubnub-pubnub)[chapter-three/apple-news-api

Push content to Apple News.

38307.5k2](/packages/chapter-three-apple-news-api)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
