← 使用场景

多标签页、多设备同步:退出登录、权限与购物车

一次退出所有地方、撤销的角色立即生效,并让购物车与设置在用户打开的每个标签页和设备上保持一致。

用户常在好几个标签页、好几台设备上开着你的应用,而每一份都要刷新才知道有变化。有人在公用电脑上退出了,手机上的标签页却还显示他的账号。管理员撤掉了同事的编辑权限,同事却能一直编辑到刷新为止。手机上把商品加进购物车,笔记本的标签页却还说购物车是空的。用 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>),用购物车的 cookie 授权。
  • 发数量和总额就好,不要发整个购物车;事件大小取决于方案,请看价格。