สร้าง Chat App มินิมอลใน 1 หน้ากระดาษ (ตัวอย่างการใช้งาน)
บทช่วยสอนนี้จะพาคุณเอาทุกอย่างตั้งแต่ HTML เปล่าๆ ต่อ WebSocket รับข้อความ ไปจับคู่กับ Node.js เล็กๆ สำหรับยืนยันรหัส Auth มารวมร่างเป็นแอปพลิเคชัน Real-time เบื้องต้น (ตัวอย่างนี้เราจะทำ "ห้องแชท") เพื่อแสดงให้เห็นว่าการเชื่อมต่อและรับส่งข้อความเข้าหากันสามารถเกิดขึ้นได้ในเสี้ยววินาที
🎯 เป้าหมาย: ทำความเข้าใจการส่ง Event แบบ Client-to-Client โดยสร้างหน้าเว็บให้ผู้ใช้พิมพ์ส่งข้อความคุยกันได้พร้อมสถานะกำลังพิมพ์ (Typing status)
🛠️ โครงสร้างที่ต้องเตรียม
- Frontend (
index.html): โค้ด HTML + JavaScript ธรรมดาๆ ไม่มี React/Vue ให้วุ่นวาย - Backend (
server.js): โค้ดสร้าง HMAC signature และ Token ให้ Client (ใช้ Node.js) - รหัส
pk_xxxx(Public Key) และsk_xxxx(Secret Key) ของโปรเจกต์คุณ
💻 1. สร้างฝั่ง Backend สำหรับสร้าง Token (Node.js)
ตั้งโฟลเดอร์รัน npm init -y และ npm install express cors
server.js
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
<!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) วางเรียงกัน เมื่อคนในจอซ้ายพิมพ์กดส่ง... ข้อความจะเด้งโผล่ที่จอขวาทุกจอในเสี้ยววินาที!!
นี่เป็นเพียง ตัวอย่างหนึ่ง ของการประยุกต์ใช้แพลตฟอร์ม คุณสามารถนำคอนเซปต์เดียวกันนี้ไปวาดกราฟ อัปเดตพิกัด หรือแสดงผลสถานะระบบได้ตามต้องการ!
