Xử lý private channel trong ứng dụng chat realtime sử dụng Laravel, VueJS, Redis, Socket.io, Laravel Echo

Chào mừng các bạn quay trở lại với series viết ứng dụng chat realtime sử dụng Laravel, VueJS, Redis, Socket.IO và Laravel Echo của mình.

Trước khi vào bài mình có đôi lời như sau 😃:

  • Từ bài trước trong series này các bạn có thể xem ở đây (các bạn có thể tham khảo hoặc xem luôn bài này) đến bài này thời gian khá dài, vì lúc đó mình làm đến phần public channel thì mọi thứ ok nhưng đến khi chuyển qua private channel thì laravel-echo-server báo lỗi authenticate mà sửa mãi không được nên đành dừng lại ở đó. Dạo gần đây ngồi làm lại mới phát hiện ra lỗi sai nho nhỏ mà làm mình đã từng vật vã không tìm ra 😄.
  • Phần code của bài này mình đã làm mới hoàn toàn lại giao diện chat so với phần trước.

Nội dung của bài này là chúng ta sẽ xây dựng ứng dụng có các phòng chat, sau đó người dùng có thể vào từng phòng riêng biệt và chat realtime với các người dùng khác.

Chuẩn bị

Thiết lập project

Ta tạo mới project đặt tên là chat-app như sau:

laravel new chat-app

Nếu bạn nào không làm được command trên thì làm theo cách bình thường dùng composer như sau nhé:

composer create-project --prefer-dist laravel/laravel chat-app

Tiếp theo ta cần cài redis bằng command:

composer require predis/predis

Sau đó ta thêm mới một database đặt tên là chat_app, đồng thời các bạn tự sửa lại phần thông tin DB trong file .env cho đúng nhé.

Vẫn ở file .env bởi vì ta dùng redis nên ta cần sửa lại một chút phần driver như sau:

BROADCAST_DRIVER=redis
CACHE_DRIVER=file
SESSION_DRIVER=file
SESSION_LIFETIME=120
QUEUE_DRIVER=redis

Sau đó các bạn lưu file lại nhé.

Chuẩn bị dữ liệu

Bởi vì sau này ta sẽ sử dụng quyền của người dùng để xác thực khi họ muốn vào một phòng chat cụ thể, nên ta sẽ sửa lại một chút ở bảng users trước khi migrate nhé.

Ta tạo mới một migration mục đích để thêm một cột role đại diện cho quyền của user.

php artisan make:migration add_column_user_table --table=users

Ta mở file migration vừa tạo, trong hàm up() ta sửa như sau:

Schema::table('users', function (Blueprint $table) {
    $table->string('role');
});

Tiếp theo ta sẽ tạo sẵn dữ liệu cho bảng user bằng cách sử dụng Seeder nhé. Đầu tiên ta tạo seeder cho bảng users bằng cách:

php artisan make:seeder UserTableSeeder

Mở seeder vừa tạo và thêm vào hàm up() như sau:

public function run()
{
    DB::table('users')->insert([
        'name' => str_random(10),
        'email' => str_random(10).'@gmail.com',
        'password' => bcrypt('123456'),
        'role' => 'admin'
    ]);

    DB::table('users')->insert([
        'name' => str_random(10),
        'email' => str_random(10).'@gmail.com',
        'password' => bcrypt('123456'),
        'role' => 'saler'
    ]);

    DB::table('users')->insert([
        'name' => str_random(10),
        'email' => str_random(10).'@gmail.com',
        'password' => bcrypt('123456'),
        'role' => 'employee'
    ]);
}

Ở bên trên đơn giản là mình tạo trước 3 user với các quyền với giá trị như của trường role.

Sau đó ta mở file DatabaseSeeder và sửa lại hàm run() như sau:

public function run()
{
    $this->call(UserTableSeeder::class);
}

