[Laravel8.46.0] websocket通信できてるのにメッセージが返ってこない時

bootstrap.js


import Echo from 'laravel-echo';

window.Pusher = require('pusher-js');

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: process.env.MIX_PUSHER_APP_KEY,
    cluster: process.env.MIX_PUSHER_APP_CLUSTER,
    
    wsHost: window.location.hostname,
	wsPort: 8000,
	forceTLS: true
    // forceTLS: false,
});

view

<script src="/js/app.js"></script>
    <script>
    	new Vue({
    		el: '#chat',
    		data: {
    			message: '',
                messages: []
    		},
    		methods: {
                getMessages(){

                    const url = '/ajax/chat';
                    axios.get(url)
                        .then((response)=>{

                            this.messages = response.data;
                        })
                },
    			send(){

    				const url = '/ajax/chat';
    				const params = { message: this.message};
    				axios.post(url, params)
    					.then((response) => {
    						this.message = '';
    					});
    			}
    		},
            mounted(){
                this.getMessages();

                Echo.channel('chat')
                    .listen('MessageCreated', (e) =>{
                        this.getMessages();
                    });
            }
    	});
    </script>

メッセージを送信しても反応しないので、何があかんねん、と思っていたら、コンソールを見て、websocketのconnection failedが出ていなければ、socket通信はできている。

再度ソースコードを見直していたところ、EventでprivateChannelになっているところを、priveteを外したら反応するようになった。
Event/MessageCreated.php

    public function broadcastOn()
    {
        return new Channel('chat');
    }

一日中悩んでトラブルシューティングできなくて愕然としてたが、解決する時は一瞬だ。
さあ、チャットのフロント作るぞー

[Laravel8.46.0] laravel-echoとpusher-jsでchat機能を実装する

config/app.php
L BroadcastServiceProviderのコメントアウトを外します。

App\Providers\BroadcastServiceProvider::class,

クライアント側で必要なパッケージをインストール
$ npm install –save laravel-echo pusher-js

resources/js/bootstrap.js
L コメントアウトを外します

import Echo from 'laravel-echo';

window.Pusher = require('pusher-js');

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: process.env.MIX_PUSHER_APP_KEY,
    cluster: process.env.MIX_PUSHER_APP_CLUSTER,
    forceTLS: true
});

.env

BROADCAST_DRIVER=pusher
// 省略
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1

$ php artisan make:model Message -m

migrationfile
L rollbackしてもう一度作り直します。

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

Model
Message.php

    protected $fillable = [
        'user_id',
        'message',
    ];

    public function user(){

    	return $this->belongsTo('App\Models\User');
    }

User.php

    public function messages(){
        return $this->hasMany('App\Models\Message');
    }

php artisan make:controller ChatsController

route

Route::get('/post', [ChatsController::class, 'index']);
Route::get('/messages', [ChatsController::class, 'fetchMessages']);
Route::post('/messages', [ChatsController::class, 'sendMessage']);

ChatsController.php

use App\Models\Message;
use Illuminate\Support\Facades\Auth;
use App\Events\MessageSent;

class ChatsController extends Controller
{
    //
    public function __construct(){
    	$this->middleware('auth');
    }

    public function index(){
    	return view('chat.post');
    }

    public function fetchMessages(){
    	return Message::with('user')->get();
    }

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

    	$message = $user->messages()->create([
    		'message' => $request->input('message')
    	]);

    	event(new MessageSent($user, $message));

    	return ['status' => 'Message Sent!'];
    }
}

$ php artisan make:event MessageSent
MessageSent.php

use App\Models\User;
use App\Models\Message;

class MessageSent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;
    public $user,
    public $message;

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

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

routes/channels.php
L プライベートチャンネルをリッスンする場合は、channels.phpで許可する

Broadcast::channel('testApp', function($user){
	return Auth::check();
});

post.blade.php

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
	<link href="{{ mix('css/app.css')}}" rel="stylesheet" type="text/css">
	<meta name="csrf-token" content="{{ csrf_token() }}">
</head>
<body>
	<div id="app">
		<example-component></example-component>
	</div>

	<script src="{{ mix('js/app.js')}}"></script>
</body>
</html>

$ composer require laravel/ui
$ php artisan ui vue
$ npm install && npm run dev

/resources/js/components/ExampleComponent.vue

<template>
    <div>
        <ul>
            <li v-for="(message, key) in messages" :key="key">
                <strong>{{ message.user.name }}</strong>
                {{ message.message }}
            </li>
        </ul>
        <input v-model="text" />
        <button @click="postMessage" :disabled="!textExists">送信</button>
    </div>
</template>

<script>
    export default {
        data(){
            return {
                text: "",
                messages: []
            };
        },
        computed: {
            textExists(){
                return this.text.length > 0;
            }
        },
        created() {
            this.fetchMessages();
            Echo.private("testApp").listen("MessageSent", e=>{
                this.messages.push({
                    message: e.message.message,
                    user: e.user
                });
            });
        },
        methods: {
            fetchMessages(){
                axios.get("/messages").then(response =>{
                    this.messages = response.data;
                });
            },
            postMessage(message){
                axios.post("/messages", {message: this.text}).then(response => {
                    this.text = "";
                });
            }
        }
    }
</script>

あれ? 何やこれ。。。componentが上手く表示されんな。。。

[Laravel8.46.0] 8系でChat機能を実装したい1

$ php artisan –version
Laravel Framework 8.46.0
$ composer require pusher/pusher-php-server
$ php artisan make:model Message -m

