Socket.ioで簡単なチャットアプリを作る

# 初めに
今回はsocket.ioを使って簡単なチャットアプリを作ります
## 実装する機能
今回は以下の機能を実装します
- ルームの作成
- ルームに参加する
- メッセージを送信する

# フォルダ構成
今回のフォルダ構成は以下の通りです。
```
---|
   |---public|
   |         |--script.js
   |         |--index.html
   |         |--style.css
   |     
   |---Servers|
   |          |--RoomServer.json   
   |
   |--server.js
   ```
# 準備
次に、プロジェクトのフォルダーに移動して、以下のコマンドを入力してください（自分の場合は、C:/Users/qqn5192/Downloads/socketIOchatになります）
```
npm install socket.io express cors fs path
```

# コードをコピー
以下のコードをコピペします
server.js:
```javascript:server.js
const { Server } = require('socket.io');
const path = require('path');
const express = require('express');
const fs = require('fs');
const cors = require('cors');

// Server Initialize
const app = express();
app.use(cors());
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));

const Sdirectory = path.join(__dirname, '/Servers/RoomServer.json');

// JSONファイルの初期化
if (!fs.existsSync(Sdirectory)) {
    fs.writeFileSync(Sdirectory, JSON.stringify([]));
}

app.get('/search', (req, res) => {
    const query = req.query.query;
    if (!query) {
        return res.status(400).json({ error: '検索クエリが指定されていません。' });
    }

    try {
        const data = fs.readFileSync(Sdirectory, 'utf8');
        const ParseData = JSON.parse(data);

        if (typeof ParseData.rooms !== 'object' || ParseData.rooms === null) {
            throw new TypeError('パースしたデータが無効です。');
        }

        const retDat = ParseData.rooms[query];

        if (retDat) {
            console.log(retDat);
            return res.json({ roomname: query, messages: retDat });
        } else {
            return res.status(404).json({ message: '一致するデータが見つかりません。' });
        }
    } catch (err) {
        console.error('JSONファイルの読み込みエラー:', err);
        return res.status(500).json({ error: 'サーバーエラーが発生しました。' });
    }
});


app.get('/home', (req, res) => {
    res.sendFile(path.join(__dirname, '/public/index.html'));
});
app.get('/socket', (req, res) => {
    res.sendFile(path.join(__dirname, 'node_modules', 'socket.io', 'client-dist', 'socket.io.js'))
})
const server = app.listen(3001, "<Your_Ipaddress>", () => { //Your_IPaddressには自分のIPv4アドレスを入力
    console.log('サーバーが3001番ポートで起動しています');
});

// Socket.io Initialization with CORS settings
const io = new Server(server, {
    cors: {
        origin: '*',
        methods: ['GET', 'POST'],
        allowedHeaders: ['Content-Type'],
        credentials: true
    }
});

io.on('connection', (socket) => {
    console.log('connection socket server on port 3001');

    // クライアントに過去のメッセージを送信
    try {
        const existingData = JSON.parse(fs.readFileSync(Sdirectory, 'utf-8'));
    } catch (err) {
        console.error('JSONファイルの読み込みエラー:', err);
    }

    // クライアントからの 'sdmessage' イベントをリスニング
    socket.on('sdmessage', (Mdata) => {
        if (!Mdata.content) {
            console.error('データが破損しているかクライアント側のコードが改造されています。');
        } else {
            console.log(Mdata);
            const MessageData = {
                name: Mdata.name,
                content: Mdata.content,
                timestamp: new Date().toISOString()
            };
    
            try {
                const existingData = JSON.parse(fs.readFileSync(Sdirectory, 'utf-8'));
    
                // ルームが存在しない場合、新たに作成
                if (!existingData.rooms) {
                    existingData.rooms = {};
                }
                if (!existingData.rooms[Mdata.roomname]) {
                    existingData.rooms[Mdata.roomname] = []; // 修正: 新しいルームに空配列を代入
                }
    
                // ルームに新しいメッセージを追加
                existingData.rooms[Mdata.roomname].push(MessageData);
    
                // JSONファイルに書き込む
                fs.writeFileSync(Sdirectory, JSON.stringify(existingData, null, 2));
            } catch (err) {
                console.error('JSONファイルの書き込みエラー:', err);
            }
    
            io.emit('message', MessageData);
        }
    });
    

    // 検索イベント 'test'
    socket.on('test', (Quer) => {
        try {
            const data = fs.readFileSync(Sdirectory, 'utf8');
            const ParseData = JSON.parse(data);
            const retDat = ParseData.find(SeaD => SeaD.roomname === Quer);

            if (retDat) {
                socket.emit('searchResult', retDat); // 結果をクライアントに個別送信
            } else {
                socket.emit('searchResult', { message: '一致するデータが見つかりません。' });
            }
        } catch (err) {
            console.error('JSONファイルの読み込みエラー:', err);
            socket.emit('searchResult', { error: 'サーバーエラーが発生しました。' });
        }
    });
    socket.on('CreateRoom', (CreateRoom) => {
        if(CreateRoom) {

        } else {

        }
    })
});
```
script.js:
```javascript:script.js

const socket = io('http://<Your_IPaddress>:3001'); //Your_IPaddressには自分のpcのIPv4アドレスを入力
let RoomID
// ルームIDの入力と検索
window.onload = async function() {
    AppendData('System', 'Connected server')

     RoomID = prompt('ルームIDを入力');
    if (RoomID) {
        console.log(`Room ID is ${RoomID}`);
        AppendData('Home/connection', `${RoomID}を検索しています。`);

        fetch(`/search?query=${encodeURIComponent(RoomID)}`, {
            method: 'GET'
        })
        .then(response => response.json())
        .then(data => {
            if (data && !data.error) {
                AppendData('SYSTEM', `ルームが見つかりました: ${data.roomname}`);
                document.title = data.roomname;
                document.getElementById('name').textContent = data.roomname;
                LoadMessages(data.messages)
            } else {
                AppendData('SYSTEM', '一致するデータが見つかりません。');
            }
        })
        .catch(err => {
            console.error('検索リクエスト中にエラーが発生しました:', err);
            AppendData('SYSTEM', '検索中にエラーが発生しました。');
        });
    }
}

function AppendData(chatname, chatcontent) {
    const chatBody = document.getElementById('chatBody');
    const messageElement = document.createElement('div');
    messageElement.classList.add('message');
    messageElement.textContent = `${chatname}: ${chatcontent}`;
    chatBody.appendChild(messageElement);
}

// サーバーからの過去メッセージを受信し、表示
function LoadMessages(messages) {
    if (!Array.isArray(messages)) {
        console.error('受信したメッセージが無効です:', messages);
        return; // エラーログを出力して処理を終了
    }

    const chatBody = document.getElementById('chatBody');
    messages.forEach(message => {
        const messageElement = document.createElement('div');
        messageElement.classList.add('message');
        messageElement.textContent = `${message.name}: ${message.content}`;
        chatBody.appendChild(messageElement);
    });
};

// 新しいメッセージをサーバーから受信して表示
socket.on('message', (MessageData) => {
    AppendData(MessageData.name, MessageData.content);
});

// 送信ボタンのクリックイベント
document.getElementById('sendButton').addEventListener('click', () => {
    const messageInput = document.getElementById('messageInput');
    const messageContent = messageInput.value.trim();
    if (messageContent) {
        const name = localStorage.getItem('name');
          const userName = name ? name : (localStorage.setItem('name', prompt('ユーザー名を入力')), localStorage.getItem('name'));
          
          const messageData = {
              roomname: RoomID,
              name: userName,
              content: messageContent
};
          
        socket.emit('sdmessage', messageData);
        messageInput.value = ''; // 入力欄をクリア
    }
});
```
index.html:
```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SocketIO_chat</title>
    <script src="/socket"></script>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="chat-container">
        <div class="chat-header">
            <h1 id="name">チャットアプリ</h1>
        </div>
        <div class="chat-body" id="chatBody">
            <!-- メッセージはここに追加される -->
        </div>
        <div class="chat-footer">
            <input type="text" class="message-input" id="messageInput" placeholder="メッセージを入力...">
            <button class="send-button" id="sendButton">送信</button>
        </div>
    </div>
    <script src="script.js" type="module"></script>
</body>
</html>
```
style.css:
```css:style.css
body {
    font-family: Arial, sans-serif;
    background-color: #121212; /* ダークモード背景色 */
    margin: 0;
    padding: 0;
    color: #e0e0e0; /* テキスト色 */
}
.chat-container {
    width: 900px; /* PC版に対応した幅 */
    margin: 50px auto;
    border: 1px solid #333;
    border-radius: 8px;
    background-color: #1e1e1e; /* ダークな背景色 */
    display: flex;
    flex-direction: column;
    height: 700px; /* PC版の高さ */
}
.chat-header {
    background-color: #333;
    color: #ffffff;
    padding: 15px;
    text-align: center;
    border-top-left-radius: 8px;
    border-top-right-radius: 8px;
}
.chat-body {
    flex: 1;
    padding: 15px;
    overflow-y: auto;
    border-bottom: 1px solid #333;
}
.message {
    margin-bottom: 10px;
    padding: 10px;
    border-radius: 8px;
    background-color: #2c2c2c; /* メッセージの背景色 */
}
.chat-footer {
    display: flex;
    border-top: 1px solid #333;
    padding: 10px;
    background-color: #1e1e1e;
}
.message-input {
    flex: 1;
    padding: 10px;
    border: 1px solid #333;
    border-radius: 8px;
    background-color: #2c2c2c; /* 入力フィールドの背景色 */
    color: #e0e0e0; /* 入力フィールドのテキスト色 */
}
.send-button {
    padding: 10px 15px;
    border: none;
    border-radius: 8px;
    background-color: #444;
    color: #e0e0e0;
    cursor: pointer;
}
.send-button:hover {
    background-color: #555;
}
```
RoomServer.json:
```json:RoomServer.json
{
  "rooms": {

  }
}
```
### 説明
今回はjson形式でデータを保存するようにしました。

# 最後に
今回はsocket.ioを使って簡単なチャットアプリを作りました。今回も、コードの修正をchatGPTにやってもらいました。
それではまたお会いしましょう！