Ổn rồi đó, cuối cùng ta chạy command sau để migrate và seed dữ liệu:

php artisan migrate --seed

Nếu khi chạy báo lỗi key too long thì các bạn mở file app/providers/AppServiceProvider và sửa lại như sau:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Schema;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Schema::defaultStringLength(191);
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

Chạy migrate:rollback sau đó chạy lại migrate --seed là được nhé.

Cuối cùng là generate key cho app của chúng ta nhé:

php artisan key:generate

Bắt tay vào làm thôi nào ^^

VueJS

Đầu tiên chúng ta sẽ cùng nhau code phần giao diện nhé. Đầu tiên ta cần chạy npm install trước để cài đặt các package cần thiết cho VueJS nhé.

Tiếp theo cài đặt vue-router nhé mọi người:

npm install --save vue-router

Tiếp theo ta tạo mới một component Vue là Lounge.vue mục đích đây là nơi chúng ta sẽ chọn các chatroom để vào. Nội dung như sau:

<template>
	<div class="lounge">
		<div class="container">
			<div class="row" style="margin-top: 50px;">
				Current user: <b>My user</b>
			</div>
			<div class="row" style="margin-top: 100px;">
				<div class="col-md-6" v-for="chatroom in chatrooms">
					<div class="card" style="width: 18rem;">
					  	<div class="card-body">
						    <h5 class="card-title">{{ chatroom.name }}</h5>
						    <p class="card-text">{{ chatroom.description }}</p>
						    <router-link class="btn btn-primary" :to="`/chatroom/${chatroom.id}`">Join</router-link>
						</div>
					</div>
				</div>
			</div>
		</div>
	</div>
</template>

<script>
	export default {
		data() {
			return {
				chatrooms: [
					{
						id: 1,
						name: 'Chatroom 1',
						description: 'Join me and have fun'
					},
					{
						id: 2,
						name: 'Chatroom 2',
						description: 'Join me and have fun'
					}
				]
			}
		},
	}
</script>

<style lang="scss" scoped>
</style>

Sau khi người dùng chọn một chatroom nào đó thì họ sẽ được redirect vào trong chatroom. Ta tạo mới một file đặt tên là Chatroom.vue nội dung như sau:

<template>
	<div class="chatroom">
		<div class="container clearfix">
		    <div class="people-list" id="people-list">
		        <div class="go-back">
		        	<router-link to="/" class="btn btn-primary">Back</router-link>
		        </div>
		    </div>
		    <div class="chat">
		        <div class="chat-history">
		            <ul>
		                <li class="clearfix">
		                    <div class="message-data align-right">
		                        <span class="message-data-time">April 11</span> &nbsp; &nbsp;
		                        <span class="message-data-name current-user-name">My name</span> <i class="fa fa-circle me"></i>

		                    </div>
		                    <div class="message float-right my-message">
		                        Hello guys
		                    </div>
		                </li>
		                <li class="clearfix">
		                    <div class="message-data align-right">
		                        <span class="message-data-time">April 11</span> &nbsp; &nbsp;
		                        <span class="message-data-name current-user-name">My name</span> <i class="fa fa-circle me"></i>

		                    </div>
		                    <div class="message float-right my-message">
		                        How are you
		                    </div>
		                </li>
		            </ul>

		        </div>
		        <div class="chat-message clearfix">
		            <textarea class="form-control" name="message-to-send" id="message-to-send" placeholder="Type your message" rows="3"></textarea>
		            <button class="btn btn-primary">Send</button>
		        </div>
		    </div>
		</div>
	</div>
</template>

<script>
	export default {

	}
</script>

