diff --git a/src/Turbo/CHANGELOG.md b/src/Turbo/CHANGELOG.md index 5a0f7d0f4f5..7fcf5b1cd91 100644 --- a/src/Turbo/CHANGELOG.md +++ b/src/Turbo/CHANGELOG.md @@ -6,6 +6,7 @@ - Add `` component - Add support for custom actions in `TurboStream` and `TurboStreamResponse` - Add support for providing multiple mercure topics to `turbo_stream_listen` +- Add support for providing options to the EventSource via `turbo_stream_listen` ## 2.21.0 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..01636c673bb 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 (isset($eventSourceOptions) && !empty($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..240721317f1 100644 --- a/src/Turbo/src/Twig/TurboStreamListenRendererInterface.php +++ b/src/Turbo/src/Twig/TurboStreamListenRendererInterface.php @@ -23,5 +23,5 @@ interface TurboStreamListenRendererInterface /** * @param string|object $topic */ - 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..448c0a3a105 100644 --- a/src/Turbo/src/Twig/TwigExtension.php +++ b/src/Turbo/src/Twig/TwigExtension.php @@ -12,6 +12,8 @@ namespace Symfony\UX\Turbo\Twig; use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Mercure\Authorization; use Symfony\UX\Turbo\Bridge\Mercure\TopicSet; use Twig\Environment; use Twig\Extension\AbstractExtension; @@ -19,12 +21,15 @@ /** * @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,24 @@ 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'])) + // @phpstan-ignore-next-line + && 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..8eef3066ce1 100644 --- a/src/Turbo/tests/Bridge/Mercure/TurboStreamListenRendererTest.php +++ b/src/Turbo/tests/Bridge/Mercure/TurboStreamListenRendererTest.php @@ -71,5 +71,13 @@ 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}"', + ]; } }