JavaScript+ScaledroneでWebSocketの同期処理を作る
はじめに
WebSocketをベースとしたリアルタイムメッセージサービス「Scaledrone」を使うと、
自分でWebSocket用サーバーを用意せず、簡単に双方向通信処理を作ることができます。
以前、Next.js(React)を使ってチャットアプリを作る記事を投稿しましたが、
今回はJavaScriptによる双方向通信処理で、フォームの同時編集機能を作ってみます。
DEMO
テキスト入力欄にカーソルを入れると、他者画面に「他のメンバーが入力中」と表示します。
カーソルを外すとメッセージを送受信し、他者画面にメッセージの内容が反映されます。
入力状況や入力文字を、リアルタイムにWebSocketで通信しています。
サンプルコード
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Scaledrone WebSocket Example</title>
<script src="https://cdn.scaledrone.com/scaledrone.min.js"></script>
<script src="websocket-example.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<textarea id="textarea" placeholder="メッセージを入力"></textarea>
<div id="statusarea"></div>
</body>
</html>
'use strict';
const CHANNEL_ID = '{チャンネルID}';
const ROOM_NAME = '{任意のルーム名}';
const drone = new Scaledrone(CHANNEL_ID);
const TEXTAREA_ID = 'textarea';
const STATUSAREA_ID = 'statusarea';
let inputElement;
let editingStatusElement;
document.addEventListener('DOMContentLoaded', init);
function init() {
inputElement = document.getElementById(TEXTAREA_ID);
inputElement.addEventListener('focus', handleFocusIn);
inputElement.addEventListener('focusout', handleFocusOut);
editingStatusElement = document.getElementById(STATUSAREA_ID);
initWebSocket();
}
function initWebSocket() {
drone.on('open', handleWebSocketOpen);
drone.on('error', handleWebSocketError);
const room = drone.subscribe(ROOM_NAME);
room.on('open', handleRoomOpen);
room.on('message', receiveMessage);
}
function handleWebSocketOpen(error) {
if (error) {
return console.error(error);
}
}
function handleRoomOpen(error) {
if (error) {
return console.error(error);
}
console.log('Connected to room');
}
function handleWebSocketError(error) {
console.error('Error with connection:', error);
}
function handleFocusIn() {
sendMessage(inputElement.value, true);
}
function handleFocusOut() {
sendMessage(inputElement.value, false);
}
function sendMessage(text, isInputting) {
drone.publish({
room: ROOM_NAME,
message: {text, isInputting}
});
}
function receiveMessage(message) {
let statusText = '';
if (!message.data.isInputting) {
inputElement.value = message.data.text;
inputElement.classList.remove('inputting');
editingStatusElement.textContent = statusText;
return;
}
inputElement.classList.add('inputting');
if (message.clientId !== drone.clientId) {
statusText = '※他のメンバーが入力中';
}
editingStatusElement.textContent = statusText;
}
#textarea {
font-size: 18px;
height: 100px;
width: 400px;
}
#textarea.inputting {
background-color: #ffffee;
}
#statusarea {
color: #ff6666;
}
解説
主にWebSocketの利用に関わる設定や処理を、抜粋してご紹介します。
【1】Scaledroneの準備
Scaledroneのアカウントを作成し、メッセージ中継用のチャンネルを作成します。
- scaledrone.comの「CREATE ACCOUNT」からアカウントを作成します。
- ログインし、Scaledroneのダッシュボードにアクセスします。
- 「+Create channel」ボタンをクリックします。
- 以下を入力して、チャンネルを作成します。
- チャンネル名(Choose a name): 任意
- 認証(Authentication): 不要(Never require authentication)
- メッセージ履歴(Message history): 無効(Disable message history)
- 「Channel overview」画面に表示されるチャンネルIDを、コピーして控えます。
【2】Scaledroneライブラリの読み込み
Scaledroneのライブラリを、CDNから読み込みます。
<script src='https://cdn.scaledrone.com/scaledrone.min.js'></script>
【3】初期化と接続開始
Scaledroneで作成したチャンネルのIDと任意のルーム名を設定した後、
Scaledroneのチャンネル、及びルームに接続します。
WebSocketのメッセージはルーム宛に送信し、ルーム参加者に共有されます。
const CHANNEL_ID = '{チャンネルID}';
const ROOM_NAME = '{任意のルーム名}';
const drone = new Scaledrone(CHANNEL_ID);
function initWebSocket() {
drone.on('open', handleWebSocketOpen);
drone.on('error', handleWebSocketError);
const room = drone.subscribe(ROOM_NAME);
room.on('open', handleRoomOpen);
room.on('message', receiveMessage);
}
open
で接続成功時、error
で接続失敗時、
message
でメッセージ受信時のイベントを定義します。
また、テキスト入力欄にカーソルを入れた時と、外した時にメッセージを送信したいので、
addEventListenerでfocus
とfocusout
のイベントを設定しておきます。
function init() {
inputElement = document.getElementById(TEXTAREA_ID);
inputElement.addEventListener('focus', handleFocusIn);
inputElement.addEventListener('focusout', handleFocusOut);
editingStatusElement = document.getElementById(STATUSAREA_ID);
initWebSocket();
}
【4】メッセージの送信処理
カーソルを入れた時はhandleFocusIn()
で「入力中」という状況を送信し、
カーソルを外した時はhandleFocusOut()
で「非入力中」の状況と入力文字を送信します。
function handleFocusIn() {
sendMessage(inputElement.value, true);
}
function handleFocusOut() {
sendMessage(inputElement.value, false);
}
function sendMessage(text, isInputting) {
drone.publish({
room: ROOM_NAME,
message: {text, isInputting}
});
}
送信する内容は構造体で自由に定義可能で、publish()
で送信をおこないます。超シンプル♪
【5】メッセージの受信処理
メッセージ受信イベントとして定義したreceiveMessage()
に、
受信メッセージの内容を画面に反映させる処理を記述します。
今回は、以下の処理を書きました。
- 入力中の場合
- テキスト入力欄をCSSで着色する。
- 他者が入力中の場合、「他のメンバーが入力中」と表示する。
- 入力が終わった場合
- テキスト入力欄の値を、受信した内容で書き換える。
- 着色のCSSを外し、テキスト入力欄を白に戻す。
- 「他のメンバーが入力中」の表示を消す。
function receiveMessage(message) {
let statusText = '';
if (!message.data.isInputting) {
inputElement.value = message.data.text;
inputElement.classList.remove('inputting');
editingStatusElement.textContent = statusText;
return;
}
inputElement.classList.add('inputting');
if (message.clientId !== drone.clientId) {
statusText = '※他のメンバーが入力中';
}
editingStatusElement.textContent = statusText;
}
チャンネル接続時に自動発行される自分のクライアントIDdrone.clientId
と
メッセージ送信者のクライアントIDmessage.clientId
を比較し、
他者が送信したメッセージかどうかを判断しています。
また、受信メッセージの本文は、message.data
から取得しています。
その他、メッセージのユニークIDはmessage.id
、
送信日時はmessage.timestamp
で取得し、利用することもできます。
おわりに
DEMOでは2画面での操作でしたが、3名以上でも同期可能です。
「〇〇さんが入力中」と名前を表示させると、より同時編集の状況が分かりやすいですね。
WebSocket用のサーバーを構築せず、お手軽にリアルタイムの同期処理を実装できるので
やはりScaledroneは非常に便利なサービスでした。
以下が、JavaScriptで実装する場合の公式ドキュメントです。
Java、Swift、React Native、Node.js、Go、Ruby、Python、PHP、RESTの
ライブラリやAPIクライアントも用意されているので、
また別の機会でもWebSocketを試してみたいと思います。
Discussion