会員チャットデモに機能を追加します。自分以外の誰かがタイプし始めたら、それをリアルタイムで知らせる機能です。チャットなら必ずある機能です。※9/23日にコード修正変更あります!!
欲しい機能
私は太郎さんとして会員ログインをしてチャット画面にいます。花子さんが、送信メッセージをタイプし始めると以下のように、「花子さんがタイプしています…」のメッセージが表示されます。
花子さんがタイプしているときには上の「タイプしています…」は表示されますが、暫くタイプを休憩したら上の「タイプしています…」は消えます。その後花子さんはメッセージを送信するかもしれませんし、しないかもしれません。
これが欲しい機能です。
Pusherでの設定
プログラムを始める前に、PusherのアカウントにおいてEnable client eventsをオンとする必要あります。
今回は、このクライアントイベント(後に説明)の機能を使います。
ソースコード
今回の機能を追加したソースコードは、前回の会員チャットをもとにして違うgitブランチ(chat-whisper)としました。
レポジトリはこちらですが、すでにデモのソースコードをインストールしているなら、以下でスイッチできます。
$ git fetch $ git checkout chat-whisper
を実行してブランチをゲットできます。
masterとの差分は以下の3つのファイルのみです。
$ git diff master --name-status M resources/js/chat.js M resources/js/components/ChatForm.vue M resources/views/chat.blade.php
前回と同様に以下のコマンドを実行すると、ブラウザでテストが可能です。
$ npm run build $ php artisan serve
Whisper
まずクライアントのchat.jsでの変更を見てみましょう(最後に9/23の修正のコードがあるので比べてみてください)。
createApp({
setup () {
const messages = ref([]),
typer = ref(false),
typing = ref(false);
fetchMessages();
window.Echo.private('chat')
.listen('MessageSent', (e) => {
messages.value.push({
message: e.message.message,
user: e.user
});
})
.listenForWhisper('typing', (e) => {
// タイプしている会員名を受信して、「〇〇さんがタイプしています」を表示
typer.value = e.typer;
typing.value = true;
// 2秒経過したら、「〇〇さんがタイプしています」のメッセージを非表示
setTimeout(() => {
typer.value = false;
typing.value = false;
}, 2000);
});
...
function isTyping(e) {
// タイプを開始して300ms経過したら、タイプした会員名を送信
// 300msごとにイベントを送信することで、Pusherへの送信の回数を減らしています
// Pusherの制限は1秒に最高10まで
setTimeout(() => {
window.Echo.private('chat')
.whisper('typing', {
typer: e.user.name
});
}, 300);
}
...
前回チャットのソースコードと違うのは、プライベートチャンネルのchatのリスナーにおいて、自分宛てのメッセージのイベント(MessageEvent)だけでなく、listenForWhisper()の関数を用いて、typingのイベントも聞いていることです。そこでは、typingのイベントがあれば、画面に「〇〇さんがタイプしています」を表示します。
また、自身のキーボートのタイプのイベントをPusherのサーバーに送るために、isTyping()の関数の定義も追加されています。この関数はキーボードのタイプのイベントにバインドされていて(後に説明)、タイプを開始するとPusherのサーバーにタイプしている会員名を含んでtypingイベントとして送ります。
以下は、Pusher側でのDebug Consoleのログですが、client-typingのイベントをたくさん受信しているのがわかります。花子さんがタイプしていることがわかりますね。イベント名の、client-typingのclient-はLaravel Echoがプリフィックスしています。
掘り下げてvuejsの世界へ
chat.jsのisTypingでタイプしているイベントをPusherのサーバーに送っているのはわかりました。さて、その関数はどのように使われているか、掘り下げて見てみます。今度は、vuejsの世界です。
まず、以下のブレードで先のisTypingの関数がchat-formのコンポーネントのistypingというvueのカスタム定義のイベントにバインドされています。
...
<chat-form v-on:messagesent="addMessage" v-on:istyping="isTyping" :user="{{ auth()->user() }}"></chat-form>
...
そして、chat-formのコンポーネントのソースChatForm.vueを見ると、input文内で@keydown = "isTyping"でキーボードをタイプするイベント(keydown)とバインドしています。ややこしいですが、そこでのisTypingは先のとは違いコンポーネント内で定義の関数で、タイプしている会員オブジェクトをデータとしてistypingのイベントとして送信しています。ちなみに、@keydown.enter="sendMessage"の方は、メッセージをタイプした後に送信するためにEnterキーを押したときのイベントです。
...
function isTyping(e) {
emit("istyping", {
user: props.user
});
}
</script>
<template>
<div class="input-group">
<input
type="text"
class="form-control"
placeholder="メッセージをタイプしてください..."
v-model="newMessage"
@keydown = "isTyping"
@keydown.enter="sendMessage"
/>
...
</template>
クライアントイベント
今回は、Pusherのクライアントイベントを使用した機能です。クライアントイベントでは、クライアント(太郎や花子のブラウザ)とPusherサーバーのみの間でのWebsocketの通信で、ウェブサーバーを通してLaravelのプログラムにアクセスする通信(Http)がまったくありません。

修正
読者からの指摘により、9/23日以前にアップしたコードにおいて、chat.js(上に掲載したコード)に問題あることがわかりました。以下のように修正されています。以前のコードでも動作には問題ないですが、以下2点の問題ありました。
- 送り側がタイプし続けると、受け取り側の「〇〇さんがタイプしています」の表示において、表示と非表示がちらつく受け取り側の問題。解決には、設定したタイマーをクリアーして非表示にするタイマーを再度設定します。そうすることでいったん表示したメッセージをキープします。上の旧コードではその処理がありませんでした。
- 送り側がタイプし続けると、Pusherへの送信のイベント数の制限(1秒に10まで)を超える、送り側の問題。解決には、最初のタイプのイベントで1回Pusherに送信したら、その後300ms待ちます。そして、その間にはタイマーは作成しません。上の旧のコードではタイプする度にタイマーを作成していて送信回数の抑制とはなっておらず、Pusherでは上限を超えるイベントエラーとなっていました。
すでにコードをgithubからダウンロードしていたなら、ローカルのブランチを削除して再度ダウンロードをお願いします。
createApp({
setup () {
const messages = ref([]),
typer = ref(false),
typing = ref(false);
fetchMessages();
let listenTimer = false;
window.Echo.private('chat')
.listen('MessageSent', (e) => {
messages.value.push({
message: e.message.message,
user: e.user
});
})
.listenForWhisper('typing', (e) => {
// タイプしている会員名を受信して、「〇〇さんがタイプしています」を表示
typer.value = e.typer;
typing.value = true;
// 2秒待つ前にタイプされたら、前回のタイマーをクリアーして
// 表示・非表示の入れ替えを抑制します
if (listenTimer) {
clearTimeout(listenTimer);
}
// 2秒経過したら、表示を非表示に
listenTimer = setTimeout(() => {
typer.value = false;
typing.value = false;
}, 2000);
});
...
let whisperTimer = false;
function isTyping(e) {
if (whisperTimer === false) {
// タイプ開始したらすぐにタイプした会員名送信して、300ms待ちます
// その間のタイプのイベントは、上の条件文のためここのコードが実行されずに無視します
// こうすることで、Pusherへの送信の回数を減らしています
// Pusherの制限は1秒に最高10まで
window.Echo.private('chat').whisper('typing', {
typer: e.user.name
});
whisperTimer = setTimeout(() => {
whisperTimer = false;
}, 300);
}
}
...
メルマガ購読の申し込みはこちらから。



