diff --git a/2026/board-advanced/completed/public/extra.js b/2026/board-advanced/completed/public/extra.js
index 94998966..241099b9 100644
--- a/2026/board-advanced/completed/public/extra.js
+++ b/2026/board-advanced/completed/public/extra.js
@@ -3,6 +3,36 @@
// script.js のあとに読み込むので、script.js の source や posts をそのまま使える。
// このファイルが途中で止まっても、script.js は動き続ける。
+// 2章の応用課題: 見ていない間に届いた件数をタブに出す
+const title = document.title;
+let unread = 0;
+
+function countUnread() {
+ if (!document.hidden) return;
+ unread = unread + 1;
+ document.title = `(${unread}) ${title}`;
+}
+function resetTitle() {
+ if (document.hidden) return;
+ unread = 0;
+ document.title = title;
+}
+
+source.addEventListener('message', countUnread);
+document.addEventListener('visibilitychange', resetTitle);
+
+// 3章の応用課題: 名前を呼ばれたら知らせる
+function notifyMention(e) {
+ const post = JSON.parse(e.data);
+ const me = document.getElementById('name-input').value;
+ if (me === '' || post.name === me) return;
+ if (!post.text.includes(me)) return;
+ alert(`${post.name}さんに呼ばれました: ${post.text}`);
+}
+
+source.addEventListener('message', notifyMention);
+
+// 4章の応用課題: 接続状態を画面に出す
const status = document.getElementById('status');
function showOnline() {
@@ -16,3 +46,10 @@ function showOffline() {
source.addEventListener('open', showOnline);
source.addEventListener('error', showOffline);
+
+// 5章の応用課題3: 接続数を画面に出す
+function showCount(e) {
+ document.getElementById('count').textContent = `${e.data}人が接続中`;
+}
+
+source.addEventListener('count', showCount);
diff --git a/2026/board-advanced/completed/public/index.html b/2026/board-advanced/completed/public/index.html
index 3cba6187..e7cf62d5 100644
--- a/2026/board-advanced/completed/public/index.html
+++ b/2026/board-advanced/completed/public/index.html
@@ -18,6 +18,7 @@
みんなの掲示板
diff --git a/2026/board-advanced/completed/public/script.js b/2026/board-advanced/completed/public/script.js
index b2c92058..db23df49 100644
--- a/2026/board-advanced/completed/public/script.js
+++ b/2026/board-advanced/completed/public/script.js
@@ -36,18 +36,12 @@ async function addPost() {
const name = document.getElementById('name-input').value;
const text = document.getElementById('text-input').value;
- const res = await fetch(`${API}/posts`, {
+ await fetch(`${API}/posts`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: name, text: text }),
});
- if (!res.ok) {
- const { message } = await res.json();
- alert(message);
- return;
- }
-
document.getElementById('text-input').value = '';
}
diff --git a/2026/board-advanced/completed/server.js b/2026/board-advanced/completed/server.js
index 6315d0c9..08c33caf 100644
--- a/2026/board-advanced/completed/server.js
+++ b/2026/board-advanced/completed/server.js
@@ -17,6 +17,21 @@ const posts = [];
// 現在の接続の一覧。接続した順に並ぶ。
const connections = [];
+// 15 秒ごとにコメント行を送り、データが流れない接続が切られないようにする
+function sendPing() {
+ for (let i = 0; i < connections.length; i++) {
+ connections[i].write(': ping\n\n');
+ }
+}
+setInterval(sendPing, 15000);
+
+// いまの接続数を、接続している全員に送る
+function sendCount() {
+ for (let i = 0; i < connections.length; i++) {
+ connections[i].write(`event: count\ndata: ${connections.length}\n\n`);
+ }
+}
+
const server = createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
@@ -33,10 +48,12 @@ const server = createServer(async (req, res) => {
res.flushHeaders();
connections.push(res);
console.log(`接続数: ${connections.length}`);
+ sendCount();
function removeConnection() {
connections.splice(connections.indexOf(res), 1);
console.log(`接続数: ${connections.length}`);
+ sendCount();
}
req.on('close', removeConnection);
return;
@@ -45,6 +62,11 @@ const server = createServer(async (req, res) => {
// 投稿を 1 件受け取る
if (req.method === 'POST' && url.pathname === '/posts') {
const post = await readPost(req);
+ if (post.text === '') {
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
+ res.end(JSON.stringify({ message: 'text が空です' }));
+ return;
+ }
posts.push(post);
if (posts.length > MAX_POSTS) posts.shift();