<style lang="scss" scoped>
	@import url(https://fonts.googleapis.com/css?family=Lato:400,700);
	.current-user-name {
		color: red;
	}
li {
	list-style-type: none;
}
$green: #86BB71;
$blue: #94C2ED;
$orange: #E38968;
$gray: #92959E;

*, *:before, *:after {
  box-sizing: border-box;
}

body {
  background: #C5DDEB;
  font: 14px/20px "Lato", Arial, sans-serif;
  padding: 40px 0;
  color: white;
}

.container {
  margin: 0 auto;
  width: 1000px;
  background: #00e2f9;
  border-radius: 5px;
  color: white;
}

.people-list {
  width:260px;
  float: left;
  
  .search {
    padding: 20px;
  }
  

  
  .fa-search {
    position: relative;
    left: -25px;
  }
  
  ul {
    padding: 20px;
    height: 770px;
 
    
    
    
    li {
      padding-bottom: 20px;
    }
  }
  
  img {
    float: left;
  }
  
  .about {
    float: left;
    margin-top: 8px;
  }
  
  .about {
    padding-left: 8px;
  }
  
  .status {
    color: $gray;
  }
  
}

.chat {
	margin-left: 100px;
  width: 490px;
  float:left;
  background: #F2F5F8;
  border-top-right-radius: 5px;
  border-bottom-right-radius: 5px;
  
  color: #434651;
  
  .chat-header {
    padding: 20px;
    border-bottom: 2px solid white;
    
    img {
      float: left;
    }
    
    .chat-about {
      float: left;
      padding-left: 10px;
      margin-top: 6px;
    }
    
    .chat-with {
      font-weight: bold;
      font-size: 16px;
    }
    
    .chat-num-messages {
      color: $gray;
    }
    
    .fa-star {
      float: right;
      color: #D8DADF;
      font-size: 20px;
      margin-top: 12px;
    }
  }
  
  .chat-history {
    padding: 30px 30px 20px;
    border-bottom: 2px solid white;
    overflow-y: scroll;
    height: 575px;
    
    .message-data {
      margin-bottom: 15px;
    }
    
    .message-data-time {
      color: lighten($gray, 8%);
      padding-left: 6px;
    }
    
    .message {      
      color: white;
      padding: 18px 20px;
      line-height: 26px;
      font-size: 16px;
      border-radius: 7px;
      margin-bottom: 30px;
      width: 90%;
      position: relative;
      
      &:after {
        bottom: 100%;
        left: 7%;
        border: solid transparent;
        content: " ";
        height: 0;
        width: 0;
        position: absolute;
        pointer-events: none;
        border-bottom-color: $green;
        border-width: 10px;
        margin-left: -10px;
      }
    }
    
    .my-message {
      background: $green;
    }
    
    .other-message {
      background: $blue;
      
      &:after {
        border-bottom-color: $blue;
        left: 93%;
      }
    }
    
  }
  
  .chat-message {
    padding: 30px;
    
    textarea {
      width: 100%;
      border: none;
      padding: 10px 20px;
      font: 14px/22px "Lato", Arial, sans-serif;
      margin-bottom: 10px;
      border-radius: 5px;
      resize: none;
      
    }
    
    .fa-file-o, .fa-file-image-o {
      font-size: 16px;
      color: gray;
      cursor: pointer;
      
    }
  }
}

.online, .offline, .me {
    margin-right: 3px;
    font-size: 10px;
  }
  
.online {
  color: $green;
}
  
.offline {
  color: $orange;
}

.me {
  color: $blue;
}

.align-left {
  text-align: left;
}

.align-right {
  text-align: right;
}

.float-right {
  float: right;
}

.clearfix:after {
	visibility: hidden;
	display: block;
	font-size: 0;
	content: " ";
	clear: both;
	height: 0;
}
</style>

Tiếp theo ta sẽ setup vue-router nhé. Trong resources/assets/js các bạn tạo mới folder đặt tên là router, trong đó các bạn tạo mới file routes.js nội dung như sau:

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

export default new VueRouter({
	mode: 'history',
    linkActiveClass: 'active',
	routes: [
	    {
	      path: '/',
	      name: 'dashboard',
	      component: require('../components/Lounge.vue')
	    },
	    {
	      path: '/chatroom/:id',
	      name: 'chatroom',
	      component: require('../components/Chatroom.vue')
	    },
    ]
})

Ở trên mình có truyền :id mỗi khi ta truy cập vào chatroom, đây chính là id của chatroom do ta định nghĩa ở component Lounge.vue

Sau đó ta vào file app.js và sửa lại như sau:

require('./bootstrap');

window.Vue = require('vue');
import router from './router/routes'

/**
 * Next, we will create a fresh Vue application instance and attach it to
 * the page. Then, you may begin adding components to this application
 * or customize the JavaScript scaffolding to fit your unique needs.
 */

const app = new Vue({
    el: '#app',
    router
});

Cuối cùng là quay lại file resources/views/welcome.blade.php và sửa như sau:

<!DOCTYPE html>
<html>
<head>
    <title>MY APP</title>
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
</head>
<body>
    <div id="app">
        <router-view></router-view>
    </div>
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
    <script src="/js/app.js"></script>
</body>
</html>

Ổn rồi đó. Các bạn chạy command sau để build và khởi động app.

npm run watch
php artisan serve

Mở trình duyệt truy cập vào địa chỉ của app nếu thấy như sau là ok rồi nhé 😃: Thử click join vào một chatroom ta sẽ được redirect vào bên trong phòng chat.

Tạo model và CRUD

Sau này mỗi khi muốn chat thì ta yêu cầu user phải đăng nhập vào trước. Do đó ta cần authenticate nhé, đầu tiên các bạn chạy command sau:

php artisan make:auth

Sau đó ta sẽ tạo model Message bằng cách chạy command sau:

php artisan make:model Message -c -m

Ở trên mình tạo luôn controller và migration cho model đó. Tiếp theo ta mở app/Message.php và sửa lại như sau:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Message extends Model
{
	protected $fillable = ['message', 'user_id', 'chatroom_id'];
	
    public function user() {
    	return $this->belongsTo(User::class);
    }
}

Mỗi Message sẽ có chứa nội dung của message, người gửi (user_id) và chatroom_id để chỉ message đó là của chatroom nào. Đồng thời mình cũng thiết lập quan hệ: mỗi message sẽ phụ thuộc vào một user. Và tương ứng mỗi user sẽ có nhiều message, do đó ta mở file User.php và thêm vào cuối như sau:

public function messages() {
    return $this->hasMany(Message::class);
}

Sau đó ta mở file migration của message và sửa lại hàm up như sau:

public function up()
{
    Schema::create('messages', function (Blueprint $table) {
        $table->increments('id');
        $table->text('message');
        $table->integer('user_id')->unsigned();
        $table->integer('chatroom_id')->unsigned();
        $table->timestamps();
    });
}

Sau đó ta cần migrate lại DB:

php artisan migrate

Tiếp theo ta mở app/Http/Controllers/MessageController.php và sửa lại như sau:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Auth;
use App\Message;

class MessageController extends Controller
{
	public function index(Request $request) {
		$messages = Message::where('chatroom_id', $request->input('chatroom_id'))->with('user')->get();
		return $messages;
	}

   	public function store(Request $request) {
   		$user = Auth::user();

	    $message = $user->messages()->create([
	        'message' => request()->get('message'),
	        'chatroom_id' => request()->get('chatroomId')
	    ]);

	    return ['status' => 'OK'];
   	}
}

Ở bên trên mình có 2 hàm:

  • index: dùng để lấy thông tin toàn bộ message có trong chatroom, lấy kèm thông tin user đã gửi message đó(sử dụng with)
  • store: hàm này đơn giản là lưu lại message vừa được gửi đi Sau đó ta mở file routes/web.php và sửa lại như sau:
Route::get('/', function () {
    return view('welcome');
})->middleware('auth');

Auth::routes();

Route::get('/getCurrentUser', function() {
    return Auth::user();
});

Route::get('/messages', '[email protected]');

Route::post('/messages', '[email protected]')->middleware('auth');

Route::get('/{any?}', function() {
    return view('welcome');
})->where('any', '^(.*)\/?$')->name('welcome');

Ở bên trên mình có:

  • mình đã thêm auth khi truy cập vào route /
  • Bên cạnh đó mình cũng có 2 route getpost message để gọi đến 2 hàm ở trong MessageController
  • Còn route any cuối cùng mục đích là để không bị báo lỗi Route not found khi ta dùng vue-router, vì nếu không có đoạn code này. Khi ta chuyển từ phòng chờ vào trong 1 chatroom nào đó, sau đó ta bấm f5 thì sẽ bị báo lỗi ngay lập tức vì trong Laravel ta không có route đó.

Ổn phần backend rồi đó. Giở ta quay lại file resources/assets/js/app.js và sửa lại một chút như sau để lấy thông tin của user đang login:

const app = new Vue({
    el: '#app',
    router,
    data: {
    	currentUserLogin: {}
    },
    created() {
    	this.getCurrentUser()
    },
    methods: {
    	getCurrentUser() {
    		axios.get('/getCurrentUser')
    		.then(response => {
                this.currentUserLogin = response.data
    		})
    		.catch(error => {
    			console.log(error)
    		})
    	}
    }
});

Sau đó ta mở lại component Lounge.vue và sửa lại một chút như sau phần hiển thị user đang đăng nhập:

<div class="row" style="margin-top: 50px;">
    Current user: <b>{{ $root.currentUserLogin.name }}</b>
</div>
<div class="row" style="margin-top: 30px;">
    Role: <b>{{ $root.currentUserLogin.role }}</b>
</div>

Thử load lại trang và các bạn có thể thấy phần current user đã lấy được user đang login(nhớ vẫn chạy npm run watch suốt quá trình nhé).

Nhưng hiện tại ta vẫn chưa thể thêm được message trong phòng chat. Sau đây ta sẽ làm nó nhé. Các bạn mở component Chatroom.vue và sửa lại như sau (phần code css giữ nguyên nên mình không copy vào đây nữa nhé):

<template>
  <div class="chatroom">
    <div class="container clearfix">
        <div class="people-list" id="people-list">
            <div class="go-back">
              <router-link to="/" class="btn btn-primary">Back</router-link>
            </div>
        </div>
        <div class="chat">
            <div class="chat-history">
                <ul>
                    <li class="clearfix" v-for="message in list_messages">
                        <div class="message-data" :class="message.user_id === $root.currentUserLogin.id ? 'align-right' : ''">
                            <span class="message-data-time">{{ message.created_at }}</span> &nbsp; &nbsp;
                            <span class="message-data-name" :class="message.user_id === $root.currentUserLogin.id ? 'current-user-name' : ''">{{ message.user.name }}</span> <i class="fa fa-circle me"></i>

                        </div>
                        <div class="message float-right" :class="message.user_id === $root.currentUserLogin.id ? 'my-message' : 'other-message'">
                            {{ message.message }}
                        </div>
                    </li>
                </ul>
            </div>
            <div class="chat-message clearfix">
                <textarea class="form-control" name="message-to-send" id="message-to-send" placeholder="Type your message" rows="3" v-model="message"></textarea>
                <button class="btn btn-primary" @click="sendMessage">Send</button>
            </div>
        </div>
    </div>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        message: '',
        list_messages: []
      }
    },
    created() {
      this.loadMessage()
    },
    methods: {
      loadMessage() {
        axios.get('/messages?chatroom_id=' + this.$route.params.id)
          .then(response => {
              this.list_messages = response.data
          })
          .catch(error => {
            console.log(error)
          })
      },
      sendMessage() {
        axios.post('/messages', {message: this.message, chatroomId: this.$route.params.id})
          .then(response => {
            console.log('success')
            this.list_messages.push({
              message: this.message,
              created_at: new Date().toJSON().replace(/T|Z/gi,' '),
              user: this.$root.currentUserLogin,
              user_id: this.$root.currentUserLogin.id
            })
            this.message = ''
          })
          .catch(error => {
            console.log(error)
          })
      }
    }
  }
