diff --git a/src/Turbo/assets/dist/turbo_stream_controller.d.ts b/src/Turbo/assets/dist/turbo_stream_controller.d.ts index 2806afea3cc..4497ad82631 100644 --- a/src/Turbo/assets/dist/turbo_stream_controller.d.ts +++ b/src/Turbo/assets/dist/turbo_stream_controller.d.ts @@ -4,11 +4,13 @@ export default class extends Controller { topic: StringConstructor; topics: ArrayConstructor; hub: StringConstructor; + eventSourceOptions: ObjectConstructor; }; es: EventSource | undefined; url: string | undefined; readonly topicValue: string; readonly topicsValue: string[]; + readonly eventSourceOptionsValue: object; readonly hubValue: string; readonly hasHubValue: boolean; readonly hasTopicValue: boolean; diff --git a/src/Turbo/assets/dist/turbo_stream_controller.js b/src/Turbo/assets/dist/turbo_stream_controller.js index 3d55567c772..f4f8b9f3852 100644 --- a/src/Turbo/assets/dist/turbo_stream_controller.js +++ b/src/Turbo/assets/dist/turbo_stream_controller.js @@ -23,7 +23,7 @@ class default_1 extends Controller { } connect() { if (this.url) { - this.es = new EventSource(this.url); + this.es = new EventSource(this.url, this.eventSourceOptionsValue); connectStreamSource(this.es); } } @@ -38,6 +38,7 @@ default_1.values = { topic: String, topics: Array, hub: String, + eventSourceOptions: Object, }; export { default_1 as default }; diff --git a/src/Turbo/assets/src/turbo_stream_controller.ts b/src/Turbo/assets/src/turbo_stream_controller.ts index 4c8fd4d915a..97bb876ad5a 100644 --- a/src/Turbo/assets/src/turbo_stream_controller.ts +++ b/src/Turbo/assets/src/turbo_stream_controller.ts @@ -18,12 +18,14 @@ export default class extends Controller { topic: String, topics: Array, hub: String, + eventSourceOptions: Object, }; es: EventSource | undefined; url: string | undefined; declare readonly topicValue: string; declare readonly topicsValue: string[]; + declare readonly eventSourceOptionsValue: object; declare readonly hubValue: string; declare readonly hasHubValue: boolean; declare readonly hasTopicValue: boolean; @@ -50,7 +52,7 @@ export default class extends Controller { connect() { if (this.url) { - this.es = new EventSource(this.url); + this.es = new EventSource(this.url, this.eventSourceOptionsValue); connectStreamSource(this.es); } } diff --git a/src/Turbo/config/services.php b/src/Turbo/config/services.php index 0cd1e6a1d5f..25f9b36a517 100644 --- a/src/Turbo/config/services.php +++ b/src/Turbo/config/services.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\Component\Mercure\Authorization; use Symfony\UX\Turbo\Broadcaster\BroadcasterInterface; use Symfony\UX\Turbo\Broadcaster\IdAccessor; use Symfony\UX\Turbo\Broadcaster\ImuxBroadcaster; @@ -45,7 +46,12 @@ ->decorate('turbo.broadcaster.imux') ->set('turbo.twig.extension', TwigExtension::class) - ->args([tagged_locator('turbo.renderer.stream_listen', 'transport'), abstract_arg('default')]) + ->args([ + tagged_locator('turbo.renderer.stream_listen', 'transport'), + abstract_arg('default'), + service(Authorization::class)->nullOnInvalid(), + service('request_stack')->nullOnInvalid(), + ]) ->tag('twig.extension') ->set('turbo.doctrine.event_listener', BroadcastListener::class) diff --git a/src/Turbo/src/Bridge/Mercure/TurboStreamListenRenderer.php b/src/Turbo/src/Bridge/Mercure/TurboStreamListenRenderer.php index 68eadd82079..04c67a67a78 100644 --- a/src/Turbo/src/Bridge/Mercure/TurboStreamListenRenderer.php +++ b/src/Turbo/src/Bridge/Mercure/TurboStreamListenRenderer.php @@ -42,8 +42,12 @@ public function __construct( $this->stimulusHelper = $stimulus; } - public function renderTurboStreamListen(Environment $env, $topic): string + public function renderTurboStreamListen(Environment $env, $topic, /* array $eventSourceOptions = []*/): string { + if (func_num_args() > 2) { + $eventSourceOptions = func_get_arg(2); + } + $topics = $topic instanceof TopicSet ? array_map($this->resolveTopic(...), $topic->getTopics()) : [$this->resolveTopic($topic)]; @@ -55,6 +59,10 @@ public function renderTurboStreamListen(Environment $env, $topic): string $controllerAttributes['topic'] = current($topics); } + if ($eventSourceOptions) { + $controllerAttributes['eventSourceOptions'] = $eventSourceOptions; + } + $stimulusAttributes = $this->stimulusHelper->createStimulusAttributes(); $stimulusAttributes->addController( 'symfony/ux-turbo/mercure-turbo-stream', diff --git a/src/Turbo/src/Twig/TurboStreamListenRendererInterface.php b/src/Turbo/src/Twig/TurboStreamListenRendererInterface.php index 3670e40bd28..267bed5b01c 100644 --- a/src/Turbo/src/Twig/TurboStreamListenRendererInterface.php +++ b/src/Turbo/src/Twig/TurboStreamListenRendererInterface.php @@ -21,7 +21,8 @@ interface TurboStreamListenRendererInterface { /** - * @param string|object $topic + * @param string|object $topic + * @param array $eventSourceOptions */ - public function renderTurboStreamListen(Environment $env, $topic): string; + public function renderTurboStreamListen(Environment $env, $topic /* array $eventSourceOptions */): string; } diff --git a/src/Turbo/src/Twig/TwigExtension.php b/src/Turbo/src/Twig/TwigExtension.php index b44d993139f..733752954b7 100644 --- a/src/Turbo/src/Twig/TwigExtension.php +++ b/src/Turbo/src/Twig/TwigExtension.php @@ -13,18 +13,23 @@ use Psr\Container\ContainerInterface; use Symfony\UX\Turbo\Bridge\Mercure\TopicSet; +use Symfony\Component\Mercure\Authorization; +use Symfony\Component\HttpFoundation\RequestStack; use Twig\Environment; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; /** * @author Kévin Dunglas + * @author Pierre Ambroise */ final class TwigExtension extends AbstractExtension { public function __construct( private ContainerInterface $turboStreamListenRenderers, private string $default, + private ?Authorization $authorization = null, + private ?RequestStack $requestStack = null, ) { } @@ -37,8 +42,9 @@ public function getFunctions(): array /** * @param object|string|array $topic + * @param array $options */ - public function turboStreamListen(Environment $env, $topic, ?string $transport = null): string + public function turboStreamListen(Environment $env, $topic, ?string $transport = null, array $options = []): string { $transport ??= $this->default; @@ -50,6 +56,23 @@ public function turboStreamListen(Environment $env, $topic, ?string $transport = $topic = new TopicSet($topic); } - return $this->turboStreamListenRenderers->get($transport)->renderTurboStreamListen($env, $topic); + if ( + null !== $this->authorization && + null !== $this->requestStack && + (isset($options['subscribe']) || isset($options['publish']) || isset($options['additionalClaims'])) && + null !== $request = method_exists($this->requestStack, 'getMainRequest') ? $this->requestStack->getMainRequest() : $this->requestStack->getMasterRequest() + ) { + $this->authorization->setCookie( + $request, + $options['subscribe'] ?? [], + $options['publish'] ?? [], + $options['additionalClaims'] ?? [], + $transport, + ); + + unset($options['subscribe'], $options['publish'], $options['additionalClaims']); + } + + return $this->turboStreamListenRenderers->get($transport)->renderTurboStreamListen($env, $topic, $options); } } diff --git a/src/Turbo/tests/Bridge/Mercure/TurboStreamListenRendererTest.php b/src/Turbo/tests/Bridge/Mercure/TurboStreamListenRendererTest.php index 9b19ba4db09..1bb5bea1592 100644 --- a/src/Turbo/tests/Bridge/Mercure/TurboStreamListenRendererTest.php +++ b/src/Turbo/tests/Bridge/Mercure/TurboStreamListenRendererTest.php @@ -71,5 +71,14 @@ public static function provideTestCases(): iterable ? 'data-controller="symfony--ux-turbo--mercure-turbo-stream" data-symfony--ux-turbo--mercure-turbo-stream-hub-value="http://127.0.0.1:3000/.well-known/mercure" data-symfony--ux-turbo--mercure-turbo-stream-topics-value="["a_topic","AppEntityBook","https:\/\/symfony.com\/ux-turbo\/App%5CEntity%5CBook\/123"]"' : 'data-controller="symfony--ux-turbo--mercure-turbo-stream" data-symfony--ux-turbo--mercure-turbo-stream-hub-value="http://127.0.0.1:3000/.well-known/mercure" data-symfony--ux-turbo--mercure-turbo-stream-topics-value="["a_topic","AppEntityBook","https:\/\/symfony.com\/ux-turbo\/App%5CEntity%5CBook\/123"]"', ]; + + yield [ + "{{ turbo_stream_listen('a_topic', 'default', { withCredentials: true }) }}", + [], + $newEscape + ? 'data-controller="symfony--ux-turbo--mercure-turbo-stream" data-symfony--ux-turbo--mercure-turbo-stream-hub-value="http://127.0.0.1:3000/.well-known/mercure" data-symfony--ux-turbo--mercure-turbo-stream-topic-value="a_topic" data-symfony--ux-turbo--mercure-turbo-stream-event-source-options-value="{"withCredentials":true}"' + : 'data-controller="symfony--ux-turbo--mercure-turbo-stream" data-symfony--ux-turbo--mercure-turbo-stream-hub-value="http://127.0.0.1:3000/.well-known/mercure" data-symfony--ux-turbo--mercure-turbo-stream-topic-value="a_topic" data-symfony--ux-turbo--mercure-turbo-stream-event-source-options-value="{"withCredentials":true}"', + + ]; } }