Quay lại
Laravel Websockets với Nuxt - 2023

Gần đây mình được giao cho làm tính năng chat giữa khách hàng và cửa hàng , Backend thì dựa trên Laravel , còn Frontend thì dựa trên Nuxt 3, cái khó khăn là chưa làm với Nuxt bao giờ và chỉ có 2 ngày để giải quyết vấn đề này , hehe , không seo vẫn chiến tốt.

Bài hôm nay mình không nói nhiều về mặt logic bên trong của tính năng chat , mà chỉ giúp các bạn config giữa Laravel websocket và thằng Nuxt này , cũng vất vả để cài cắm nếu bạn nào không biết , thì gặp bài này của mình chắc vui lém !!

Tạo một dự án Laravel mới

laravel new api

Cấu hình cơ sở dữ liệu của bạn

DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=laravelwebsocketsnuxt
DB_USERNAME=alexgarrettsmith
DB_PASSWORD=
php artisan migrate
php artisan serve

Mở cái này trong trình duyệt của bạn với tên miền localhost. Vậy là xong việc cài dự án mới , bây giờ chúng ta sẽ sang cài Laravel websockets nhé !

Cài đặt Laravel Websockets

Tiếp theo, chúng ta sẽ cài đặt package Laravel Websockets.

Đầu tiên, cài đặt bằng Composer.

composer require beyondcode/laravel-websockets

Publish websocket statistics và migrate nó

php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"
php artisan migrate

Configuration file cho cái package này

php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"

Bởi vì chúng ta đang sử dụng Laravel Websockets để thay thế cho Pusher, nên chúng ta cũng cần phải sử dụng SDK PHP của Pusher.

composer require pusher/pusher-php-server

Sau đó thì vào .env và chuyển BROADCAST_DRIVER sang pusher.

BROADCAST_DRIVER=pusher

Khi sử dụng .env, chúng ta sẽ đặt ID App, key và secret cho Pusher. Đây là những gì chúng ta thường sử dụng để kết nối với Pusher, nhưng vì chúng ta đang thay thế Pusher bằng gói Laravel Websockets nên mọi giá trị sẽ hoạt động ở đây để phát triển dứoi local

PUSHER_APP_ID=local
PUSHER_APP_KEY=local
PUSHER_APP_SECRET=local
PUSHER_APP_CLUSTER=mt1

Bây giờ hãy truy cập config/broadcasting.php và cập nhật cấu hình pusher để truy cập máy chủ websocket local của chúng ta. Trong phần kết nối, cấu hình pusher sẽ trông như thế này.

'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'),
        'encrypted' => true,
        'host' => '127.0.0.1',
        'port' => 6001,
        'scheme' => 'http'
    ],
],

Hãy chạy máy chủ websocket và đảm bảo chúng ta có thể truy cập.

php artisan websockets:serve

Đảm bảo bạn vẫn chạy php artisan serve từ trước đó, hãy mở bảng điều khiển websocket trong trình duyệt của bạn. Điều này được tìm thấy tại http://localhost:8000/laravel-websockets.

Nhấn nút Kết nối và bạn sẽ thấy một cái gì đó như thế này.

Máy chủ websocket của bạn đã được định cấu hình đúng cách và chúng ta có thể kiểm tra việc gửi sự kiện.

Note: Khi các bạn nhìn màn hình log của websocket nó sẽ 1 thông báo lỗi như này 

Error message > Unhandled promise rejection with TypeError: Argument 1 passed to React\Http\Io\ClientRequestStream::closeError() must be an instance of Exception, instance of TypeError given, called in /Users/SoeMoe/DevProjects/titi/vendor/react/promise/src/Internal/RejectedPromise.php on line 73 in /Users/SoeMoe/DevProjects/titi/vendor/react/http/src/Io/ClientRequestStream.php:238

Để giải quyết được bugs này các bạn cài thêm package : react/promise

composer require react/promise:^2.3

Tùy vào package các bạn có thể cài các phiên bản khác nhau : "react/promise": "^3 || ^2.3 || ^1.2.1",

Tạo broadcashing Event