</script>

<style lang="scss" scoped>
//Nothing change
</style>

Giải thích:

  • Ở trên mình có 2 phương thức: 1 để lấy danh sách message của chatroom hiện tại, một để gửi đi một tin nhắn. Muốn lấy id của chatroom hiện tại mình sử dụng this.$route.params.id. Chú ý là sau khi thêm tin nhắn mới ta không load lại từ DB vì như thế sẽ tốn tài nguyên mà ta chỉ append tin nhắn mới đó vào mảng list_messages và Vue sẽ tự re-render lại cho ta
  • Phần code template bên trên chủ yếu là để hiển thị dữ liệu, bên cạnh đó mình cũng bind với class một số chỗ nhìn cho đẹp hơn. (các bạn xem có gì không hiểu comment cho mình nhé)

Ok ổn rồi đó. Giờ ta load lại trang, mở thêm một tab ẩn danh đăng nhập vào và thử chat, thì hiện tại chưa có realtime, nên mỗi khi có tin nhắn mới ta phải f5 lại mới có thể thấy.

Xử lý realtime và private channel

Ở series này mình sẽ sử dụng Laravel Echo, Laravel Echo server và Redis để xử lý phần realtime nhé các bạn. Bởi vì trên mạng xem tut hầu như toàn hướng dẫn bằng Pusher, mà Pusher cũng sẽ có limit một số thứ, nên mình sẽ hướng dẫn các bạn dùng Laravel-echo-server hoàn toàn free nhé 😃

