会員チャットデモに機能を追加します。今度は違う機能で、現在参加している会員リストをリアルタイムで表示します。会員がログインしたらその会員名が追加され、会員がログアウトしたら削除されます。これもグループチャットではよくある機能です。

欲しい機能

私が太郎さんとしてログインすると、上の右の場所に、現在チャットに参加している会員名をダイナミックでリストします。すでに花子さんがチャット画面を閲覧しています。

ここで、花子さんがログアウトすると上のリストから自動的に削除されます。今回は、この機能をPusherのプレゼンスチャンネル(後に説明)を使用して作成します。

ソースコード

今回の機能を追加したソースコードは、前回の会員チャットをもとにして違うgitブランチ(chat-presence)としました。

レポジトリはこちらですが、すでにデモのソースコードをインストールしているなら、以下でスイッチできます。

$ git fetch
$ git checkout chat-presence

を実行してブランチをゲットできます。

masterとの差分は以下の4つのファイルのみです。

$ git diff master --name-status
M       app/Events/MessageSent.php
M       resources/js/chat.js
M       resources/views/chat.blade.php
M       routes/channels.php

前回と同様に以下のコマンドを実行すると、ブラウザでテストが可能です。

$ npm run build
$ php artisan serve

サーバー側の変更

今回は、Pusherで使用するチャンネルの形態が変わるために、ブロードキャストするチャンネルがプライベートチャンネルからプレゼンスチャンネルに変わります。プレゼンスチャンネルは基本的にはプライベートチャンネルと同じですが、プライベートチャンネルと違って現時点誰が参加しているかを追跡することが可能です。

その変更のために以下の2つファイルが編集されています。

使用するチャンネルのクラスを、PrivateChannelからPresenceChannelに変えます。

...
     public function broadcastOn()
     {
         // return new PrivateChannel('chat');
         return new PresenceChannel('chat');
     }
...

プレゼンスチャンネルでは、認証の返し値は、プライベートチャンネルのOKならブーリアンはなく、認証された会員名を属性とした配列を返します。

...
 Broadcast::channel('chat', function ($user) {
    // return Auth::check();
     if (Auth::check()) {
         return ['name' => $user->name];
     }
 
     return false;
 });

クライアント側の変更

クライアントのchat.jsでも、プライベートチャンネル(private)からプレゼンスチャンネル(join)への変更あります。
また、会員の出入りの追跡のために、here(), joining(), leaving()が新たに使用されています。メッセージの受信(listen)は変わりません。

...
createApp({
    setup () {
        const messages = ref([]),
            members = ref([]); // 参加会員リスト

        fetchMessages();

        window.Echo.join('chat') // privateからjoinに
            .here(users => { // 現在のリスト
                members.value = users;
            })
            .joining(user => { // 参加した会員を追加
                members.value.push(user);
            })
            .leaving(user => { // 抜けた会員を削除
                members.value.splice(members.value.indexOf(user), 1);
            })
            .listen('MessageSent', (e) => { // こちらはそのまま使用可能
                messages.value.push({
                    message: e.message.message,
                    user: e.user
                });
            });
  ...

        return {
            members, // コンポーネントで利用できるように
            messages,
            fetchMessages,
            addMessage
        }
    }
})
.component('chat-messages', ChatMessages)
.component('chat-form', ChatForm)
.mount('#app');

ブレードでは以下の部分を追加しています。

...
        <div class="col-md-4">
            <div class="card">
                <div class="card-header">参加会員</div>
                <div class="card-body">
                    <ul>
                        <li v-for="member in members" v-text="member.name"></li>
                    </ul>
                </div>
            </div>
        </div>
...

プレゼンスチャンネル

PusherのDebugコンソールで、イベントのリストを見てみましょう。表示は新から旧への順番です。

リストを下から見ていくと、まず花子さんがログインすると、pusher_internal:member_addedとしてPusherのイベントが作成され接続している他の会員にWebsocketで知らせます。ログアウトすると、今度はpusher_internal:member_removedのイベントが作成されてPusherのサーバーが他の会員へ知らせます。

By khino