Laravel Broadcasting, how to auth to a presence channel? - php

I'm trying to join a presence channel (Public channels work well), but I can't get this to work:
Vue code:
mounted(){
Echo.join('game.' + "0").here((users) => {
alert("In the channel!");
})
.joining((user) => {
console.log("Someone entered");
})
.leaving((user) => {
console.log(user.name);
})
.listen('GameEvent', (e) => {
console.log("Hey")
});
Echo.channel('NewSentence')
.listen('NewSentence',(sentence)=>{
alert("HOLA");
});
}
I'm trying to join the channel "game.0". As I'm using Laravel Passport I need to authenticate myself with a token, and that is working. Sending the auth request for Laravel Echo returns a key, but the JavaScript events are not triggering .here(), .listening() ....
BroadcastService provider boot function:
public function boot() {
Broadcast::routes(["middleware" => "auth:api"]);
require base_path('routes/channels.php');
}
channels.php
Broadcast::channel('game.0', function ($user,$id) {
return ['id' => $user->id];
});
The auth route:
Route::post('/broadcasting/auth', function(Request $request){
$pusher = new Pusher\Pusher(
env('PUSHER_APP_KEY'),
env('PUSHER_APP_SECRET'),
env('PUSHER_APP_ID'),
array(
'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => false,
'host' => '127.0.0.1',
'port' => 6001,
'scheme' => 'http',
)
);
return $pusher->socket_auth($request->request->get('channel_name'),$request->request->get('socket_id'));
});
Do I need to do something extra to make it work? This is the auth request:
EDIT:
GameEvent event:
class GameEvent implements ShouldBroadcastNow {
use Dispatchable, InteractsWithSockets, SerializesModels;
public $gameEvent;
public $gameId;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($gameEvent, $gameId) {
//
$this->gameEvent = $gameEvent;
$this->gameId = $gameId;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\PresenceChannel|array
*/
public function broadcastOn() {
//return new PrivateChannel('channel-name');
return new PresenceChannel('game.0');
}
public function broadcastWith() {
return $this->gameEvent;
}
}
EDIT:
I've hardcoded the names: 'game.0' is now hardcoded in the routes/channels.php route, in the Echo connection and in the GameEvent. I also removed broadcastAs(). After entering the laravel-websockets debugging dashboard I found that the channel I want to subscribe doesn't even appear. It looks like it won't start a connection, but I can't figure out what it going on.
I hardcoded the

The problem here seems to be that the Echo is not listening on proper channel. First of all the Echo.join is using channel game.0 in which 0 is a user's id, and i don't think that there is actually a user with id 0. Secondly, you are broadcasting as
GameEvent
and Echo is connecting to channel named game.{id} I suggest that you either remove the broadcastAs() function from your event file or listen on GameEvent. Also use the websocket dashboard for testing this. The dashboard will be available at
/laravel-websockets
route automatically, which is available only for local environment so make sure that environment is local in your .env.
Use the debugging dashboard provided by laravel-websockets to send data to channels, first connect to your web socket within the dashboard then just enter the channel name, event name and data in JSON format and hit send on the dashboard.
Try finding out if that helps with resolving your problem.
I also recommend thoroughly reading laravel's official documentation on broadcasting as well as laravel-websockets debugging dashboard guide.
Also update what you got in result to this question.

Related

Laravel Websocket package issue with the latest Laravel, pusher, and Echo package

I am working on Laravel 9 where I install Laravel WebSocket, Laravel Echo, and Pusher PHP server.
By the way, I didn't use the official Pusher application, just using the package as per Laravel-WebSocket package documentation suggested.
User case - I want to update the site model value and send a notification (broadcast and mail) to the end user as soon as the user deletes a site.
Everything is installed and working fine but I found some glitches in the Laravel-WebSocket, Pusher package.
I have created the following event which will broadcast to the end user.
SiteDelete.php
<?php
namespace App\Events;
use App\Models\Site;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class SiteDeleted implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* The site instance.
*
* #var \App\Models\Site
*/
public $site;
/**
* The name of the queue connection to use when broadcasting the event.
*
* #var string
*/
public $connection = 'database';
/**
* The name of the queue on which to place the broadcasting job.
*
* #var string
*/
public $queue = 'default';
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Site $site)
{
$this->site = $site;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
// return new PrivateChannel('site.delete.'.$this->site->id); // this is not working.
// return [new PrivateChannel('site.delete.'.$this->site->id)]; // this is not working.
return [new PrivateChannel('site.delete.'.$this->site->id), new Channel('mock')]; // This will call but I need to pass two channels intentionally.
}
/**
* Get the data to broadcast.
*
* #return array
*/
public function broadcastWith()
{
return ['id' => $this->site->id];
}
}
app.js
Echo.private("site.delete.1")
.listen('SiteDeleted', (e) => {
console.log("SiteDeleted");
console.log(JSON.stringify(e));
})
Echo.private('App.Models.User.7')
.notification((notification) => {
console.log("App.Models.User");
console.log(notification);
});
Problem
As you can see comments in my event class's broadcastOn method where I need to pass two channels. One is real and the second one is fake. So ultimately you need to pass at least two channels so the pusher request will have a channels parameter [which will work] but the channel parameter never works[i.e when you pass a single channel].
I can able to send custom events from the WebSocket GUI. i.e from http://localhost:8000/laravel-websockets URL. but those events are never caught by the front end unless I do it the dirty way.
The notifications are never caught by the front end due to this channel and channels parameter issue.
Dirty Way[Yes I know we should never touch the vendor folder but just curious to know why the things are not working]
I checked the vendor folder very deeply and I come to know, in the vendor/pusher/pusher-php-server/src/Pusher.php under the make_event function if I update the following line then it starts working without passing two channels.
vendor/pusher/pusher-php-server/src/Pusher.php
private function make_event(array $channels, string $event, $data, array $params = [], ?string $info = null, bool $already_encoded = false): array
{
// if (count($channel_values) == 1) {
// $post_params['channel'] = $channel_values[0];
// } else {
// $post_params['channels'] = $channel_values;
// }
$post_params['channels'] = $channel_values;
}
My Research
As the WebSocket package suggests installing pusher-php-server version 3.0 but I install the latest one i.e 7. Version 3.0 is incompatible with Laravel 9. But I can't and don't want to install the older version.
I think the WebSocket package is not able to send the event and data on a single channel with a newer version of pusher-php-server.
I can't raise an issue (or blame it) for Pusher SDK because we are just replacing the package and I think the Pusher SDK package is working fine when you use their credentials(ie. you have to create an app on Pusher).
Even if you can check on the WebSocket dashboard i.e http://localhost:8000/laravel-websockets when you send the event it will never catch in the front end. But as soon as you update the Pusher.php file it starts catching an event on the front end.
due to the above reason, as you know the notification are sent to the user on their private channels, So I can't add a mock channel for notification as I did for my event, so notification will never catch by the frontend application.
composer.json
"beyondcode/laravel-websockets": "^1.13",
"pusher/pusher-php-server": "^7.2",
"laravel/framework": "^9.19",
package.json
"pusher-js": "^7.5.0",
"laravel-echo": "^1.14.2",
I tried the explicit way as well i.e using the pusher SDK's functions[which are giving 200 status code] but not working. As soon as I do it the dirty way it starts working, I mean everything starts working without any issue.
public function pusherTesting(Request $request)
{
$path = "/apps/123456/events";
$settings = [
'scheme' => 'http',
'port' => '6001',
'path' => '',
'timeout' => '30',
'auth_key' => '1b5d6e5b1ab73b',
'secret' => '3739db6a99c1ba',
'app_id' => '123456',
'base_path' => '/apps/123456',
'host' => '127.0.0.1',
];
$params = [];
$body = '{"name":"Illuminate\\Notifications\\Events\\BroadcastNotificationCreated","data":"{\"site_id\":1,\"domain_url\":\"yucentipede-tuvo.blr3.instawp-testing.xyz\",\"save\":\"socket\",\"id\":\"2f53aac0-8d83-45f4-962d-516c1c8bc97c\",\"type\":\"App\\\\Notifications\\\\SiteDeletedNotification\"}","channels":["private-App.Models.User.7"]}';
$params['body_md5'] = md5($body);
$params_with_signature = \Pusher\Pusher::build_auth_query_params(
$settings['auth_key'],
$settings['secret'],
'POST',
$path,
$params
);
$headers = [
'Content-Type' => 'application/json',
'X-Pusher-Library' => 'pusher-http-php 7.2.1'
];
$client = new \GuzzleHttp\Client();
try {
$response = $client->post(ltrim($path, '/'), [
'query' => $params_with_signature,
'body' => $body,
'http_errors' => false,
'headers' => $headers,
'base_uri' => 'http://127.0.0.1:6001'
]);
} catch (Exception $e) {
print_r($e->getMessage());
}
$response_body = json_decode($response->getBody(), false, 512, JSON_THROW_ON_ERROR);
echo $status = $response->getStatusCode();
die;
}

