18 章の fetch は「こちらから聞いて、答えをもらう」形です。チャットや通知のように、相手の都合で届く知らせをすぐ受け取るには、別の仕組みを使います。
WebSocket と Server-Sent Events は、受け持つサーバが必要なので、このページでは書き方を紹介するだけにします。同じオリジンのタブ間で送り合う BroadcastChannel と、ブラウザ同士がつながる WebRTC は、このページの中で動かせます。
BroadcastChannel は、同じオリジンで開いている別のタブやウィンドウに、メッセージを送る仕組みです。サーバは要りません。まず仕組みだけを見ます。別のタブの代わりに、同じページの中にもう 1 つ受け手を作っています。
スクリプト
const me = new BroadcastChannel("jsl-demo");
const other = new BroadcastChannel("jsl-demo"); // 別のタブの代わり
other.addEventListener("message", (event) => {
console.log("受け取った:", event.data.text);
});
me.postMessage({ text: "こんにちは" });
受け取った: こんにちは
次はチャットの画面です。このページをもう 1 つのタブで開き、両方で「▶ 実行」を押してから、片方で書き込んでみてください。もう片方に届きます。
body の中身
<form id="form">
<input id="text" placeholder="メッセージ" autocomplete="off">
<button>送る</button>
</form>
<ul id="log"></ul>
スクリプト
const channel = new BroadcastChannel("jsl-chat");
const log = document.querySelector("#log");
const name = `タブ${Math.floor(Math.random() * 90 + 10)}`;
function show(who, text) {
const li = document.createElement("li");
li.textContent = `${who}: ${text}`;
log.append(li);
}
channel.addEventListener("message", (event) => show(event.data.who, event.data.text));
document.querySelector("#form").addEventListener("submit", (event) => {
event.preventDefault();
const input = document.querySelector("#text");
if (input.value.trim() === "") return;
channel.postMessage({ who: name, text: input.value });
show(`${name}(自分)`, input.value);
input.value = "";
});
サーバとつなぎっぱなしにして、どちらからでもいつでも送れます。チャットのサーバにつなぐブラウザ側のコードは、次のような形です。
const socket = new WebSocket("wss://chat.example.com/room/1");
socket.addEventListener("open", () => {
socket.send(JSON.stringify({ type: "join", name: "ゲスト" }));
});
socket.addEventListener("message", (event) => {
const msg = JSON.parse(event.data);
console.log(`${msg.name}: ${msg.text}`);
});
socket.addEventListener("close", () => console.log("切れました"));
サーバ側は、Node.js なら ws などのパッケージ(10 章の npm)を使って書けます。
サーバからの一方通行でよいなら、もっと簡単です。ブラウザ側は EventSource で受け取ります。
const events = new EventSource("/notifications");
events.addEventListener("message", (event) => {
console.log("お知らせ:", event.data);
});
サーバは、つないだままのレスポンスに「data: 本文」と空行を書き足していくだけです。Node.js の node:http(14 章の serve.mjs と同じ道具)だけで書けます。
res.writeHead(200, { "content-type": "text/event-stream" });
const id = setInterval(() => res.write(`data: ${new Date().toISOString()}\n\n`), 1000);
req.on("close", () => clearInterval(id));
WebRTC は、ブラウザ同士がサーバを通さずに直接データや映像を送り合う仕組みです。ビデオ会議 ・ 画面共有 ・ ファイルの受け渡しに使われます。
次の例は、仲介役の代わりに、同じページの中で 2 つの接続(a と b)を作って直接つなぎ、文字を送っています。本物のアプリでは、offer と answer を WebSocket などで相手に届けます。
スクリプト
const a = new RTCPeerConnection();
const b = new RTCPeerConnection();
a.addEventListener("icecandidate", (e) => e.candidate && b.addIceCandidate(e.candidate));
b.addEventListener("icecandidate", (e) => e.candidate && a.addIceCandidate(e.candidate));
const channel = a.createDataChannel("chat");
channel.addEventListener("open", () => channel.send("WebRTC でこんにちは"));
b.addEventListener("datachannel", (e) => {
e.channel.addEventListener("message", (m) => console.log("b が受け取った:", m.data));
});
// つなぎ方の情報を交換する(本物では仲介役が受け渡す)
const offer = await a.createOffer();
await a.setLocalDescription(offer);
await b.setRemoteDescription(offer);
const answer = await b.createAnswer();
await b.setLocalDescription(answer);
await a.setRemoteDescription(answer);
setTimeout(() => {
a.close();
b.close();
}, 2000); // 2 秒後に切る
b が受け取った: WebRTC でこんにちは