milpa / mercure
Mercure hub publisher for the Milpa PHP framework: self-signed JWT generation and SSE publish over cURL.
Requires
- php: >=8.3
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.65
- milpa/core: *
- phpstan/phpdoc-parser: ^2.3
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5
This package is auto-updated.
Last update: 2026-07-07 19:45:47 UTC
README
Milpa Mercure
The Mercure hub publisher for the Milpa PHP framework, with zero package dependencies. One class,
MercureService, mints self-signed HS256 JWTs by hand and pushes real-time updates to a Mercure hub overcurl_*directly — no HTTP client abstraction, nomilpa/coreat runtime.
milpa/mercure is the smallest possible seam onto a Mercure hub:
sign a publisher JWT, POST a topic + JSON payload to the hub's /.well-known/mercure
endpoint, and mint short-lived subscriber JWTs for the browser's EventSource connection.
No ORM, no event dispatcher, no framework coupling — construct it with four strings and
call publish().
Install
composer require milpa/mercure
Quick example
use Milpa\Mercure\MercureService; $mercure = new MercureService( hubUrl: 'https://hub.internal/.well-known/mercure', // server-side POST target publicUrl: 'https://hub.example.com/.well-known/mercure', // what the browser connects to publisherKey: $_ENV['MERCURE_PUBLISHER_JWT_KEY'], subscriberKey: $_ENV['MERCURE_SUBSCRIBER_JWT_KEY'], ); // Server-side: push an update. Mints a 60s publisher JWT, POSTs form-encoded // topic + JSON data, throws RuntimeException on cURL failure or HTTP >= 400. $mercure->publish('conversations/42/messages', [ 'event' => 'message.created', 'body' => 'hello', ]); // Browser-side: mint a 5-minute subscriber JWT scoped to the topics this // visitor may listen to, and hand the browser the public URL to connect to. $jwt = $mercure->generateSubscriberJwt(['conversations/42/messages']); $publicUrl = $mercure->getPublicUrl();
The browser then opens new EventSource(publicUrl + '?topic=...', { withCredentials: true })
with $jwt set as the mercureAuthorization cookie — standard Mercure subscribe flow, nothing
Milpa-specific about it.
What it does — and doesn't
publish(string $topic, array $data)— signs a 1-minute publisher JWT scoped to$topic, form-encodestopic+json_encode($data), andPOSTs it tohubUrlwithAuthorization: Bearer <jwt>. Acurl_init()failure, acurl_exec()failure, or an HTTP status>= 400all throwRuntimeException—publish()never fails silently.generateSubscriberJwt(array $topics)— a 5-minute HS256 JWT whose payload is{"mercure":{"subscribe":$topics},"exp":...}, signed withsubscriberKey. This is the token a browser presents to subscribe; it grants no publish access.getPublicUrl()— returns thepublicUrlgiven to the constructor, unmodified. Kept separate fromhubUrlbecause the two are commonly different: the server publishes to an internal/Docker-network hostname, while the browser subscribes through a public one.
Both JWTs are HS256, hand-assembled from header.payload.signature with hash_hmac() +
base64url — no JWT library dependency. There is no retry, no queue, and no HTTP client
abstraction to inject: publish() talks to curl_* directly, by design (see
Requirements).
Requirements
- PHP ≥ 8.3 with the cURL extension enabled (
ext-curl) - Nothing else —
milpa/mercurehas no package dependencies, Milpa or otherwise
Documentation
Full API reference: getmilpa.github.io/mercure — generated straight from the source DocBlocks and dressed with the Milpa design system.
Contributing
Contributions are welcome — see CONTRIBUTING.md. Please report security issues via SECURITY.md, and note that this project follows a Code of Conduct.
License
Apache-2.0 © TeamX Agency.
Milpa is designed, built, and maintained by TeamX Agency.