Đầu tiên ta cài các package cần thiết nhé:

npm install --save laravel-echo laravel-echo-server socket.io-client

Sau đó ta cần khởi tạo laravel-echo-server bằng cách chạy command:

laravel-echo-server init

Các bạn chọn như sau nhé: Sau đó ta khởi động laravel-echo-server bằng command:

laravel-echo-server start

Sau đó ta mở resources/assets/js/bootstrap.js kéo xuống dưới cùng và thêm vào như sau:

import Echo from 'laravel-echo'

window.io = require('socket.io-client');

window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001'
}); 

Tiếp theo ta quay lại phần backend, tạo một event là PrivateMessage, event này sẽ được gọi mỗi khi có một user nhắn tin vào chatroom và chỉ những ai đang trong chatroom đó mới thấy. Ta tạo event bằng command:

php artisan make:event PrivateMessage

Sau đó ta mở app/events/PrivateMessage.php và sửa lại như sau:

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use App\Message;
use App\User;

class PrivateMessage implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;
    public $message;
    public $chatroomId;
    public $user;
    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct(Message $message, $chatroomId)
    {
        $this->user = User::find($message->user_id);
        $this->message = $message;
        $this->chatroomId = $chatroomId;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('chatroom.' . $this->chatroomId);
    }
}