migrationfile

    public function up()
    {
        Schema::create('messages', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->integer('sent_id')->index()->unsigned();
            $table->integer('recived_id')->index()->unsigned();
            $table->text('message');
            $table->timestamps();
        });
    }

model(Message.php)

    protected $fillable = [
        'sent_id',
        'recieved_id',
        'message',
    ];

$ php artisan serve –host 192.168.33.10 –port 8000

register画面からuserを3つぐらい作ります。

$ php artisan make:controller HomeController

route

use App\Http\Controllers\HomeController;

Route::get('/home', [HomeController::class, 'index']);

HomeController.php

use App\Models\User;
use Illuminate\Support\Facades\Auth;

class HomeController extends Controller
{
    //
    public function __construct(){
    	$this->middleware('auth');
    }

    public function index(){
    	$user = Auth::user();
    	$users = User::where('id','<>', $user->id)->get();

    	return view('chat.user_select', compact('users'));
    }
}

layout/app.blade.php
L bootstrap5を追加
L pusher.jsを追加

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">

// 省略
            <main>
                @yield('content')
            </main>
// 省略
<script src="https://js.pusher.com/7.0.3/pusher.min.js"></script>
<script
  src="https://code.jquery.com/jquery-3.6.0.min.js"
  integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
  crossorigin="anonymous"></script>
@yield('script')

user_select.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
	<div class="row">
		<div class="col-md-8 col-md-offset-2">
		</div>
	</div>

	<table class="table">
		<thead>
			<tr>
				<th>#</th>
				<ht>Name</ht>
				<th></th>
			</tr>
		</thead>
		<tbody>
		@foreach($users as $key => $user)
		<tr>
			<td>{{$loop->iteration}}</td>
			<td>{{$user->name}}</td>
			<td><a href="/chat/{{$user->id}}"><button type="button" class="btn btn-primary">Chat</button></a></td>
		</tr>
		</tbody>
	</table>
</div>
@endsection

$ php artisan make:controller ChatController

route

use App\Http\Controllers\ChatController;

Route::get('/chat/{recieved_id}', [ChatController::class, 'index'])->name('chat');
Route::post('/chat/send', [ChatController::class, 'store'])->name('chatSend');

chat/chat.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
	<div class="row">
		<div class="col-md-8 col-md-offset-2">
		</div>
	</div>

	<!-- チャットルーム -->
	<div id="room">
		@foreach($messages as $key => $message)
			@if($message->sent_id == \Illuminate\Support\Facades\Auth::id())
				<div class="send" style="text-align: right">
					<p>{{$message->message}}</p>
				</div>
			@endif

			@if($message->received_id == \Illuminate\Support\Facades\Auth::id())
				<div class="send" style="text-align: right">
					<p>{{$message->message}}</p>
				</div>
			@endif
		@endforeach
	</div>

	<form>
		<textarea name="message" style="width:100%"></textarea>
		<button type="button" id="btn_send" class="btn btn-primary">送信</button>
	</form>

	<input type="hidden" name="sent_id" value="{{$param['sent_id']}}">
	<input type="hidden" name="recieved_id" value="{{$param['recieved_id']}}">
	<input type="hidden" name="login" value="{{\Illuminate\Support\Facades\Auth::id()}}">
</div>
@endsection

@section('script')
<script>
	Pusher.logToConsole = true;

	var pusher = new Pusher('*', {
		cluster: '*',
		encrypted: true
	});

	var pusherChannel = pusher.subscribe('testApp');

	pusherChannel.bind('chat_event', function(data){

		let appendText;
		let login = $('input[name="login"]').val();

		if(data.sent_id === login){
			appendText = '<div class="send" style="text-align:right"><p>' + data.message + '</p></div> ';
		} else if(data.recieved_id === login){
			appendText = '<div class="recieve" style="text-align:left"><p>' + data.message + '</p></div> ';
		} else {
			return false;
		}

		$("#room").append(appendText);

		if(data.recieved_id === login){
			Push.creaet("新着メッセージ",
			{
				body: data.message,
				timeout: 8000,
				onClick: function(){
					window.focus();
					this.close();
				}
			})
		}
	});

	$.ajaxSetup({
		headers : {
			'X-CSRF-TOKEN' : $('meta[name="csrf-token').attr('content'),
		}
	});

	$('#btn_send').on('click', function(){
		$.ajax({
			type: 'POST',
			url: '/chat/send',
			data: {
				message : $('textarea[name="message"]').val(),
                sent_id : $('input[name="sent_id"]').val(),
                recieved_id : $('input[name="recieved_id"]').val(),
			}
		}).done(function(result){
			$('textarea[name="message"]').val('');
		}).fail(function(result){

		});
	});
</script>
@endsection

$ php artisan make:event ChatMessageRecieved
ChatMessageRecieved.php

public function __construct($request)
    {
        //
        $this->request = $request;
    }

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

    public function broadcastWith(){
        return [
            'message' => $this->request['message'],
            'sent_id' => $this->request['sent_id'],
            'recieved_id' => $this->request['recieved_id'],
        ];
    }

    public function broadcastAs(){
        return 'chat_event';
    }

あ、想定通りに動いてないけど、ちょっと思い出してきた。
pusherは基本jsでonclickでpostしてstoreし、eventを発火させて新しいデータを取得するんだった。
でも確かpusherのcrudentialは.envだった記憶があるんだが。。。