archtechx / tenancy

Automatic multi-tenancy for Laravel. No code changes needed.
https://tenancyforlaravel.com
MIT License
3.66k stars 429 forks source link

Pusher Broadcast not recognising tenant #208

Closed radmanz closed 5 years ago

radmanz commented 5 years ago

I am currenly blocked on how to resolve a Pusher broadcast exception i am receiving. We use laravel-websockets to push messages to users. When creating a broadcast event an exception TenantCouldNotBeIdentifiedException is raised (see below). It's almost as if the broadcast event is executed and a request is is generated internally, causing the tenant domain to be lost, hence reporting 127.0.0.1 as the tenant? Does any one have any ideas as to how this can be resolved? Many thanks in advance 😎

The stack trace is as follows.

Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedException: Tenant could not be identified on domain 127.0.0.1 in file /home/vagrant/code/vendor/stancl/tenancy/src/StorageDrivers/Database/DatabaseStorageDriver.php on line 55

#0 /home/vagrant/code/vendor/stancl/tenancy/src/TenantManager.php(198): Stancl\Tenancy\StorageDrivers\Database\DatabaseStorageDriver->findByDomain('127.0.0.1')
#1 /home/vagrant/code/vendor/stancl/tenancy/src/TenantManager.php(159): Stancl\Tenancy\TenantManager->findByDomain('127.0.0.1')
#2 /home/vagrant/code/vendor/stancl/tenancy/src/Middleware/InitializeTenancy.php(37): Stancl\Tenancy\TenantManager->init('127.0.0.1')
#3 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(171): Stancl\Tenancy\Middleware\InitializeTenancy->handle(Object(Illuminate\Http\Request), Object(Closure))
#4 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(105): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#5 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(151): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#6 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(116): Illuminate\Foundation\Http\Kernel->sendRequestThroughRouter(Object(Illuminate\Http\Request))
#7 /home/vagrant/code/public/index.php(53): Illuminate\Foundation\Http\Kernel->handle(Object(Illuminate\Http\Request))
#8 {main}
 Illuminate\Broadcasting\BroadcastException: 

Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedException: Tenant could not be identified on domain 127.0.0.1 in file /home/vagrant/code/vendor/stancl/tenancy/src/StorageDrivers/Database/DatabaseStorageDriver.php on line 55

#0 /home/vagrant/code/vendor/stancl/tenancy/src/TenantManager.php(198): Stancl\Tenancy\StorageDrivers\Database\DatabaseStorageDriver->findByDomain('127.0.0.1')
#1 /home/vagrant/code/vendor/stancl/tenancy/src/TenantManager.php(159): Stancl\Tenancy\TenantManager->findByDomain('127.0.0.1')
#2 /home/vagrant/code/vendor/stancl/tenancy/src/Middleware/InitializeTenancy.php(37): Stancl\Tenancy\TenantManager->init('127.0.0.1')
#3 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(171): Stancl\Tenancy\Middleware\InitializeTenancy->handle(Object(Illuminate\Http\Request), Object(Closure))
#4 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(105): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#5 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(151): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#6 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(116): Illuminate\Foundation\Http\Kernel->sendRequestThroughRouter(Object(Illuminate\Http\Request))
#7 /home/vagrant/code/public/index.php(53): Illuminate\Foundation\Http\Kernel->handle(Object(Illuminate\Http\Request))
#8 {main}

 in file /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php on line 119