Broadcashing Event thì nó sẽ có 2 loại private channelpublic channel về thằng public channel mình sẽ không đề cập nó ở đây vì nó đơn giản hơn và dễ cấu hình . Bài viết này tập chung vào private channel nha .

Hãy tạo một sự kiện sẽ trả về tên của người dùng trên private channel cần xác thực.

<?php

namespace App\Events;

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 PrivateTest implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public $user;
    public function __construct($user)
    {
        $this->user = $user;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */

    public function broadcastWith(){
        return ['name'=> $this->user->first_name];
    }
    public function broadcastOn()
    {
        return new PrivateChannel('test-channel.'.$this->user->id);
    }
}

Sự kiện này sẽ lấy user model làm tham số và điểm khác biệt duy nhất ở đây là BroadcastOn() chúng a trả về PrivateChannel với tên kênh chứa Id người dùng để chỉ người dùng đó mới có thể đăng ký kênh này.

Hãy chỉnh sửa kênh của chúng ta trong route\channels.php

Broadcast::channel('test-channel.{id}', function ($user, $id) {
    return (int) $user->id === (int) $id;
});

Hãy tạo một route trong tệp Routes\web.php để chúng ta có thể kiểm tra sự kiện của mình.

Route::get('/broadcastPrivate',function(){
    $user = App\Models\User::find(5);
    broadcast(new PrivateTest($user));
    return "Event has been sent!";
});

Bây giờ user có id 5 có thể nhận được tin nhắn và bạn có thể truy cập /broadcastPrivate URL .

Authorizing Private Broadcast Channels

Tiếp theo chúng ta cần phải Authorizing Private Broadcast Channels , (với private channel thì mới cần xác thực thằng này nhé ! )

Có 2 cách xác thực để mình chỉ cho từng cách nhé !

Cách 1: Sử dụng Sanctum RESTful API

Thêm vào route\api.php đoạn code sau :

Broadcast::routes(['middleware' => ['auth:sanctum']]);

Sau khi các bạn thêm vào route thì các bạn sẽ thấy một endpoint được sinh ra là : domain/api/broadcasting/auth

Các bạn có thể kiểm chứng lại bằng lệnh: php artisan route:lis

Cách 2: Tự custom lấy 1 cái :

Tạo ra 1 Controller mới cho thằng pusher ví dụ : PusherController trong file này sẽ chứa đoạn code sau :

<?php
namespace App\Http\Controllers\Admin\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Broadcasting\Broadcasters\PusherBroadcaster;
use Illuminate\Http\Request;
use Pusher\Pusher;

