Reputation: 482
I have a websocket group chat in my app which broadcasts user's message to all the other users. I want to make a one on one chat also which will broadcast messages to two sides only. How can I tell the websocket to broadcast the message to a specific user?
My Controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Message;
use App\Events\MessageSent;
use Auth;
class ChatsController extends Controller
{
public function index()
{
if (Auth::check() === false) {
return view('login');
}
else
{
return view('chats');
}
}
public function fetchMessages()
{
return Message::with('user')->get();
}
public function sendMessage(Request $request)
{
$message = auth()->user()->messages()->create([
'message' => $request->message
]);
broadcast(new MessageSent($message->load('user')))->toOthers();
return ['status' => 'success'];
}
}
My Vue file:
<template>
<div class="row">
<div class="col-8">
<div class="card card-default">
<div class="card-header">Messages</div>
<div class="card-body p-0">
<ul class="list-unstyled" style="height:300px; overflow-y:scroll" v-chat-scroll>
<li class="p-2" v-for="(message, index) in messages" :key="index" >
<div style="background: #009afb; padding: 8px; color: white; border-radius: 15px;">
<img v-if="message.user.image == 'img'" src="https://www.stickpng.com/assets/images/585e4bf3cb11b227491c339a.png" style="width: 35px; height: 35px;">
<img v-else v-bind:src="'../images/' + message.user.image"
style="width: 35px; height: 35px;border-radius: 50%;">
<strong>{{ message.user.name }} {{ message.user.surname }} :</strong>
{{ message.message }}
<p style="color:lightgray;">
{{ message.created_at }}
</p>
</div>
</li>
</ul>
</div>
<input
@keydown="sendTypingEvent"
@keyup.enter="sendMessage"
v-model="newMessage"
type="text"
name="message"
placeholder="Enter your message..."
class="form-control">
</div>
<span class="text-muted" v-if="activeUser" >{{ activeUser.name }} is typing...</span>
</div>
<div class="col-4">
<div class="card card-default">
<div class="card-header">Active Users</div>
<div class="card-body">
<ul>
<li class="py-2" v-for="(user, index) in users" :key="index">
{{ user.name }}
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props:['user'],
data() {
return {
messages: [],
newMessage: '',
users:[],
activeUser: false,
typingTimer: false,
}
},
created() {
this.fetchMessages();
Echo.join('chat')
.here(user => {
this.users = user;
})
.joining(user => {
this.users.push(user);
})
.leaving(user => {
this.users = this.users.filter(u => u.id != user.id);
})
.listen('MessageSent',(event) => {
this.messages.push(event.message);
})
.listenForWhisper('typing', user => {
this.activeUser = user;
if(this.typingTimer) {
clearTimeout(this.typingTimer);
}
this.typingTimer = setTimeout(() => {
this.activeUser = false;
}, 3000);
})
},
methods: {
fetchMessages() {
axios.get('messages').then(response => {
this.messages = response.data;
})
},
sendMessage() {
this.messages.push({
user: this.user,
message: this.newMessage
});
axios.post('messages', {message: this.newMessage});
this.newMessage = '';
},
sendTypingEvent() {
Echo.join('chat')
.whisper('typing', this.user);
}
}
}
</script>
My table has fields of id,user_id and message.
Upvotes: 3
Views: 3064
Reputation: 23
Hello I have the same problem and fixed it like this But Notice that I have the extra logic for it
1- in channels.php
Broadcast::channel('chat.{ticketId}', function ($user, $ticketId) {
$ticket = \App\Models\Ticket::find($ticketId);
return $user->id == $ticket->asked_user_id || $user->id == $ticket->responded_user_id;
});
2- I just allow users to chat with each other that one of them is asked the support ticket or the admin that responded the user
and in my event I have this code
class MessageSentEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $message;
public $ticket;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(TicketMEssage $message, Ticket $ticket)
{
$this->message = $message;
$this->ticket = $ticket;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PresenceChannel('chat.' . $this->ticket->id);
}
public function toBroadcast()
{
return new MessageSentEvent($this->message);
}
}
3- I get message and the ticket like this
$ticket = Ticket::find($id);
$messages = $ticket->messages;
$messages->load('user');
return response()->json([
'messages' => $messages,
'ticket' => $ticket,
'user' => auth()->user()
]);
4- and I store and broadcast Message like below
$ticket = Ticket::find($request->ticket_id);
$message = $ticket->messages()->create([
'message' => $request->message,
'for' => auth()->id() == $ticket->asked_user_id ? TicketMessage::FOR_USER['asked'] : TicketMessage::FOR_USER['responded'],
'user_id' => auth()->id(),
]);
$message->load('user');
broadcast(new MessageSentEvent($message, $ticket));
return ['status' => 'success', 'message' => $message];
5- and the important part in js file (React Or Vue Or ...)
window.Echo.join(`chat.${props.ticketId}`)
.listen('MessageSentEvent', (message) => {
setMessages((prevState) => [...prevState, message.message]);
});
I listen just to channel that is with the ticket id that I get from props(you should pass it in any way)
and the key parts are 1 and 4 and 5
Hope This Help You :)))
Upvotes: 2