Skip to content

สร้าง Chat App มินิมอลใน 1 หน้ากระดาษ (ตัวอย่างการใช้งาน)

บทช่วยสอนนี้จะพาคุณเอาทุกอย่างตั้งแต่ HTML เปล่าๆ ต่อ WebSocket รับข้อความ ไปจับคู่กับ Node.js เล็กๆ สำหรับยืนยันรหัส Auth มารวมร่างเป็นแอปพลิเคชัน Real-time เบื้องต้น (ตัวอย่างนี้เราจะทำ "ห้องแชท") เพื่อแสดงให้เห็นว่าการเชื่อมต่อและรับส่งข้อความเข้าหากันสามารถเกิดขึ้นได้ในเสี้ยววินาที

🎯 เป้าหมาย: ทำความเข้าใจการส่ง Event แบบ Client-to-Client โดยสร้างหน้าเว็บให้ผู้ใช้พิมพ์ส่งข้อความคุยกันได้พร้อมสถานะกำลังพิมพ์ (Typing status)


🛠️ โครงสร้างที่ต้องเตรียม

  1. Frontend (index.html): โค้ด HTML + JavaScript ธรรมดาๆ ไม่มี React/Vue ให้วุ่นวาย
  2. Backend (server.js): โค้ดสร้าง HMAC signature และ Token ให้ Client (ใช้ Node.js)
  3. รหัส pk_xxxx (Public Key) และ sk_xxxx (Secret Key) ของโปรเจกต์คุณ

💻 1. สร้างฝั่ง Backend สำหรับสร้าง Token (Node.js)

ตั้งโฟลเดอร์รัน npm init -y และ npm install express cors

server.js

javascript
const express = require('express');
const cors = require('cors');
const crypto = require('crypto');

const app = express();
app.use(cors()); // ยอมให้ HTML ข้ามโดเมนมาขอ Token ได้

// ⚠️ ให้ใช้ Secret Key จริงที่สร้างจาก Dashboard ตรงเมนู API Keys
const SECRET_KEY = 'sk_0kdn4nfj...'; // เปลี่ยนเป็นของคุณ

// ฟังก์ชันสร้าง ID สุ่มให้เสมือนมี User ใหม่
function makeRandomId() {
    return 'usr_' + Math.floor(Math.random() * 1000000);
}

// API Endpoint สำหรับให้ Client มาขอ Auth Token
app.get('/api/get-token', (req, res) => {
    const userId = makeRandomId();
    const nowMs = Date.now();
    const expMs = nowMs + (10 * 60 * 1000); // หมดอายุใน 10 นาที
    
    // 1. จัดก้อน Token (7 ด่านบังคับ)
    const tokenPayload = {
        v: "v1",
        purpose: "ws-auth",
        sub: userId,
        aud: "websocket",
        iat: nowMs,
        exp: expMs,
        jti: crypto.randomUUID()
    };
    
    // 2. เรียง payload ขึ้นบรรทัดใหม่
    const payloadString = `${tokenPayload.v}\n${tokenPayload.purpose}\n${tokenPayload.sub}\n${tokenPayload.aud}\n${tokenPayload.iat}\n${tokenPayload.exp}\n${tokenPayload.jti}`;
    
    // 3. sign ด้วย HMAC-SHA256
    const signature = crypto.createHmac('sha256', SECRET_KEY)
                            .update(payloadString)
                            .digest('hex');

    // ส่งกลับไปให้ Browser
    res.json({
        token: tokenPayload,
        signature: signature
    });
});

app.listen(3000, () => console.log('Backend รันอยู่ที่พอร์ต 3000!'));

รันคำสั่ง node server.js ทิ้งไว้หน้าต่างนึงเลย


📱 2. สร้างฝั่ง Frontend รับ-ส่ง (HTML/JS)

index.html

