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

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

Source: https://hibira.io/th/use-cases/multi-tab-sync

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

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

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

## สร้างด้วย

- **การยืนยันตัวผู้ใช้** (`pusher.signin()`) แบบเดียวกับ[การแจ้งเตือนในแอปแบบเรียลไทม์](https://hibira.io/th/use-cases/in-app-notifications) ทุกการเชื่อมต่อที่ลงชื่อเข้าใช้จึงรู้ว่าเป็นของผู้ใช้คนไหน
- **`sendToUser`** เรียกครั้งเดียวถึงทุกแท็บและทุกอุปกรณ์ของผู้ใช้คนนั้น
- **`terminateUserConnections`** ปิดทุกการเชื่อมต่อที่ลงชื่อเข้าใช้เป็นผู้ใช้คนนั้น

```text
flow: การเรียกครั้งเดียวจากเซิร์ฟเวอร์ ถึงทุกที่ที่ผู้ใช้เปิดแอปอยู่
== มีบางอย่างเปลี่ยนสำหรับผู้ใช้คนหนึ่ง
เซิร์ฟเวอร์ของคุณ|ออกจากระบบ บทบาทใหม่ ตะกร้า --`sendToUser()`--> *Hibira --WebSocket--> ทุกแท็บและอุปกรณ์|ของผู้ใช้คนนั้น
== ออกจากระบบทุกอุปกรณ์
เซิร์ฟเวอร์ของคุณ|ยกเลิกเซสชันแล้ว --ตัดทั้งหมด--> *Hibira --ปิด--> ทุกการเชื่อมต่อ|ที่ลงชื่อเข้าใช้เป็นผู้ใช้คนนั้น
```

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

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

```js
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: ให้การเปลี่ยนสิทธิ์มีผลทันที

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

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

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

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

```js
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: ตอบสนองในทุกแท็บ

```js
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>`) และอนุญาตด้วยคุกกี้ของตะกร้าได้
- ส่งแค่จำนวนและยอดรวม ไม่ต้องส่งทั้งตะกร้า ขนาดของอีเวนต์ขึ้นอยู่กับแพ็กเกจ ดูที่[ราคา](https://hibira.io/th/pricing)
