Skip to content

[4.x] BroadcastingConfigBootstrapper: fix central instances persisting in tenant context#1448

Open
lukinovec wants to merge 23 commits intomasterfrom
broadcasting-fixes
Open

[4.x] BroadcastingConfigBootstrapper: fix central instances persisting in tenant context#1448
lukinovec wants to merge 23 commits intomasterfrom
broadcasting-fixes

Conversation

@lukinovec
Copy link
Copy Markdown
Contributor

@lukinovec lukinovec commented Mar 31, 2026

Broadcasting auth route problem (Broadcast facade always uses central BroadcastManager)

Note: Tested with Pusher and Reverb

When using BroadcastingConfigBootstrapper for broadcasting on private channels with multiple broadcasting apps (= each tenant has its own Pusher/Reverb/... app and credentials), the auth requests sent to the broadcasting server use the central broadcasting credentials. This is because the Broadcast facade (used in BroadcastController::authenticate like Broadcast::auth($request) to authorize the channels and then retrieve the auth key that's then sent to the broadcasting server) keeps using the BroadcastManager instance resolved in the central context instead of TenancyBroadcastManager. This is happening because calling withBroadcasting() in bootstrap/app.php results in calling Broadcast::routes() in the central context.

Clearing the facade's resolved Illuminate\Contracts\Broadcasting\Factory instance in BroadcastingConfigBootstrapper::bootstrap() forces the facade to re-resolve Factory (= BroadcastManager) on the next use, so the next Broadcast::auth() call uses TenantBroadcastManager in the tenant context, and the credentials from the current config will be used for authentication.

TenancyBroadcastManager doesn't inherit custom driver creators

When registering a custom driver creator (app(BroadcastManager::class)->extend('custom-driver', fn($app, $config) => new CustomBroadcaster(...))) in the central context (e.g. in BroadcastServiceProvider/AppServiceProvider, or in our case, in the tests), the creator won't be available in the tenant context. Calling e.g. app(BroadcastManager::class)->driver('custom-driver') in the tenant context (if the creator was registered only in the central context) would throw InvalidArgumentException ("Driver [custom-driver] is not supported.").

To fix that, register the original (central) BroadcastManager's custom creators in the new TenancyBroadcastManager in BroadcastingConfigBootstrapper::bootstrap().

Central Broadcaster instance bound in tenant context

Resolving Illuminate\Contracts\Broadcasting\Broadcaster::class in the tenant context returns a Broadcaster instance from the central context. Currently, this doesn't cause any issues, but it could be a problem when using DI/resolving Broadcaster.

Fixed by making Illuminate\Contracts\Broadcasting\Broadcaster::class resolve to the driver of the current BroadcastManager instance (which is TenancyBroadcastManager in the tenant context) by using app->extend().

But there's a catch: updating credentials in the config while in tenant context won't be reflected on the bound Broadcaster instance. To make that work (with direct config updates), we'd have to use bind() instead of extend(), but then, we wouldn't be able to resolve the original broadcaster in TenancyBroadcastManager::get() (so that we can pass its channels to the new broadcaster). We'd have to pass the original broadcaster to TenancyBroadcastManager, and for that, we'd have to override the manager's constructor, which doesn't seem worth, since this is a pretty niche use case and simply reinitializing tenancy fixes the problem anyway.

So for cases like sending a notification in response to updating tenant's broadcasting credentials in tenant context, it's recommended to reinitialize tenancy after updating the credentials.

Credential map: overriding presets (BroadcastingConfigBootstrapper)

Previously, credential mappings from $mapPresets overrode mappings defined in $credentialsMap. If someone used e.g. Pusher and wanted to override some of that preset's mappings, e.g. use 'pusher_app_key' instead of 'pusher_key' by specifying 'pusher_app_key' in $credentialsMap, the preset's mapping ('pusher_key') would still be used.

Fixed that by reversing the array_merge() order in BroadcastingConfigBootstrapper::__construct().

Tests

Deleted the BroadcastingTest file, moved the tests to appropriate bootstrapper test files.

Added tests for mapping tenant properties to broadcaster credentials. Also added tests for the changes mentioned above and improved the existing tests.

To-dos

  • Add 'reverb' to TenancyBroadcastManager::$tenantBroadcasters (add it everywhere where the prop is modified to make the tests pass)

lukinovec and others added 9 commits March 31, 2026 15:32
Test that BroadcastingConfigBootstrapper correctly maps tenant properties to broadcasting config/credentials, and that the credentials don't leak when switching contexts. Also add the `$config` property to `TestingBroadcaster` so that we can access the credentials used by the broadcaster.
…ators

Test that TenancyBroadcastManager inherits the custom driver creators from the central BroadcastManager.
…ed `Broadcasting\Factory` instance

After initializing tenancy, calls like `Broadcast::auth()` use the central `BroadcastManager`. Clearing the facade's resolved `Broadcasting\Factory` instance fixes that problem.
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 31, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.12%. Comparing base (fb654e7) to head (f8528fc).

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1448      +/-   ##
============================================
+ Coverage     86.05%   86.12%   +0.06%     
- Complexity     1152     1153       +1     
============================================
  Files           184      184              
  Lines          3371     3379       +8     
============================================
+ Hits           2901     2910       +9     
+ Misses          470      469       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@lukinovec lukinovec marked this pull request as ready for review March 31, 2026 16:03
@lukinovec lukinovec marked this pull request as draft March 31, 2026 16:04
lukinovec and others added 6 commits April 1, 2026 15:50
Moved binding `Broadcaster` to the bootstrapper.
Test that the bound Broadcaster instance inherits the channels too. Also test that the channels aren't lost when switching context to another tenant.
Tests from BroadcastingTest moved to the appropriate bootstrapper test files. The new tenant credentials test has assertions equal to both the original property -> config mapping test and the config -> credentials test.
@lukinovec lukinovec marked this pull request as ready for review April 2, 2026 14:54
Tests now use datasets with all drivers that are in `TenancyBroadcastManager::$tenantBroadcasters` by default plus the custom driver. Also add assertions for updating the tenant properties/config in tenant context.
… order

Previously, credential mappings from `$mapPresets` overrode mappings defined in `$credentialsMap`. If someone used pusher/reverb/ably and wanted to override some of that preset's mappings, e.g. use 'pusher_app_key' instead of 'pusher_key' by specifying 'pusher_app_key' in `$credentialsMap`, the preset's mapping ('pusher_key') would still be used.
Adding 'reverb' to `TenancyBroadcastManager::$tenantBroadcasters` will make these tests pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant