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