2222use Silverback \ApiComponentsBundle \Helper \Publishable \PublishableStatusChecker ;
2323use Symfony \Component \HttpFoundation \Cookie ;
2424use Symfony \Component \HttpKernel \Event \ResponseEvent ;
25- use Symfony \Component \Mercure \Jwt \ TokenFactoryInterface ;
25+ use Symfony \Component \Mercure \Authorization ;
2626use Symfony \Component \Routing \RequestContext ;
2727
2828class AddMercureTokenListener
2929{
3030 use CorsTrait;
3131
32- public function __construct (private TokenFactoryInterface $ tokenFactory , private ResourceNameCollectionFactoryInterface $ resourceNameCollectionFactory , private ResourceMetadataCollectionFactoryInterface $ resourceMetadataCollectionFactory , private PublishableStatusChecker $ publishableStatusChecker , private RequestContext $ requestContext )
33- {
32+ public function __construct (
33+ private readonly ResourceNameCollectionFactoryInterface $ resourceNameCollectionFactory ,
34+ private readonly ResourceMetadataCollectionFactoryInterface $ resourceMetadataCollectionFactory ,
35+ private readonly PublishableStatusChecker $ publishableStatusChecker ,
36+ private readonly RequestContext $ requestContext ,
37+ private readonly Authorization $ mercureAuthorization ,
38+ private readonly string $ cookieSameSite = Cookie::SAMESITE_STRICT ,
39+ private readonly ?string $ hubName = null
40+ ) {
3441 }
3542
3643 /**
@@ -47,47 +54,66 @@ public function onKernelResponse(ResponseEvent $event): void
4754
4855 $ subscribeIris = [];
4956 $ response = $ event ->getResponse ();
50-
5157 foreach ($ this ->resourceNameCollectionFactory ->create () as $ resourceClass ) {
52- $ resourceMetadataCollection = $ this ->resourceMetadataCollectionFactory ->create ($ resourceClass );
53-
54- try {
55- $ operation = $ resourceMetadataCollection ->getOperation (forceCollection: false , httpOperation: true );
56- } catch (OperationNotFoundException $ e ) {
57- continue ;
58+ if ($ resourceIris = $ this ->getSubscribeIrisForResource ($ resourceClass )) {
59+ $ subscribeIris [] = $ resourceIris ;
5860 }
61+ }
62+ $ subscribeIris = array_merge ([], ...$ subscribeIris );
5963
60- if (!$ operation instanceof HttpOperation) {
61- continue ;
62- }
64+ // Todo: await merge of https://github.com/symfony/mercure/pull/93 to remove ability to publish any updates and set to null
65+ // May also be able to await a mercure bundle update to set the cookie samesite in mercure configs
66+ $ cookie = $ this ->mercureAuthorization ->createCookie ($ request , $ request , $ subscribeIris , [], $ this ->hubName );
67+ $ cookie ->withSameSite ($ this ->cookieSameSite );
68+ $ response ->headers ->setCookie ($ cookie );
69+ }
6370
64- $ mercure = $ operation ->getMercure ();
71+ private function getSubscribeIrisForResource (string $ resourceClass ): ?array
72+ {
73+ $ operation = $ this ->getMercureResourceOperation ($ resourceClass );
74+ if (!$ operation ) {
75+ return null ;
76+ }
6577
66- if (!$ mercure ) {
67- continue ;
68- }
78+ $ refl = new \ReflectionClass ($ operation ->getClass ());
79+ $ isPublishable = \count ($ refl ->getAttributes (Publishable::class));
6980
70- $ refl = new \ ReflectionClass ( $ operation ->getClass () );
71- $ isPublishable = \count ( $ refl -> getAttributes (Publishable::class)) ;
81+ $ uriTemplate = $ this -> buildAbsoluteUriTemplate () . $ operation -> getRoutePrefix () . $ operation ->getUriTemplate ( );
82+ $ subscribeIris = [ $ uriTemplate ] ;
7283
73- $ uriTemplate = $ this ->buildAbsoluteUriTemplate () . $ operation ->getRoutePrefix () . $ operation ->getUriTemplate ();
84+ if (!$ isPublishable ) {
85+ return $ subscribeIris ;
86+ }
7487
75- if (! $ isPublishable ) {
76- $ subscribeIris [] = $ uriTemplate ;
77- continue ;
78- }
88+ // Note that `?draft=1` is also hard coded into the PublishableIriConverter, probably make this configurable somewhere
89+ if ( $ this -> publishableStatusChecker -> isGranted ( $ operation -> getClass ())) {
90+ $ subscribeIris [] = $ uriTemplate . ' ?draft=1 ' ;
91+ }
7992
80- // Note that `?draft=1` is also hard coded into the PublishableIriConverter, probably make this configurable somewhere
81- if ($ this ->publishableStatusChecker ->isGranted ($ operation ->getClass ())) {
82- $ subscribeIris [] = $ uriTemplate . '?draft=1 ' ;
83- $ subscribeIris [] = $ uriTemplate ;
84- continue ;
85- }
93+ return $ subscribeIris ;
94+ }
95+
96+ private function getMercureResourceOperation (string $ resourceClass ): ?HttpOperation
97+ {
98+ $ resourceMetadataCollection = $ this ->resourceMetadataCollectionFactory ->create ($ resourceClass );
99+
100+ try {
101+ $ operation = $ resourceMetadataCollection ->getOperation (forceCollection: false , httpOperation: true );
102+ } catch (OperationNotFoundException $ e ) {
103+ return null ;
104+ }
105+
106+ if (!$ operation instanceof HttpOperation) {
107+ return null ;
108+ }
109+
110+ $ mercure = $ operation ->getMercure ();
86111
87- $ subscribeIris [] = $ uriTemplate ;
112+ if (!$ mercure ) {
113+ return null ;
88114 }
89115
90- $ response -> headers -> setCookie (Cookie:: create ( ' mercureAuthorization ' , $ this -> tokenFactory -> create ( $ subscribeIris , []))) ;
116+ return $ operation ;
91117 }
92118
93119 /**
0 commit comments