class PusherController extends Controller
{
    /**
     * Authenticates logged-in user in the Pusher JS app
     * For presence channels.
     */
    public function postAuth(Request $request)
    {
        $pusher = new Pusher(
            config('broadcasting.connections.pusher.key'),
            config('broadcasting.connections.pusher.secret'),
            config('broadcasting.connections.pusher.app_id')
        );
        $broadcaster = new PusherBroadcaster($pusher);

        /*
         * Since the dashboard itself is already secured by the
         * Authorize middleware, we can trust all channel
         * authentication requests in here.
         */
        return $broadcaster->validAuthenticationResponse($request, []);
    }  

Tiếp theo chúng ta sẽ tạo một route cho custom auth pusher này :

Route::post('pusher/auth', [PusherController::class, 'postAuth'])->name('auth.check');

Ok vậy là xong cho phần auth này rồi nè !

À quên trước khi sang phần config cho thằng Nuxt thì các bạn nhớ :
Uncomment App\Providers\BroadcastServiceProvider::class, trong config\app.php tại thư mục providers nha 

Tạo Ứng dụng Nuxt mới

Nếu bạn muốn chạy thử thì làm các bước tạo mới này , có ứng dụng rồi thì bỏ qua việc tạo mới này nhá

npm init nuxt-app client

Khi Nuxt đã được cài đặt xong bạn chạy lệnh sau để chạy nó 

npm run dev

Laravel Echo With Nuxt

Laravel Echo có mọi thứ bạn cần cho một ứng dụng front-end Nuxt riêng biệt. Hãy cài đặt gói với pusher js.

npm i laravel-echo
npm i pusher-js

Ứng dụng Nuxt theo mặc định hỗ trợ tệp .env. vì vậy copy biến môi trường sau vào nhé ! 

VUE_APP_WEBSOCKETS_KEY=local
VUE_APP_WEBSOCKETS_SERVER=127.0.0.1

baseURL= http://127.0.0.1:8000

VUE_APP_WEBSOCKETS_KEY : Key này phải giống key server nhé để nó có thể đọc hiểu nhau được nha

VUE_APP_WEBSOCKETS_SERVER : Endpoint của Websocket nhé http://127.0.0.1:6001

Trong thằng Nuxt này nó đã có sẵn một thư mục plugins để chúng ta có thể config những package mục đích để phục vụ trong việc tái sử dụng trong các component một cách đơn giản. Để cấu hình được thằng Laravel echo này thì chúng ta cũng phải tạo một plugin và cấu hình nó . Các bạn hãy tạo một file với tên như sau: echo.client.ts

Chúng ta phải thêm đuôi là .client.ts nhá bởi vì sao ? Bởi vì chúng ta đang chạy dứoi chế độ Client Rendering , không phải SSR mà mặc định cái thằng Nuxt này nó lại là SSR, mình thử chạy với chế độ SSR rồi méo chạy được các bạn ạ vì sử dụng các biến global như window, $this , nó méo cho dùng thôi đành chuyển qua Client Rendering vậy. hehe

Trong file echo.client.ts chúng ta thêm đoạn code config sau :

import Echo from 'laravel-echo'
import Pusher from 'pusher-js'

import {
  AUTH_PUSHER,
  methodPost,
} from "~/constants/api";

declare global {
  interface Window {
    Pusher:any;
    Echo:any;
  }
}

export default defineNuxtPlugin(() => {
  window.Pusher = Pusher
  // Pusher.logToConsole = true;
  window.Echo = new Echo({
    enabledTransports: ['ws'],
    broadcaster: 'pusher',
    key: 'be1980513e2abfc70f78',
    wsHost: '127.0.0.1',
    wsPort: 6001,
    cluster: 'ap1',
    forceTLS: false,
    disableStats: true,
    authorizer: (channel: any, options: any) => {
      return {
          authorize: (socketId: any, callback: any) => {

                axios.post('domain/api/broadcasting/auth', {
                    socket_id: socketId,
                    channel_name: channel.name
                })
                .then(response => {
                    callback(null, response.data);
                })
                .catch(error => {
                    callback(error);
                });
          }
      };
  },
  })

  return {
    provide: {
      window: window
    }
  }
})

Đó !! Vậy là config xong rồi rùi nè , các bạn chỉ điển lại thông tin cấu hình nữa là được ! về cái chỗ authorize pusher bạn có thể chuyển sử dụng endpoint authorize custom pusher như mình đề cập ở bên trên nhé ! Mình dùng cách 2 tự custom hehe , nếu kết nối thành công nó sẽ trả về cho bạn một chuỗi string mã hoá nhé !

Để kiểm tra xem pusher đã được kết nối thành công hay chưa bởi vì mặc định nó méo show ra log đâu thì các bạn thêm đoạn log sau :

Pusher.logToConsole = true;

Trước khi sang cách sử dụng thì các bạn cấu hình thêm 1 bước nữa là config nuxt.config.ts:

Các bạn thêm đoạn code sau:

plugins: [
    .....
    { src: '~/plugins/echo.client.ts', ssr: false },
],

Ok bây giờ sang cách sử dụng thui nèo ! 

Trong component nào mà bạn muốn lắng nghe call websockets thì sử dụng như sau:

watch(
  () => {
    window.Echo.private("test-channel.1").listen('PrivateTest', (e: any) => {
       console.log(e);
    })
  }
);

Done ! Mình đã chạy thành công và chat thành công , Chúc các bạn thành công ! Cần giúp đỡ hãy comment bên dứoi nhé !

 
 
 

Bình luận (0)

Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough

Bài viết liên quan

Learning English Everyday