I installed mercure using the following command as stated in the documentation:
composer require mercure
After that, i'm supposed to start the mercure server and here is the issue: how?
Symfony's documentation doesn't state that.
Mercure's documentation also doesn't state anything about it, especially using a config file generated in /config/packages/mercure.yaml
.
What i've found is that i need to download a separate binary (for my specific platform) and then i'm supposed to start the server from Powershell using the following:
.\/bin/mercure run
However, if i try to pass -envfile ".env"
, then it doesn't even try to use any of the variables inside.
On top of that, it doesn't seem to be using any environment variables at all, even ones i've defined in cmd.
There are other things like how do i allow CORS, as there isn't something i can change in my config file, it isn't an argument i can pass to the binary and i can't add it as an environment variable.
if i ever try to subscribe to the event (which is done on port 2019 cause of the previous issues), 404 and CORS is returned.
also this is what i get with just .\/bin/mercure run
:
My .env
:
Right now i'm wondering how am i supposed to use mercure, i've looked at a couple videos for symfony 5.4 and they basically either are on linux and it somehow work for them OR It's marked as outdated cause of configs changes, syntax changes and others...
The documentation also doesn't help at all in that regard which kind of make me regret trying to use mercure.
EDIT:
I was told to try fiddling with Caddy configs, but it doesn't seem like mercure is trying to use those either, even tried specifying the config with -config
.
Result:
Caddyfile:
# Learn how to configure the Mercure.rocks Hub on https://mercure.rocks/docs/hub/config
{
{$GLOBAL_OPTIONS}
http_port 3000
https_port 3000
}
localhost:3000
route {
encode zstd gzip
@origin header Origin http://localhost:8000
header @origin Access-Control-Allow-Origin "http://localhost:8000"
header @origin Access-Control-Request-Method GET
header Access-Control-Allow-Credentials "true"
mercure {
# Publisher JWT key
publisher_jwt "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdfX0.iHLdpAEjX4BqCsHJEegxRmO-Y6sMxXwNATrQyRNt3GY"
subscriber_jwt "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InN1YnNjcmliZSI6WyIqIl19fQ.0J4jgLQSHzPMTZ5jiYM8Yv7jmTANRgILG5FKY98LgEU"
publish_origins *
# Extra directives
{$MERCURE_EXTRA_DIRECTIVES}
}
respond /healthz 200
respond "Not Found" 404
}
at this point, i feel like i'm using every tricks in the book and it still doesn't work, i'm probably gonna end up moving to periodic fetch instead, reliable and actually work.
No HTTP errors, event is apparently subscribed from what mercure say, but nothing received, something tell me mercure doesn't support semantics
With a topic such as /update/test/{0}
, set in the update, the event source and yaml config, no updates are received on the client
i cannot get updates to work at all, either with or without semantics, here is my config and route atm:
mercure.yaml :
mercure:
hubs:
default:
url: '%env(MERCURE_URL)%'
public_url: '%env(MERCURE_PUBLIC_URL)%'
jwt:
secret: '%env(MERCURE_JWT_SECRET)%'
publish: '["/update/test/*"]'
subscribe: '["/update/test/*"]'
My route (DefaultController):
#[Route('/update/test/{id}', name: 'test')]
public function test(HubInterface $hub, ?int $id): Response
{
$update = new Update("localhost:8000/update/test/{$id}",
son_encode(
array(
"result" => "success",
"data" => "User is in Room {$id}"
)
)
);
$hub->publish($update);
return new Response("Success");
}
I will give you an example of mercure running over windows without use of Docker, sorry for my english ok?!!
I will start from an installation of symfony 5.4 with a SSL virtual host: https://myshop.local and MercureBundle.
The goal is to notify for example, to all authenticated users when a new product enter on a shop stock.
First: the mercure hub and your site must run over the same domain or subdomain, in other case isn't possible to share the auth cookie; so our mercure hub will run on https://myshop.local:3000 (the same domain, but another port, check your firewall)
Second: It's neccesary to set the appropiates environment variables, so in your .env.local
:
###> symfony/mercure-bundle ###
# See https://symfony.com/doc/current/mercure.html#configuration
# The URL of the Mercure hub, used by the app to publish updates (can be a local URL)
MERCURE_URL=https://myshop.local:3000/.well-known/mercure
# The public URL of the Mercure hub, used by the browser to connect
MERCURE_PUBLIC_URL=https://myshop.local:3000/.well-known/mercure
# The secret used to sign the JWTs
MERCURE_JWT_SECRET=m3rcu353cr37pa55pra53DEV
###< symfony/mercure-bundle ###
third: Mercure it´s based on topics concept (likes a channels), so your mercure.yaml
recipe:
mercure:
hubs:
default:
url: '%env(MERCURE_URL)%'
public_url: '%env(MERCURE_PUBLIC_URL)%'
jwt:
secret: '%env(MERCURE_JWT_SECRET)%'
publish: ['notif/new-on-stock'] # the topic or topics where the notifications will be published coming from the mercure hub.
subscribe: ['notif/new-on-stock'] # the topic or topics to which users will subscribe to receive notifications.
fourth: You need to download the Windows binaries of mercure, and set somes configs in the Caddy file:
# Learn how to configure the Mercure.rocks Hub on https://mercure.rocks/docs/hub/config
{
{$GLOBAL_OPTIONS}
}
{$SERVER_NAME:myshop.local:3000}
log
tls C:\wamp64\bin\apache\apache2.4.41\conf\ssl\myshop.local.crt C:\wamp64\bin\apache\apache2.4.41\conf\ssl\myshop.local.key
route {
encode zstd gzip
mercure {
# Transport to use (default to Bolt)
transport_url {$MERCURE_TRANSPORT_URL:bolt://mercure.db}
# Publisher JWT key
publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
# Subscriber JWT key
subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG}
cors_origins https://myshop.local
publish_origins https://myshop.local
# Extra directives
{$MERCURE_EXTRA_DIRECTIVES}
}
respond /healthz 200
respond "Not Found" 404
}
three important aspects here:
fifth: you must run your mercure server from your power shell; from inside your mercure binary directory run: $env:MERCURE_PUBLISHER_JWT_KEY='m3rcu353cr37pa55pra53DEV';$env:MERCURE_SUBSCRIBER_JWT_KEY='m3rcu353cr37pa55pra53DEV';.\mercure.exe run -config .\Caddyfile
Important!!: that key is the same of enviroment var MERCURE_JWT_SECRET
Now from a controller, you must publish on a topic a notification:
public function publishNewProductAction(Symfony\Component\Mercure\HubInterface $mercureHub):Response{
/** insert new product to a database **/
...
$em->persist($newProduct);
$em->flush();
$data=['productName'=>$newProduct->getName(), 'productPrice'=>$newProduct->getPrice()];
$update = new Symfony\Component\Mercure\Update();
$hubUpdate = new Symfony\Component\Mercure\Update(
'notif/new-on-stock', # the topic where you go to publish
\json_encode($data), # the data that you want to publish on the mercure hub
true # indicate that need auth
);
return new Response('A new product has been registered!!'); # its the normal response to user that insert a product (admin user for exmple)
}
Important: the real power of symfony to create a real-time notification system is in combining the Messenger component and async queue with Mercure. You can investigate about it.
So, on the client side for example:
<script type="text/javascript">
$(document).ready(function () {
const eventSource = new EventSource("{{ mercure('notif/new-on-stock', { subscribe:'notif/out-of-stock'})|escape('js')}}", {withCredentials: true});
eventSource.onopen = function () {
console.log('Socket connection!');
};
eventSource.onmessage = function (e) {
var data = JSON.parse(e.data);
console.log('A new product is enable for you: '+data.productName+'. Price: $'+data.price);
};
eventSource.onerror = function () {
console.log('Socket error!');
};
});
</script>