html
<!DOCTYPE html>
<html>
<head>
  <title>RawPush Chat</title>
  <style>
      body { font-family: sans-serif; max-width: 500px; margin: auto; padding: 20px;}
      #chat-box { border: 1px solid #ccc; height: 300px; overflow-y: scroll; padding: 10px; margin-bottom: 10px; background: #f9f9f9; }
      .msg { margin: 5px 0; padding: 5px; background: #fff; border-radius: 4px; border: 1px solid #eee; }
      .me { border-left: 3px solid #2196F3; }
      .other { border-left: 3px solid #FF9800; }
      .system { color: #888; font-size: 0.8em; text-align: center; }
      #typing { color: gray; font-size: 0.9em; height: 20px; }
  </style>
</head>
<body>

  <h1>ห้องแชทรวมมิตร 💬</h1>
  <div id="chat-box"></div>
  <div id="typing"></div>
  
  <input type="text" id="msg-input" placeholder="พิมพ์ข้อความที่นี่..." onkeyup="handleTyping()" />
  <button onclick="sendChat()">ส่งเลย!</button>

  <script>
    // ⚠️ ใช้ Public Key จริงจากหน้า Dashboard ของโปรเจกต์คุณ
    const PUBLIC_KEY = 'pk_c9x4n2k...'; 
    const CHANNEL = 'chat:global_room';
    
    let ws;
    let myUserId;
    
    // 1. ไปขอ Token ก่อน (จาก Node.js Backend ที่รันไว้)
    async function init() {
       const res = await fetch('http://localhost:3000/api/get-token');
       const data = await res.json();
       myUserId = data.token.sub; // จำ User ID ของเราไว้
       
       connectRawPush(data);
    }
    
    // 2. connect WebSocket ไปหา RawPush
    function connectRawPush(authData) {
       ws = new WebSocket(`wss://api.rawpush.com/ws?app_key=${PUBLIC_KEY}`);
       
       ws.onopen = () => {
           // connect สำเร็จ รีบส่ง auth ทันที (มีเวลา 10 วินาทีก่อนโดน timeout)
           ws.send(JSON.stringify({
               cmd: 'auth',
               token: authData.token,
               signature: authData.signature
           }));
       };
       
       ws.onmessage = (event) => {
           const msg = JSON.parse(event.data);
           
           // auth สำเร็จแล้ว subscribe channel เลย
           if (msg.type === 'reply' && msg.status === 'ok' && msg.data?.session_id) {
               ws.send(JSON.stringify({
                   cmd: 'subscribe',
                   channel: CHANNEL,
                   ref: 'my-sub'
               }));
           }
           
           // เมื่อมีอัปเดตใหม่ถูกบรอดแคสต์จาก Channel นี้ ส่งมาที่เรา
           if (msg.type === 'event' && msg.channel === CHANNEL) {
               
               // เคลียร์ Typing แบบมักง่าย
               document.getElementById('typing').innerText = '';
               
               // กรณี Event เป็นแชทข้อความ
               if (msg.event === 'chat.message') {
                   const isMe = msg.sender.user_id === myUserId;
                   const name = isMe ? "คุณ" : `User (${msg.sender.user_id.slice(-4)})`;
                   const cssClass = isMe ? "me" : "other";
                   
                   const chatBox = document.getElementById('chat-box');
                   chatBox.innerHTML += `<div class="msg ${cssClass}"><b>${name}:</b> ${msg.data.text}</div>`;
                   chatBox.scrollTop = chatBox.scrollHeight;
               }
               
               // กรณี Event เป็นคนกำลังพิมพ์
               if (msg.event === 'chat.typing' && msg.sender.user_id !== myUserId) {
                   document.getElementById('typing').innerText = `User (${msg.sender.user_id.slice(-4)}) กำลังพิมพ์...`;
               }
           }
       };
    }
    
    // 3. ฟังก์ชันกดส่งข้อความ
    function sendChat() {
       const input = document.getElementById('msg-input');
       const text = input.value;
       if (!text) return;
       
       // ยิง Publish ไปที่ช่องทาง WebSocket ตรงๆ 
       ws.send(JSON.stringify({
           cmd: 'publish',
           channel: CHANNEL, 
           event: 'chat.message', 
           data: { text: text },
           ref: 'my-pub'
       }));
       
       input.value = '';
    }

    // 4. ฟังก์ชันส่งสัญญาณตอนกำลังพิมพ์
    let typingTimer;
    function handleTyping() {
        ws.send(JSON.stringify({
           cmd: 'publish',
           channel: CHANNEL, 
           event: 'chat.typing', 
           data: { is_typing: true }
        }));
        
        // เคลียร์สถานะถ้าหยุดพิมพ์เกิน 2 วิ
        clearTimeout(typingTimer);
        typingTimer = setTimeout(() => {
             ws.send(JSON.stringify({
               cmd: 'publish',
               channel: CHANNEL, 
               event: 'chat.typing', 
               data: { is_typing: false }
            }));
        }, 2000);
    }

    // เรียกจุดเริ่มต้น
    init();
  </script>
</body>
</html>

🎉 ผลลัพธ์!

ลองเปิดไฟล์ index.html ของคุณดูสัก 3-4 หน้าต่าง (Browser Tabs) วางเรียงกัน เมื่อคนในจอซ้ายพิมพ์กดส่ง... ข้อความจะเด้งโผล่ที่จอขวาทุกจอในเสี้ยววินาที!!

นี่เป็นเพียง ตัวอย่างหนึ่ง ของการประยุกต์ใช้แพลตฟอร์ม คุณสามารถนำคอนเซปต์เดียวกันนี้ไปวาดกราฟ อัปเดตพิกัด หรือแสดงผลสถานะระบบได้ตามต้องการ!

Released under the MIT License.