How to show an alert message to a user with Event Listener

I have created an Event called UserWalletNewTransaction.php and added this to it:
public $transaction;
public function __construct($transaction) {
$this->$transaction = $transaction;
}
Now in order to fire this event at the Controller, I coded this:
$newTransaction = UserWalletTransaction::create(['user_id' => $user_id, 'wallet_id' => $wallet_id, 'creator_id' => $creator_id, 'amount' => $amount_add_value, 'description' => $trans_desc]);
event(new UserWalletNewTransaction($newTransaction));
Then at the Listener, UserWalletNotification.php, I tried:
public function handle(UserWalletNewTransaction $event) {
$uid = $event->transaction->user_id;
$user = User::find($uid);
// now sends alert message to the user
}
So the scenario is, when Admins create a new Transaction for a custom user, a new alert message must be sent for him/her to let him/her know that new transaction was added for him/her.
But I don't really know how to do that.. So if you know, please let me know, I would really appreciate that...
Thanks in advance.
If by alert you mean showing a message on the web interface, use flash data.
https://laravel.com/docs/5.8/session#flash-data
$newTransaction = UserWalletTransaction::create(...);
event(new UserWalletNewTransaction($newTransaction));
$request->session()->flash('status', 'Transaction done.');
return view(...)
<span>{{ session('status') }}</span>
If you mean sending an email, just use the Mail facade in your listener to send a mailable.
https://laravel.com/docs/5.8/mail#sending-mail
public function handle(UserWalletNewTransaction $event) {
$uid = $event->transaction->user_id;
$user = User::find($uid);
Mail::to($user)->send(new TransactionDoneMail($event->transaction)); // TransactionDoneMail being your mailable class, made with "php artisan make:email TransactionDoneMail"
}
There are nice examples on how to build a mailable class in the documentation.
https://laravel.com/docs/5.8/mail#writing-mailables
There are many different things you can do in terms of "alerting" the customer.
One route would be to send an email or text message in your event listener. See https://laravel.com/docs/5.8/mail for help doing it via email.
Another way would be using browser push notifications. You could use OneSignal for this. You would setup the front end to display an alert to a customer user asking if they would like to subscribe to push notifications. When they subscribe, you will get back an ID for that specific user. Make an API call to your Laravel app, and store that ID in the users table (you will need a migration). Then from within your event listener, you can make a call to OneSignal's API and send the user a notification, which will popup on their computer.
Here is an example of using OneSignal to send an event to a user via the API:
Your OneSignal service:
<?php
namespace App\Services;
use App\User;
use GuzzleHttp\Client;
class OneSignalService
{
public function sendNotificationToUser(User $user, string $title, string $message, string $url, string $subtitle = null)
{
if (!$user->one_signal_id) {
return;
}
$fields = [
'app_id' => config('services.onesignal.app_id'),
'include_player_ids' => [$user->one_signal_id],
'headings' => ['en' => $title],
'contents' => ['en' => $message],
'url' => $url,
];
if ($subtitle) {
$fields['subtitle'] = ['en' => $subtitle];
}
$client = new Client([
'base_uri' => 'https://onesignal.com/api/v1/',
'headers' => [
'Content-Type' => 'application/json; charset=utf-8',
'Authorization' => 'Basic <<API_KEY>>',
]
]);
$client->request('POST', 'notifications', [
'json' => $fields
])
}
}
UserWalletNotification:
public function handle(UserWalletNewTransaction $event) {
$uid = $event->transaction->user_id;
$user = User::find($uid);
// now sends alert message to the user
$oneSignal = new OneSignalService();
$oneSignal->sendNotificationToUser($user, 'New Transaction', 'You have a new transaction', 'yourwebsite.com');
}
The way I would go about this would be via broadcasting, which would use websockets to instantly send the customer user an alert to their browser, in which you could then display a popup of some sort. You could install Laravel Echo Server, but to keep things simple you can use Pusher. Follow the guide to install on the front end of your website.
Then, create a private channel specific to a customer user "transaction.created.{{USER ID}}" and listen for it on your front end.
Within Laravel you will install the PHP Pusher SDK via composer.
Then within your .env file set:
BROADCAST_DRIVER=pusher
Next, open up channels.php within your routes directory in Laravel and add:
Broadcast::channel('transaction.created.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});
This will verify authentication for your user to the private channel.
Create an Laravel Event:
<?php
namespace App\Events;
use App\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class TransactionCreated implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user = null;
public $transaction = null;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(User $user, UserWalletTransaction $transaction)
{
$this->user = $user;
$this->transaction = $transaction;
}
public function broadcastWith(): array
{
return $this->transaction->toArray(); //Or whatever information you want to send to the front end
}
public function broadcastAs(): string
{
return 'TransactionCreated';
}
/**
* Get the channels the event should broadcast on.
*
* #return Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('transaction.created.' . $this->user->id);
}
}
Fire the event from UserWalletNotification:
public function handle(UserWalletNewTransaction $event) {
$uid = $event->transaction->user_id;
$user = User::find($uid);
// now sends alert message to the user
event(new TransactionCreated($user, $event->transaction));
}
Lastly, create some sort of popup and display it on the front end when your callback function for the private channel is hit.
If you need anymore help, feel free to comment.
What you want to do I believe, is asynchronous notifications.
Well, if you really mean flash messages - those who are stored in session - it will not be so easy.
Normal steps are create flash message for the user currently logged in on a website, stored in session that is unique for the current user. It can be shown only for this user.
What you want is to create flash message as the admin (from admin perspective) , then only to admin it can be shown.
I would do this, create new table, when these notification messages will be stored. Some table with columns like id, user_id, message, type, created_date, shown_date. Admins will put alert/notification messages for each user. Then create class (can be in controller for example) that will check this table for each user and if there is new not already shown message, show it normally in flash message for that current user. Dont forget to mark that message as shown. That is it.
So much for custom solution. I belive there must be some for example jQuery/other Jvascript plugins or Laravel plugins for asynchronous notifications, please check those.

Laravel - Sending customized Bugsnag error reports based on exception type?

I have a Laravel 7 app that uses a 3rd party PHP package to integrate with a service (Convirza). I bootstrapped and bound the service into the container via my own service provider App\Providers\ConvirzaServiceProvider. My problem is that I need to send error logs from the service to Bugsnag (error reporting service)... and depending on the Exception thrown from the service, I need to add metadata to the report. I do NOT want to filter exceptions from the base App\Exceptions\Handler class because I want everything to be wrapped into the Service Provider.
For example, when the Skidaatl\Convirza\Exceptions\BadRequestException gets thrown, I want to add the request parameters as metadata to the report. Another example would be if the Skidaatl\Convirza\Exceptions\InvalidTokenException gets thrown, I want to add the token as metadata to my report.
I would rather not add a try/catch block every time I call the service. How would I go about this?
Here is my Service Provider:
class ConvirzaServiceProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
if ($this->app->runningInConsole()) {
$this->commands([
Commands\UpdateCampaignsCommand::class,
Commands\UpdateGroupsCommand::class,
Commands\GenerateReportsCommand::class
]);
}
$this->app['events']->subscribe(EventHandler::class);
}
/**
* Register services.
*
* #return void
*/
public function register()
{
$this->loadRoutesFrom(__DIR__.'/routes.php');
$this->loadMigrationsFrom(__DIR__.'/database/migrations');
$this->app->singleton('convirza', function(Container $app) {
$config = config('convirza.client');
$config = Config::create($config);
return new Convirza($config);
});
$this->app->bind(ReportBuilder::class, function(Container $app) {
return new ReportBuilder($app->get('convirza'));
});
$this->app->alias('convirza', Convirza::class);
}
}
I suggest using Bugsnag's registerCallback to inspect the report and add metadata based on specific parameters of the report.
For example, you can get the context of your exception and even the stacktrace information. Additionally, you can access the originalError to get information including the error message. Using that, you can create logic in a callback to add metadata.
E.g.
public function boot()
{
Bugsnag::registerCallback(function ($report) {
$stacktrace = $report->getStacktrace();
$frames = &$stacktrace->getFrames();
// Adds metadata for `log_error` frame if present
if ($frames[0]['method'] == 'log_error') {
$report->setMetaData([
'account' => [
'token' => '12345',
]
}
]);
}

Laravel event with socket-io [receive notifications]

For the first time attempt with laravel and socket-io I am trying to send very simple notification to admins. So far my event is firing but I need help with receiving event notifications.
Logic
It's very basic because I want to understand the process.
User opens page Add Product
Admin gets notification that user X is in App Product page.
So far
So far I can fire event and get user data (user that is in Add Product page)
Need help for
I need help to understand the way that admin receives notifications.
Code
Component script
created() {
let user = JSON.parse(localStorage.getItem("user"))
this.listenForBroadcast(user);
},
methods: {
listenForBroadcast(user) {
Echo.join('userInAddProduct')
.here((Loggeduser) => {
console.log('My user data', Loggeduser);
});
}
}
Result of code above
My user data [{…}]
0:
id: 1
name: "Test User"
photo: "User-1588137335.png"
__ob__: Observer {value: {…}, dep: Dep, vmCount: 0}
get id: ƒ reactiveGetter()
set id: ƒ reactiveSetter(newVal)
get name: ƒ reactiveGetter()
set name: ƒ reactiveSetter(newVal)
get photo: ƒ reactiveGetter()
set photo: ƒ reactiveSetter(newVal)
__proto__: Object
length: 1
__proto__: Array(0)
Channels route
Broadcast::channel('userInAddProduct', function ($user) {
return [
'id' => $user->id,
'photo' => $user->photo,
'name' => $user->name
];
});
MessagePushed (event file)
class MessagePushed implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function broadcastOn()
{
return new PresenceChannel('userInAddProduct');
}
}
Question
How can I receive notification about this event fire? I want to notify my admin users that user x is in page Add Product?
Update
Since I published this question I've made some changes and here is my latest code + questions.
bootstrap.js
window.io = require('socket.io-client');
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname + ':6001',
auth: { // added authentication token (because all my events are private)
headers: {
Authorization: localStorage.getItem('access_token'),
},
},
});
Add.vue (add product component where event has to be fired)
listenForBroadcast(user) {
let ui = JSON.parse(localStorage.getItem("user"))
Echo.join('userInAddProduct')
.here((users) => {
console.log('My user data', users)
})
.joining((user) => {
this.$notify({
title: '',
message: user + 'joining',
offset: 100,
type: 'success'
});
})
.leaving((user) => {
this.$notify({
title: '',
message: user + 'is leaving new product',
offset: 100,
type: 'warning'
});
})
.whisper('typing', (e) => {
this.$notify({
title: '',
message: ui.username + 'is adding new product',
offset: 100,
type: 'success'
})
})
.listenForWhisper('typing', (e) => {
console.log(e)
this.$notify({
title: '',
message: ui.username + 'is entered add new product page.',
offset: 100,
type: 'success'
});
})
.notification((notification) => {
console.log('noitication listener: ', notification.type);
});
},
Then I've made 4 files to handle events:
Event (passing data)
Event Listener (process the database storage and showing notifications to online admins)
Observer (firing event)
Notification (store data to database for admins in case when event fire they're not online so they can see notifications later on)
Event file
class MessagePushed extends Event implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user;
public $product;
public function __construct(User $user, Product $product)
{
$this->user = $user;
$this->product = $product;
}
public function broadcastOn()
{
return new PresenceChannel('userInAddProduct');
}
}
Listener file
class ThingToDoAfterEventWasFired implements ShouldQueue
{
public function handle(MessagePushed $event)
{
//Log testing purpose only
$user = $event->user->username;
$product = $event->product->name;
// Real data that should be broadcasts
$user2 = $event->user;
$product2 = $event->product;
// inform all admins and authorized staffs about new product
$admins = User::role(['admin', 'staff'])->get();
foreach($admins as $admin) {
$admin->notify(new UserAddProduct($user2, $product2));
}
Log::info("Product $product was Created, by worker: $user");
}
}
Notification
class UserAddProduct extends Notification implements ShouldQueue
{
use Queueable;
protected $product;
protected $user;
public function __construct(User $user, Product $product)
{
$this->product = $product;
$this->user = $user;
}
public function via($notifiable)
{
return ['database', 'broadcast'];
}
public function toDatabase($notifiable)
{
return [
'user_id' => $this->user->id,
'user_username' => $this->user->username,
'product_id' => $this->product->id,
'product_name' => $this->product->name,
];
}
public function toArray($notifiable)
{
return [
'id' => $this->id,
'read_at' => null,
'data' => [
'user_id' => $this->user->id,
'user_username' => $this->user->username,
'product_id' => $this->product->id,
'product_name' => $this->product->name,
],
];
}
}
Observer
public function created(Product $product)
{
$user = Auth::user();
event(new MessagePushed($user, $product));
}
Questions
How can I return live notifications as soon as event is fired in whole app? currently as my code is placed in add.vue component admins get notify IF they are in same page only :/
How do I get notifications of multiple event? let say I have another event, listener, observer for other page actions I want admin be notified of both product event and other event in whole app.
thanks
We differentiate channel types like public and private.
Public: Illuminate\Broadcasting\Channel. It works without any authentication, so anonymus users can listen to this broadcast. Like football match scores available for everyone.
Private: Illuminate\Broadcasting\PrivateChannel. It works with an authentication, so only authenticated users can listen to this broadcast. Like admins watching what specific users do or user watching their orders status.
On the otherhand there is another private channel which is Illuminate\Broadcasting\PresenceChannel. Also require authentication. In example would be much like an any chat room. Ex.: only authenticated users can communicate with each other.
So these types will define your method. And you have to follow different paths. I am very suggesting to read the Laravel documentation (https://laravel.com/docs/broadcasting), it will guide you very carefully.
In the routes/channels.php you have to decide or verify if the authenticated user can listen on this particular channel. So you have to return boolean value (true or false). So here, you are not returning any value to the client side.
Every public property in your MessagePushed broadcast event will be serialized and sent to the client side. You have to give every informational data to the MessagePushed constructor and set to property/properties. Now you can handle the received data by like that, ex:
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname + ':6001',
auth: {
headers: {
'X-Auth-Token': "<token>"
}
}
});
window.Echo.private('userInAddProduct')
.listen('MessagePushed ', (e) => {
console.log(e.auction);
});
I recommend to create custom authentication with token usage. Above I sent token to the server to authenticate user. https://laravel.com/docs/authentication#adding-custom-guards
In your situation I would separate these two mechanism.
check the user for going to the App Product page
notify the admin
For the 2. point would be enough to use the Illuminate\Broadcasting\PrivateChannel. So there is need for websocket on the admin page.
The 1. point could be tricky, because it's depends on your requirments. It is enough to notify the admin about user goes to exact App Product page?
If yes, then you will trigger this broadcast event in your controller when user goes to that page.
If not enough, then you need another websocket on the user page to "poll" your server and send tick broadcast to the admin in every X seconds.
Trigger broadcast event:
broadcast(new App\Events\MessagePushed($user));
My taste of usage is:
client side: socket.io-client
sever side: laravel-echo-server
redis broadcaster
custom authentication mechanism
Hope, I could help!
Solved
Here is how I done it based on my need and my question (it might be somehow different if your case is different than mine)
In component that activity happens (in my case Add.vue)
Echo.join('userInAddProduct')
.here((users) => {
console.log('here: ', users);
})
.joining((user) => {
console.log('joining: ', user);
})
.leaving((user) => {
console.log('leaving: ', user);
})
.whisper('typing', {data: notf}) // this part sends my data
.notification((notification) => {
console.log('noitication listener1: ', notification.type);
});
As I needed my notification be visible in all pages to admins I've added my socket emit (i.e. Echo.listenForWhisper) into my navbar component.
mounted() {
this.notifs();
window.Echo.join('userInAddProduct')
// listen to broadcast from Add.vue
.listenForWhisper('typing', (product) => {
// add it into my navbar notifications list
this.notifications.unshift(product);
});
}
Now each time user adds new product to server admin gets notified.
note To see my other settings for this function to happen please scroll back to my update part and see Event, Listener & Observe files code. (you make those files and all this codes above to your preferred components then you'll have real time notifications).
I recently wrote an article How to use Laravel WebSockets for NuxtJs notifications where I described in sufficient detail including the event setting for Laravel WebSockets. I hope you find it useful.

How to configure Channel Authorization for Broadcasting in Laravel 5.3 using Pusher Driver?

Laravel Version: 5.3.*
PHP Version: 5.6.17
Database Driver & Version:
mysql
Description:
According to Laravel 5.3 documentation when broadcasting events on private or presence channels, in the boot method of the BroadcastServiceProvider one must provide a callback that resolves if an user has authorization to listen to that channel to the Broadcast facade method channel. This method should return a boolean. In the the BroadcastServiceProvider method boot we should also include Broadcast::routes() that will define the auth route that the client will call to check for permission on the channel. This routes method can receive an array of attributes to apply to the route. Now it's where it gets weird. When the client call this route no matter what callback I passed to the Broadcast::channel method it will give me a 403 forbidden unless (and now comes the weirdest part) I provide an array to the Broadcast::routes with a key named prefix and a value of whatever. If the key is not prefix it will go back to 403 forbidden.
HttpException in PusherBroadcaster.php line 42:
My setup follows. I'm for sure doing something wrong but after a lot of ours trying to understand I can't figure it out. Can someone give an hint?
Steps To Reproduce:
I have created a simple event:
<?php
namespace App\Events;
use App\Models\Presentation;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class PresentationCreated implements ShouldBroadcast
{
use InteractsWithSockets, SerializesModels;
public $presentation;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Presentation $presentation)
{
$this->presentation = $presentation;
}
/**
* Get the channels the event should broadcast on.
*
* #return Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('presentation');
}
}
that i trigger by calling event(new PresentationCreated($presentation));
I have installed "pusher/pusher-php-server": "^2.5.0" and created an account in pusher.
I put my pusher credentials in .env:
BROADCAST_DRIVER=pusher
PUSHER_APP_ID=*****
PUSHER_APP_KEY=*****************
PUSHER_APP_SECRET=****************
PUSHER_APP_CLUSTER=**
in my config\broadcast.php I have:
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => 'eu',
'encrypted' => true,
],
],
My client side:
this.Echo = new Echo({
broadcaster: 'pusher',
key: typeof handover.pak !== 'undefined' ? handover.pak : '',
cluster: 'eu'
});
this.Echo.private(`presentation`)
.listen('PresentationCreated', (e) => {
console.log(e, 'raposa')
});
And finally the BroadcastServiceProvider:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Broadcast;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Broadcast::routes();
//The commented line would make the authorization pass even if I return false bellow
//Broadcast::routes(['prefix' => 'I do not know what I am doing']);
/*
* Authenticate the user's personal channel...
*/
Broadcast::channel('presentation', function ($user) {
return false;
});
}
}
EDIT
Thanks to #yazfield answer I was able to understand what was going on. The http error was due to the $request->user() being null. That was because I was not passing the additional middlewares that my route namespace was using. By doing Broadcast::routes(['middleware' => ['web', 'clumsy', 'admin-extra']]); I was able to solve the problem.
This Laravel issue also helped me getting the grasp of the thing.
By giving a parameter to routes you're setting routes attributes and overriding the attributes that default to 'middleware' => ['web'], which basically means you're not using any of the web middlewares when you give any array without middleware attribute, you're not verifying crsfToken...etc.

Categories