Ở đây mỗi khi có tin nhắn mới gửi đến ta sẽ broadcast cho toàn bộ user ở trong chatroom với id xác định, dữ liệu gửi đi bao gồm: message, thông tin user, và id của chatroom.

Tiếp theo ta mở MessageController và sửa lại một chút hàm store như sau:

...
use App\Events\PrivateMessage;
...

public function store(Request $request) {
    $user = Auth::user();

    $message = $user->messages()->create([
        'message' => request()->get('message'),
        'chatroom_id' => request()->get('chatroomId')
    ]);

    broadcast(new PrivateMessage($message, request()->get('chatroomId')))->toOthers();

    return ['status' => 'OK'];
}

Ở đây mỗi khi ta insert vào một tin nhắn sau đó ta sẽ broadcast tin nhắn này cho những người khác trong phòng sử dụng toOthers

Còn một chút nữa thôi ^^

Vì ta sử dụng Private channel nên ta sẽ cần xác thực những user nào sẽ được broadcastbroadcast nhé. Ta mở file routes/channel.php thêm vào cuối như sau:

Broadcast::channel('chatroom.1', function ($user) {
    return $user->role === 'admin' || $user->role === 'employee';
});

Broadcast::channel('chatroom.2', function ($user) {
    return $user->role === 'saler' || $user->role === 'employee';
});

