I'm trying to broadcast an event in a case where two user share a conversation (like a chat) where only the two of the can access and get notified when a new message comes.
I think, after reading the documentation that the presence channel is the best option (private is for one people and the server and channel is for something plublic without checking ?)
So in my routes/channels.php I have something like this:
Broadcast::channel('conversation.{conversation}', function ($user, Conversation $conversation) {
if ($user->id === $conversation->user1_id || $user->id === $conversation->user2_id) {
return $user;
}
return null;
});
In the client side a Component I have:
Echo
.join('conversation.${conversation.id}')
.here((users) => {
this.usersInRoom = users;
})
.joining((user) => {
this.usersInRoom.push(user);
})
.leaving((user) => {
this.usersInRoom = this.usersInRoom.filter(u => u != user);
})
.listen('MessagePosted', (e) => {
this.messages.push({
id :e.message.id,
user_id :e.message.user_id,
conversation_id :e.message.conversation_id,
user :e.user,
text :e.message.text,
created_at :e.message.created_at,
updated_at :e.message.updated_at,
});
});
And the class that emit the even MessagePosted :
public function broadcastOn()
{
return new PresenceChannel('conversation.'.$this->message->conversation_id);
}
So I now that previously I used a PresenceChannel without any checking, so if two people where in there conversation they would get notification from everyone. not the right thing.
In the server side, I have the 'conversation.{conversation}' that was mentioned in the documentation to make a separated channel. But I also saw something like 'conversation.*'.
And on the client side I have join('conversation.${conversation.id}') but here I am not sure at all I just know that I have in the props (props : ['user', 'friend', 'conversation']) a conversation which is an object with the id of the conversation.
So When, when everyone was on the same channel with no restriction everything was working perfectly and now, I think I have an error which make the whole thing not work.
I have two 500 server side error when I load a client conversation :
ReflectionException in Broadcaster.php line 170:
Class Conversation does not exist
(And in the route/channels.php I import the Conversation class use App\Conversation;)
and
HttpException in Broadcaster.php line 154:
I found the following solution to my problem. I think I saw something in the documentation saying that you might need to use the Path of the class you want to broadcast. So I tried something like this :
In the routes/channel.php
Broadcast::channel('App.Conversation.{id}', function ($user, $id) {
$conversation = Conversation::findOrFail($id);
if ($user->id === $conversation->user1_id || $user->id === $conversation->user2_id) {
return $user;
}
return null;
});
In the client side in the vue component :
Echo
.join('App.Conversation.'+this.conversation.id)
...
In the class that emit the event MessagePosted :
public function broadcastOn()
{
return new PresenceChannel('App.Conversation.'.$this->message->conversation_id);
}
Related
I ask this question while I have done researches to find clear solution for this problem but many of available answers are just how to use Laravel Scheduler itself!
So I am creating a simple website with Laravel to let users create reminders for themselves.Users can receive reminder either on their Emails or if they are logged in, they receive a notification alert something like Facebook Notifications.
I am not sure if I am following the right path or not but I'm using Laravel Scheduler to send reminders to each specific user.
I also use Events in the Laravel to push recent changes every 1 minute to users but my problem is, all users receive same notification. For example if I want to remind an appointment to Mr X, Ms Y also receives same exact notification!!!
At this point, I have two questions:
1 - Is using Laravel Scheduler a good idea for this project or not? If not then what technology or method is recommended?
2 - If I have to use Laravel Scheduler for this project, so how can I send notification to related user? (I cannot use User ID as Laravel Scheduler is running by server itself and not users!)
I also attached my codes to show you what I have up to this point and I know the method I used in Laravel Channels Broadcast is somehow wrong but I just tried it!If anyone knows the answer please help me. Thank you
Laravel Scheduler - Command - Handle Function
public function handle()
{
$reminders = Note::whereTime('task_date', '<=', Carbon::now()->addMinutes(30))
->WhereTime('task_date', '>', Carbon::now()->subMinutes(30))->get();
foreach ($reminders as $reminder) {
if (!$reminder->notified) {
Note::find($reminder->id)
->update(['notified' => 1]);
event(new RemindUsers($reminder));
dd($reminder->title);
}
}
}
Laravel Event
public $reminder;
public function __construct($reminder)
{
$this->reminder = $reminder;
}
public function broadcastOn()
{
return new PrivateChannel('remind.'.$this->reminder->user_id);
}
public function broadcastWith () {
return [
'reminders' => $this->reminder
];
}
Laravel Channels
Broadcast::channel('remind.{id}', function ($user, $id) {
$notes = Note::where('user_id', $id)->get();
foreach ($notes as $note) {
return $user->id === $note->user_id;
}
});
Vue JS Code - Echo Method
data () {
return {
user: 1
}
},
CatchReminders () {
Echo.private(`remind.${this.user}`)
.listen('RemindUsers', (response) => {
this.$toast.info(`${response.reminders.title}`)
})
}
In my project, I have a route to display topics that looks like this.
Route::get('/voting-topics', [PublicTopicController::class, 'index'])->name('public-topics');
The voting topics page show a list of topic pulled from my DB where they are marked as public.
The issue I'm facing is I also want to display a full list of topics for subscribers.
Currently, I'm using this route for subscribers
Route::group(['middleware' => ['subscriber']], function () {
Route::get('/dashboard/topics', [MemberTopicController::class, 'topics'])->name('dashboard.topics');
});
I don't think this is the best way, I'd prefer to have all the topics on /topics and but only show the public ones for none subscribers.
I guess my question is, is there a way to check in my route if a user is a subscriber and do something like this.
if( subscriber()->user() ) {
Route::post('/topics', [TopicController::class, 'index'])->name('topics');
} else {
Route::post('/topics', [TopicController::class, 'publicTopic'])->name('topics');
}
Instead of declaring two different routes, I would declare a single route and I would decide in the controller what to do, based on the subscriber status.
Something like:
Route::get('/topics', [TopicController::class, 'topics'])->name('topics');
in your controller:
public function topics() {
if (subscriber()->user()) {
return $this->subscriberTopics();
} else {
return $this->publicTopics();
}
}
private function publicTopics() {
// public topics stuff
}
private function subscriberTopics() {
// subscriber topics stuff
}
How do I receive data when the client initially joins a channel?
Something like this:
Echo.channel('channel-name')
.onjoin(function (data) {
console.log(data) // Data received from server
})
As soon when the client joins, the server should respond with data, preferably with PHP.
Presence channels return information about the users in the channel at the time the client subscribes.
If you require other data from your server then you could send a post request to your server to get the information. You could invoke this in a pusher:subscription_succeeded binding so it occurs as soon as the subscription is established.
Edit:
.here will display the data per user connected, which is not what I want.
I thought presence channel was what I was looking for. https://laravel.com/docs/8.x/broadcasting#presence-channels
// Define channel
Broadcast::channel('channel-name', function () {
if (/* Condition if user is allowed to join channel */) {
return ['data' => 'your Data'];
}
});
// Join channel
Echo.join('channel-name')
.here(function(data) {
console.log(data)
})
// Event
class Event implements ShouldBroadcast
{
public function broadcastOn()
{
return new PresenceChannel('channel-name');
}
}
I've setup a notification system with Pusher and Echo on my Laravel 8 app. It works fine, I'm able to retrieve the notification event in VanillaJS with
window.Echo.private('App.Models.User.' + User.id)
.notification((notification) => {
if (notification.type === 'App\\Notifications\\JobLiked') {
let count = document.getElementById('count');
let number = count.innerHTML;
number++;
count.innerHTML = number;
}
});
But now I want to use Livewire listeners to trigger my function, then I setup :
public function getListeners()
{
return [
"echo-private:App.Models.User.{$this->authId},NotificationSent" => 'notifyNewJobLiked',
];
}
But nothing seems to work and I have no error message.. do you have any clue what could possibly going on ?
Thank you very much ! :)
Try to configure your listener with the following specific event name:
public function getListeners()
{
return [
"echo-private:App.Models.User.{$this->authId},.Illuminate\\Notifications\\Events\\BroadcastNotificationCreated" => 'notifyNewJobLiked',
];
}
Since you are using Laravel Notifications to trigger the broadcast instead of a broadcastable event, the event name when fired defaults to Illuminate\\Notifications\\Events\\BroadcastNotificationCreated.
In Echo there are two methods to listen for incoming messages: notification and listen. The reason why it works with your vanilla js is that you are using the notification method, whereas the livewire event listener only works with Echo's listen, which expects the name of the calling event.
If you are using pusher, you can see the name of the calling event in the pusher debug console.
Also take care and add a dot in front of the namespaced event as described in the documentation.
Right way to get Echo notification in livewire:
public function getListeners()
{
$user_id = auth()->user()->id;
return [
"echo-notification:App.Models.User.{$user_id}, notification" => 'gotNotification'
];
}
As you can see the channel type is notification, not private:
echo-notification
Also don't forget to have a record authenticating the users in your channels route, same as you do for a private channel:
Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});
Another thing I learned is that you can get the notification channel data from the returned field.
So you can do:
public function gotNotification($notification)
{
}
I have the following code:
AppServiceProvider:
User::saved(function (User $user) {
return $user->modifiedEvent();
});
User::deleted(function (User $user)
return $user->modifiedEvent();
});
In my User model I have:
public function modifiedEvent()
{
$stage = $this->isDirty('terms_and_conditions')
&& $this->terms_and_conditions !== null ? 'completed' : 'incomplete';
$this->stage_terms_and_conditions = $stage;
return $this->save();
}
The issue I have is that the modifiedEvent also modifies the model, which means we get an infinite loop!
Whats the best way to make sure that this event is not triggered multiple times due caused by the event it self.
I will try to use the 'saving' event so there is no need to call $this->save() at the end.