← กรณีการใช้งาน

ซิงก์ทุกแท็บและทุกอุปกรณ์: ออกจากระบบ สิทธิ์ และตะกร้าสินค้า

ออกจากระบบทุกที่ในครั้งเดียว ใช้สิทธิ์ที่ถูกเพิกถอนทันที และให้ตะกร้าสินค้ากับการตั้งค่าเหมือนกันในทุกแท็บและทุกอุปกรณ์ที่ผู้ใช้เปิดอยู่

ผู้ใช้มักเปิดแอปของคุณไว้หลายแท็บและหลายอุปกรณ์ และแต่ละที่จะรู้ว่ามีอะไรเปลี่ยนก็ต่อเมื่อรีโหลด มีคนออกจากระบบบนคอมพิวเตอร์ที่ใช้ร่วมกัน แต่แท็บบนมือถือยังแสดงบัญชีของเขาอยู่ แอดมินถอนสิทธิ์แก้ไขของเพื่อนร่วมทีม แต่เพื่อนร่วมทีมยังแก้ไขต่อได้จนกว่าจะรีโหลด ใส่สินค้าลงตะกร้าบนมือถือ แต่แท็บบนแล็ปท็อปยังบอกว่าตะกร้าว่าง เมื่อใช้ Hibira เซิร์ฟเวอร์ของคุณแจ้งทุกที่ที่เปิดแอปอยู่ได้ในครั้งเดียว

ผู้ใช้ของคุณได้อะไร

  • "ออกจากระบบทุกอุปกรณ์" ที่ปิดทุกเซสชันที่เปิดอยู่ทันทีจริง ๆ
  • การเปลี่ยนบทบาทหรือถอนสิทธิ์มีผลโดยไม่ต้องรีโหลด
  • ตะกร้า การตั้งค่า และจำนวนที่ยังไม่อ่านตรงกันทุกแท็บและทุกอุปกรณ์

สร้างด้วย

  • การยืนยันตัวผู้ใช้ (pusher.signin()) แบบเดียวกับการแจ้งเตือนในแอปแบบเรียลไทม์ ทุกการเชื่อมต่อที่ลงชื่อเข้าใช้จึงรู้ว่าเป็นของผู้ใช้คนไหน
  • sendToUser เรียกครั้งเดียวถึงทุกแท็บและทุกอุปกรณ์ของผู้ใช้คนนั้น
  • terminateUserConnections ปิดทุกการเชื่อมต่อที่ลงชื่อเข้าใช้เป็นผู้ใช้คนนั้น

มีบางอย่างเปลี่ยนสำหรับผู้ใช้คนหนึ่ง

เซิร์ฟเวอร์ของคุณออกจากระบบ บทบาทใหม่ ตะกร้า
sendToUser()
Hibira
WebSocket
ทุกแท็บและอุปกรณ์ของผู้ใช้คนนั้น

ออกจากระบบทุกอุปกรณ์

เซิร์ฟเวอร์ของคุณยกเลิกเซสชันแล้ว
ตัดทั้งหมด
Hibira
ปิด
ทุกการเชื่อมต่อที่ลงชื่อเข้าใช้เป็นผู้ใช้คนนั้น
การเรียกครั้งเดียวจากเซิร์ฟเวอร์ ถึงทุกที่ที่ผู้ใช้เปิดแอปอยู่

ขั้นตอนที่ 1: ออกจากระบบทุกอุปกรณ์

ยกเลิกเซสชันในฐานข้อมูลก่อน แจ้งทุกที่ที่เปิดแอปอยู่ แล้วจึงปิดการเชื่อมต่อ:

app.post('/account/sign-out-everywhere', requireLogin, async (req, res) => {
  const id = String(req.user.id);
  await db.sessions.revokeAll(id);
  await pusher.sendToUser(id, 'signed-out', {});
  await pusher.terminateUserConnections(id);
  res.sendStatus(204);
});

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

ขั้นตอนที่ 2: ให้การเปลี่ยนสิทธิ์มีผลทันที

เมื่อแอดมินเปลี่ยนบทบาทของใครสักคน ให้ส่งสัญญาณไป หน้าเว็บจะดึงสิทธิ์จากเซิร์ฟเวอร์ใหม่ ซึ่งยังเป็นแหล่งความจริงเพียงแห่งเดียว:

await db.members.update(member.id, { role: 'viewer' });
await pusher.sendToUser(String(member.userId), 'permissions-changed', {});

ขั้นตอนที่ 3: ให้ตะกร้าเหมือนกันทุกที่

ทุกครั้งที่ตะกร้าเปลี่ยน ให้ส่งสรุปที่ส่วนหัวของหน้าแสดง:

app.post('/api/cart/items', requireLogin, async (req, res) => {
  const cart = await db.carts.add(req.user.id, req.body);
  await pusher.sendToUser(String(req.user.id), 'cart', { count: cart.count, total: cart.total });
  res.json(cart);
});

ขั้นตอนที่ 4: ตอบสนองในทุกแท็บ

pusher.user.bind('signed-out', () => location.assign('/login'));
pusher.user.bind('permissions-changed', async () => {
  applyPermissions(await fetch('/api/me/permissions').then((r) => r.json()));
});
pusher.user.bind('cart', ({ count, total }) => cartBadge.set(count, total));

เคล็ดลับ

  • ด้านความปลอดภัย อย่าเชื่ออีเวนต์โดยตรง มันแค่บอกว่า "ให้ตรวจใหม่" API ของคุณยังต้องตรวจเซสชันและบทบาทในทุกคำขอ
  • BroadcastChannel ซิงก์แท็บในเบราว์เซอร์เดียวกันได้ แต่ไม่ถึงอุปกรณ์อื่น และไม่รู้การเปลี่ยนแปลงจากแอดมินหรือเซิร์ฟเวอร์
  • ตะกร้าของผู้ใช้ที่ไม่ได้ลงชื่อเข้าใช้ ใช้ช่องส่วนตัวต่อตะกร้า (private-cart-<id>) และอนุญาตด้วยคุกกี้ของตะกร้าได้
  • ส่งแค่จำนวนและยอดรวม ไม่ต้องส่งทั้งตะกร้า ขนาดของอีเวนต์ขึ้นอยู่กับแพ็กเกจ ดูที่ราคา