แชตพร้อมสถานะกำลังพิมพ์และสถานะออนไลน์
แชตฝ่ายบริการลูกค้า ข้อความระหว่างผู้ซื้อผู้ขาย และห้องแชตทีม: ข้อความมาถึงทันที สถานะ “กำลังพิมพ์…” ระหว่างเบราว์เซอร์ และรายชื่อผู้ออนไลน์ที่ถูกต้องเสมอ
แชตคือที่ที่ผู้ใช้รู้สึกถึงความหน่วงมากที่สุด ข้อความที่ขึ้นช้าไปหนึ่งวินาที หรือสถานะ “กำลังพิมพ์…” ที่ไม่เคยขึ้น ทำให้ผลิตภัณฑ์ดูเหมือนไม่มีใครดูแล แชตฝ่ายบริการลูกค้า ข้อความระหว่างผู้ซื้อกับผู้ขาย ห้องแชตทีม และแชตในเกม ต้องการสามอย่างเหมือนกัน คือ ข้อความที่มาถึงทันที สถานะกำลังพิมพ์ และรายชื่อผู้ออนไลน์ที่ตรงความจริง
สิ่งที่ผู้ใช้ได้รับ
- ข้อความขึ้นให้ทุกคนในห้องเห็นทันทีที่ส่ง ไม่ต้องรีโหลด
- “Ann กำลังพิมพ์…” ระหว่างที่มีคนเขียนตอบ
- จุดสีเขียวข้างชื่อคนที่อยู่ในบทสนทนาตอนนี้
ฟีเจอร์ที่ใช้
- ช่อง presence (
presence-) รักษารายชื่อสมาชิกในห้องให้ถูกต้องบนทุกโหนด - อีเวนต์ฝั่งไคลเอนต์ (
client-*,whisperของ Laravel Echo) ส่งสถานะกำลังพิมพ์จากเบราว์เซอร์ถึงเบราว์เซอร์โดยไม่ผ่านเซิร์ฟเวอร์ของคุณ - การยกเว้นซ็อกเก็ต (
socket_id,toOthers()ของ Laravel) ผู้ส่งจะไม่ได้รับข้อความของตัวเองซ้ำ - Webhook (
member_added,member_removed) เมื่อเซิร์ฟเวอร์ต้องตอบสนองต่อคนที่เข้าหรือออก
ขั้นที่ 1: ยืนยันสิทธิ์เข้าห้อง
มีแต่สมาชิกของบทสนทนาที่เข้าช่องของมันได้ เส้นทางยืนยันสิทธิ์ของคุณเป็นผู้ตัดสิน และส่งข้อมูลผู้ใช้สำหรับรายชื่อสมาชิกกลับไป:
app.post('/pusher/auth', requireLogin, express.urlencoded({ extended: false }), async (req, res) => {
const { socket_id, channel_name } = req.body;
const room = channel_name.replace(/^presence-room-/, '');
if (!(await db.rooms.isMember(room, req.user.id))) return res.sendStatus(403);
res.json(pusher.authorizeChannel(socket_id, channel_name, {
user_id: String(req.user.id),
user_info: { name: req.user.name, avatar: req.user.avatarUrl },
}));
});ขั้นที่ 2: เข้าห้อง แสดงรายชื่อสมาชิก และสถานะกำลังพิมพ์
เปิด อนุญาตอีเวนต์ฝั่งไคลเอนต์ ในการตั้งค่าแอป แล้ว:
const room = pusher.subscribe('presence-room-42');
room.bind('pusher:subscription_succeeded', (members) => {
members.each((m) => online.add(m.id, m.info));
});
room.bind('pusher:member_added', (m) => online.add(m.id, m.info));
room.bind('pusher:member_removed', (m) => online.remove(m.id));
// Typing: at most one event every 2 seconds while the user writes
let last = 0;
input.addEventListener('input', () => {
if (Date.now() - last < 2000) return;
last = Date.now();
room.trigger('client-typing', { name: me.name });
});
room.bind('client-typing', ({ name }) => typing.show(name, 3000));
room.bind('message', (m) => messages.append(m));ขั้นที่ 3: ส่งข้อความผ่านเซิร์ฟเวอร์
ข้อความผ่านเซิร์ฟเวอร์ของคุณ จึงถูกบันทึกและตรวจกลั่นกรองก่อนจะกระจายออกไป ส่ง socket ID ของผู้ส่งไปด้วย แท็บของผู้ส่งซึ่งแสดงข้อความอยู่แล้วจะถูกข้ามไป:
app.post('/rooms/:id/messages', requireLogin, async (req, res) => {
const msg = await db.messages.create({ room: req.params.id, user: req.user.id, text: req.body.text });
await pusher.trigger('presence-room-' + req.params.id, 'message', msg, { socket_id: req.body.socket_id });
res.json(msg);
});เบราว์เซอร์ส่ง pusher.connection.socket_id ไปพร้อมกับคำขอ
แบบเดียวกันใน Laravel
// routes/channels.php
Broadcast::channel('room.{id}', function ($user, $id) {
return $user->rooms()->whereKey($id)->exists() ? ['id' => $user->id, 'name' => $user->name] : false;
});
// In the controller
broadcast(new MessageSent($message))->toOthers();Echo.join(`room.${roomId}`)
.here((users) => online.set(users))
.joining((user) => online.add(user))
.leaving((user) => online.remove(user))
.listen('MessageSent', (e) => messages.append(e.message))
.listenForWhisper('typing', (e) => typing.show(e.name, 3000));
Echo.join(`room.${roomId}`).whisper('typing', { name: me.name });เคล็ดลับ
- ช่อง presence รับสมาชิกได้สูงสุด 100 คน สำหรับห้องที่ใหญ่กว่า เช่น แชตในไลฟ์สตรีม ดูที่การประมูลสดและผู้ชมจำนวนมาก
- จำกัดความถี่ของอีเวนต์ฝั่งไคลเอนต์ในเบราว์เซอร์ เพราะแต่ละการเชื่อมต่อมีขีดจำกัดอัตรา และแต่ละอีเวนต์นับเป็นหนึ่งข้อความ
- หากต้องการแจ้งคนที่ออกจากห้องไปแล้ว ให้ใช้ร่วมกับการแจ้งเตือนในแอปและพุช