Ở trên, tin nhắn trong chatroom.1 sẽ chỉ broadcast cho admin và employee. Tương tự như với chatroom.2

Tiếp theo các bạn mở lại component Chatroom.vue, sau đó thêm vào hàm created như sau:

window.Echo.private('chatroom.' + this.$route.params.id)
.listen('PrivateMessage', (e) => {
    let message = e.message
    message.user = e.user
    this.list_messages.push(message)
});

Ở trên ta lắng nghe sự kiện từ phía Laravel, nếu có tin nhắn mới thì append ngay vào list_messages và hiển thị.

BƯỚC CUỐI CÙNG 😄. Ta mở file config/app.php kéo xuống dưới và bỏ comment dòng:

App\Providers\BroadcastServiceProvider::class,

Cuối cùng..... 😎 Đó là ta f5 lại trang, mở tab ẩn danh, thử đăng nhập 2 tài khoản adminemployee nhé.

Bùm!!! Laravel-echo-server báo lỗi không thể authenticate. Đây là lỗi mình đã nói ở đầu bài. Bởi vì ta cấu hình laravel-echo-server chưa đúng. Các bạn mở lại file laravel-echo-server.json sửa lại trường authHost như sau:

"authHost": "http://localhost:8000",

Sau đó ta khởi động lại laravel-echo-server nhé.

Thử load lại trang, cho 2 tài khoản join vào cùng một chatroom nếu thấy laravel-echo-server báo như sau là được nhé: Đến test rồi đó 😄. Thử chat thôi nào.

Đến đây khi ta thử chat thì không thấy gì xảy ra, không có tí realtime nào vẫn phải load lại. WTF ông đùa tôi đấy à, xem mòn đít đến giờ rồi????????

Việc cuối cùng ta cần làm là chạy queue nhé. Các bạn chạy command sau:

php artisan queue:work

Bây giờ load lại 2 tab trình duyệt một lần nữa. Thử chat. Yeahhh, cuối cùng cũng đã có realtime rồi 😄.

Ở trên ta đăng nhập bằng 2 tài khoản admin và employee ở chatroom 1. Giờ ta mở thêm một tab safari hoặc firefox đăng nhập tài khoản saler vào. Cho saler join vào chatroom 2. Bây giờ ta có 3 tab. Thử chat ở tab của admin hoặc employee ta thấy chỉ 2 user đó thấy còn saler không thấy gì cả. vì saler đang ở chatroom 2. Thế là thành công rồi nhé

Kết luận

Cám ơn các bạn đã đọc đến tận cùng bài này. Qua bài này hi vọng rằng các bạn đã hiểu được cách sử dụng private channel. Đồng thời biết cách sử dụng Laravel echo kết hợp Socket.IO, Redis, Laravel-echo-server. Vì đa số tut realtime ở trên mạng đều sử dụng pusher. Nhưng về lâu dài pusher sẽ yêu cầu ta trả phí nên mình quyết định dùng laravel-echo-server.

Ở bài tiếp theo mình sẽ hướng dẫn các bạn sử dụng Presence Channel để lấy thông tin các user có trong chatroom nhé.

Có gì thắc mắc các bạn để lại dưới comment nhé. Cám ơn các bạn đã theo dõi ^^!