# 多分頁、多裝置同步：登出、權限與購物車

> 一次登出所有地方、撤銷的角色立即生效，並讓購物車與設定在使用者開著的每個分頁和裝置上保持一致。

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

使用者常在好幾個分頁、好幾台裝置上開著你的應用，而每一份都要重新整理才知道有變化。有人在共用電腦上登出了，手機上的分頁卻還顯示他的帳號。管理員撤掉了同事的編輯權限，同事卻能一直編輯到重新整理為止。手機上把商品加進購物車，筆電的分頁卻還說購物車是空的。用 Hibira，你的伺服器能一次通知應用的每一份副本。

## 使用者得到什麼

- 「登出所有裝置」真的會立刻關閉每個打開的工作階段。
- 角色變更或權限撤銷不必重新整理就生效。
- 每個分頁、每台裝置上的購物車、設定和未讀數都一致。

## 用到的功能

- **使用者驗證**（`pusher.signin()`），做法同[即時站內通知](https://hibira.io/zh-tw/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>`），用購物車的 cookie 授權。
- 送數量和總額就好，不要送整個購物車；事件大小依方案而定，請看[價格](https://hibira.io/zh-tw/pricing)。