#0 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastEvent.php(64): Illuminate\Broadcasting\Broadcasters\PusherBroadcaster->broadcast(Array, 'App\\Events\\Modu...', Array)
#1 [internal function]: Illuminate\Broadcasting\BroadcastEvent->handle(Object(Illuminate\Broadcasting\Broadcasters\PusherBroadcaster))
#2 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(32): call_user_func_array(Array, Array)
#3 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Support/helpers.php(522): Illuminate\Container\BoundMethod::Illuminate\Container\{closure}()
#4 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(90): value(Object(Closure))
#5 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(34): Illuminate\Container\BoundMethod::callBoundMethod(Object(Illuminate\Foundation\Application), Array, Object(Closure))
#6 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Container/Container.php(591): Illuminate\Container\BoundMethod::call(Object(Illuminate\Foundation\Application), Array, Array, NULL)
#7 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(94): Illuminate\Container\Container->call(Array)
#8 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(130): Illuminate\Bus\Dispatcher->Illuminate\Bus\{closure}(Object(Illuminate\Broadcasting\BroadcastEvent))
#9 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(105): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Broadcasting\BroadcastEvent))
#10 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(98): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#11 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(83): Illuminate\Bus\Dispatcher->dispatchNow(Object(Illuminate\Broadcasting\BroadcastEvent), false)
#12 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(130): Illuminate\Queue\CallQueuedHandler->Illuminate\Queue\{closure}(Object(Illuminate\Broadcasting\BroadcastEvent))
#13 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(105): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Broadcasting\BroadcastEvent))
#14 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(85): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#15 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(59): Illuminate\Queue\CallQueuedHandler->dispatchThroughMiddleware(Object(Illuminate\Queue\Jobs\SyncJob), Object(Illuminate\Broadcasting\BroadcastEvent))
#16 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php(88): Illuminate\Queue\CallQueuedHandler->call(Object(Illuminate\Queue\Jobs\SyncJob), Array)
#17 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Queue/SyncQueue.php(42): Illuminate\Queue\Jobs\Job->fire()
#18 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Queue/Queue.php(44): Illuminate\Queue\SyncQueue->push(Object(Illuminate\Broadcasting\BroadcastEvent), '', NULL)
#19 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastManager.php(128): Illuminate\Queue\Queue->pushOn(NULL, Object(Illuminate\Broadcasting\BroadcastEvent))
#20 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php(267): Illuminate\Broadcasting\BroadcastManager->queue(Object(App\Events\ModulePacketEvent))
#21 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php(190): Illuminate\Events\Dispatcher->broadcastEvent(Object(App\Events\ModulePacketEvent))
#22 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php(482): Illuminate\Events\Dispatcher->dispatch('App\\Events\\Modu...')
#23 /home/vagrant/code/app/Http/Controllers/API/Module/ModuleApiController.php(79): event(Object(App\Events\ModulePacketEvent))
#24 [internal function]: App\Http\Controllers\Api\Module\ModuleApiController->moduleEventSave(Array)
#25 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): call_user_func_array(Array, Array)
stancl commented 5 years ago

How did you resolve this @radmanz?

radmanz commented 5 years ago

Hey @stancl I have found that this second request that is being created (as described above) should have been making a request to the web socket service (php artisan websockets:serve).

To get everything working on a tenant level it should be noted that

  1. within config/broadcasting, your connections.pusher.options.host and .port settings must be set to the websockets server spawned by artisan, in my case it was 127.0.0.1:6001. If you are running a single instance of the ws server via kuberneties or similar, you can use one server for multiple tenants if configured correctly.
  2. Ensure that your connections.pusher.options.secret, .key, and .app_id are unique to the tenant also by setting these values within your broadcasting code. I stored these values within the tenant custom properties. This will make sure that all messages sent from the tenants laravel instance to the websocket service will contain the approprate broadcast destination (tenant users).
  3. Ensure your tenants frontend connects with the tenant specific key and secret defined in point 2 above. This will ensure that when a laravel tenant broadcasts a message to the ws service, it will broadcast to the defined ws client due to matching key and secret values.
wammy21 commented 4 years ago

Hello, not sure if I should re-open this issue or create a new one but I am having a bit of trouble getting this broadcasting with pusher working on tenants.

I have the central app pusher config set via ENV on the deployment config and with proper entries in the config file:

'connections' => [

        'pusher' => [
            'driver' => 'pusher',
            'key' => env('PUSHER_APP_KEY'),
            'secret' => env('PUSHER_APP_SECRET'),
            'app_id' => env('PUSHER_APP_ID'),
            'options' => [
                'cluster' => env('PUSHER_APP_CLUSTER'),
                'useTLS' => true,
            ],
        ],

I am using Laravel Echo and I am sure that the correct values are being setup:

Echo = new window.LaravelEcho({
                broadcaster: 'pusher',
                key: '{{config('broadcasting.connections.pusher.key')}}',
                cluster: '{{config('broadcasting.connections.pusher.options.cluster')}}',
                forceTLS: true
            });

First issue was being redirected to /app when the pusher client tried going to /broadcast/auth, I solved that by setting the middleware to the Broadcast::routes call in my service provider:

 Broadcast::routes(['middleware' => ['tenancy','web']]);

That seems to resolve the authentication issue for any tenants using the default pusher config.

However each tenant should have their own pusher config, so following the above I setup the storage_to_config_map as such:

'storage_to_config_map' => [ // Used by the TenantConfig feature
        'app_name'=>'app.name',
        'pusher_key'=>'broadcasting.connections.pusher.key',
        'pusher_secret'=>'broadcasting.connections.pusher.secret',
        'pusher_app_id'=>'broadcasting.connections.pusher.app_id',
        'pusher_cluster'=>'broadcasting.connections.pusher.options.cluster',
    ],

I have two 'tenants':

[Tenant] id: 9653e0fd-3147-465c-98c4-bfa7e12ace8d @ bar.localhost
[Tenant] id: fd20fafc-ef76-48f3-b086-21ccd7c19030 @ foo.localhost

and then set the values via (for testing):

tenancy()->findByDomain('foo.localhost')->put(['pusher_key'=>'xxx','pusher_secret'=>'xxx','pusher_app_id'=>'xxx','pusher_cluster'=>'us3']);

which is (as I understand) set correctly:

>>> tenancy()->findByDomain('foo.localhost');
=> Stancl\Tenancy\Tenant {#3576
     +data: [
       "id" => "fd20fafc-ef76-48f3-b086-21ccd7c19030",
       "plan" => "free",
       "is_trial" => 0,
       "trial_start" => null,
       "owner" => 0,
       "pusher_key" => "xxx",
       "pusher_app_id" => "xxx",
       "pusher_secret" => "xxx",
       "pusher_cluster" => "us3",
     ],
     +domains: [
       "foo.localhost",
     ],
     +persisted: true,
   }
>>>

First issue: I have a command to test sending the notification, when I call it via:

#send notification to user#1 on foo.localhost
php artisan tenant:run user:test-notification --tenants=fd20fafc-ef76-48f3-b086-21ccd7c19030 --argument="user_id=1" --argument="via=broadcast" 

The notification is being sent via the default (central) pusher config and the browser window on bar.localhost gets the notification.

Second, The Echo pusher client connection seems to open properly to the correct cluster using the correct key, but I am having issues with the client authenticating (for private channels). It seems /broadcast/auth is returning the incorrect key in its response.

{"auth":"app-key-from-central-config:xxxx"}

I am not sure if this is an issue with this package, or with the way Broadcasting is implemented, I did some digging into the BroadcastManager code and the Pusher Broadcast driver, it seems perhaps they're loading the config in the service provider once and not after the middleware has switched to the tenant environment.

stancl commented 4 years ago

Hi,

Regarding the first issue. Could you try making it an explicitly tenant-aware command?

Regarding the second issue, I can't really help much because I've never used broadcasting, so it would take me a long dig through that code, but it does seem like the config is loaded once, before tenancy is initialized (most likely a dependency is injected when the app is still in the central environment). It might be useful to look at the BroadcastingServiceProvider (https://github.com/laravel/framework/blob/6.x/src/Illuminate/Broadcasting/BroadcastServiceProvider.php#L24) and see what things are persisted, like the $drivers property on BroadcastManager.

wammy21 commented 4 years ago

Hi @stancl I've implemented the command as a tenant-aware command and it seems the issue still persists.

I think the issue is going to be in the way the BroadcastServiceProvider persists the config. Not sure how to move forward or how feasible it would be to make something work here.

a-ssassi-n commented 3 years ago

@wammy21 did you solve this issue?

juanxodj commented 1 year ago

@wammy21 did you solve this issue?

I'm still having the same problem.

stancl commented 1 year ago

Tenancy v4 has a dedicated bootstrapper for broadcasting tenancy.