@@ -91,27 +91,11 @@ public function bootstrap(Tenant $tenant): void
9191 // contract gets the same tenant broadcaster that the manager uses, instead of the stale central one.
9292 // The closure runs immediately (the extended singleton is already resolved), and it's also what makes
9393 // channel auth work in tenant context -- the broadcaster resolved here gets cached as the tenant
94- // manager's default driver and receives the central channel auth closures (see below ).
94+ // manager's default driver and receives the central broadcaster's auth state (see copyAuthState() ).
9595 $ this ->app ->extend (BroadcasterContract::class, function (BroadcasterContract $ centralBroadcaster ) {
9696 $ tenantBroadcaster = $ this ->app ->make (BroadcastManager::class)->connection ();
9797
98- // The newly resolved broadcaster doesn't have any channel auth closures registered, so the
99- // closures registered in central context (e.g. in routes/channels.php) have to be passed to it
100- // manually, otherwise, Broadcast::auth() would throw a 403 for those channels.
101- // Since Laravel only ever uses the default broadcaster's channel auth closures for broadcasting auth,
102- // we only have to pass the channel closures to the default broadcaster.
103- //
104- // The channel() and getChannels() methods aren't part of the Broadcaster contract -- they come
105- // from the abstract Broadcaster class, so the closures can only be copied between broadcasters extending it
106- // (which all of Laravel's default broadcasters, e.g. PusherBroadcaster, do).
107- if ($ centralBroadcaster instanceof Broadcaster && $ tenantBroadcaster instanceof Broadcaster) {
108- // invade() because the channel options can't be retrieved through any of the broadcaster's public methods
109- $ channelOptions = invade ($ centralBroadcaster )->channelOptions ;
110-
111- foreach ($ centralBroadcaster ->getChannels () as $ channel => $ callback ) {
112- $ tenantBroadcaster ->channel ($ channel , $ callback , $ channelOptions [$ channel ] ?? []);
113- }
114- }
98+ $ this ->copyAuthState ($ centralBroadcaster , $ tenantBroadcaster );
11599
116100 return $ tenantBroadcaster ;
117101 });
@@ -122,6 +106,32 @@ public function bootstrap(Tenant $tenant): void
122106 Broadcast::clearResolvedInstance ();
123107 }
124108
109+ /**
110+ * Copy the auth state (the channel auth closures, their options, and the authenticated user
111+ * callback) from one broadcaster to another. A freshly resolved broadcaster has no auth state,
112+ * so without the copying, channel auth and user auth would stop working (403) in tenant context.
113+ *
114+ * The auth state is stored on the abstract Broadcaster class, not in the Broadcaster
115+ * contract, and it's stored in protected properties. Because of that, we have
116+ * to check that both broadcasters are instances of the abstract Broadcaster class and
117+ * use invade() to access the protected properties (for the $channels property, there
118+ * is a public accessor -- getChannels() -- but since invade is already used here,
119+ * we access the property directly for consistency).
120+ */
121+ protected function copyAuthState (BroadcasterContract $ from , BroadcasterContract $ to ): void
122+ {
123+ if (! $ from instanceof Broadcaster || ! $ to instanceof Broadcaster) {
124+ return ;
125+ }
126+
127+ $ fromState = invade ($ from );
128+ $ toState = invade ($ to );
129+
130+ $ toState ->channels = $ fromState ->channels ;
131+ $ toState ->channelOptions = $ fromState ->channelOptions ;
132+ $ toState ->authenticatedUserCallback = $ fromState ->authenticatedUserCallback ;
133+ }
134+
125135 public function revert (): void
126136 {
127137 // Revert the bound BroadcastManager and Broadcaster singletons back to their original state
0 commit comments