← Tình huống

Đồng bộ mọi tab và thiết bị: đăng xuất, quyền và giỏ hàng

Đăng xuất khắp nơi cùng lúc, áp dụng ngay vai trò bị thu hồi, và giữ giỏ hàng cùng cài đặt giống nhau trên mọi tab và thiết bị người dùng đang mở.

Người dùng hay mở ứng dụng của bạn ở nhiều tab và nhiều thiết bị, và mỗi nơi chỉ biết có thay đổi khi tải lại. Ai đó đăng xuất trên máy tính dùng chung, nhưng tab trên điện thoại vẫn hiện tài khoản của họ. Quản trị viên thu hồi quyền sửa của một đồng nghiệp, nhưng người đó vẫn sửa được cho đến khi tải lại. Thêm sản phẩm vào giỏ trên điện thoại, nhưng tab trên laptop vẫn báo giỏ trống. Với Hibira, máy chủ của bạn báo cho mọi nơi đang mở ứng dụng cùng một lúc.

Người dùng nhận được gì

  • "Đăng xuất khỏi mọi thiết bị" thật sự đóng mọi phiên đang mở ngay lập tức.
  • Đổi vai trò hay thu hồi quyền có hiệu lực mà không cần tải lại.
  • Giỏ hàng, cài đặt và số chưa đọc giống nhau ở mọi tab và mọi thiết bị.

Xây dựng bằng

  • Xác thực người dùng (pusher.signin()), như trong thông báo trong ứng dụng theo thời gian thực, để mỗi kết nối đã đăng nhập thuộc về một người dùng.
  • sendToUser, một lần gọi tới mọi tab và thiết bị của người dùng đó.
  • terminateUserConnections, đóng mọi kết nối đăng nhập bằng người dùng đó.

Có gì đó thay đổi với một người dùng

Máy chủ của bạnđăng xuất, vai trò mới, giỏ hàng
sendToUser()
Hibira
WebSocket
Mọi tab và thiết bịcủa người dùng đó

Đăng xuất khỏi mọi thiết bị

Máy chủ của bạnđã thu hồi phiên
ngắt tất cả
Hibira
đóng
Mọi kết nốiđăng nhập bằng người dùng đó
Một lần gọi từ máy chủ tới mọi nơi người dùng đang mở ứng dụng

Bước 1: đăng xuất khỏi mọi thiết bị

Thu hồi các phiên trong cơ sở dữ liệu trước, báo cho mọi nơi đang mở, rồi đóng kết nối:

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);
});

Các kết nối đã đóng không thể đăng nhập lại, vì endpoint xác thực người dùng của bạn không còn chấp nhận phiên đã thu hồi.

Bước 2: áp dụng thay đổi quyền ngay

Khi quản trị viên đổi vai trò của ai đó, gửi một tín hiệu. Trang sẽ lấy lại quyền từ máy chủ của bạn, nơi vẫn là nguồn sự thật duy nhất:

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

Bước 3: giữ giỏ hàng giống nhau ở mọi nơi

Sau mỗi thay đổi của giỏ hàng, gửi phần tóm tắt mà đầu trang hiển thị:

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);
});

Bước 4: phản hồi ở mọi tab

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));

Mẹo

  • Về bảo mật, đừng tin chính sự kiện: nó chỉ có nghĩa "hãy kiểm tra lại". API của bạn vẫn kiểm tra phiên và vai trò ở mọi yêu cầu.
  • BroadcastChannel đồng bộ các tab trong cùng một trình duyệt, nhưng không tới thiết bị khác và không nghe được thay đổi do quản trị viên hay máy chủ tạo ra.
  • Giỏ hàng của khách chưa đăng nhập có thể dùng một kênh riêng tư cho mỗi giỏ (private-cart-<id>), cấp quyền bằng cookie của giỏ.
  • Chỉ gửi số lượng và tổng tiền, không gửi cả giỏ; kích thước sự kiện tùy theo gói, xem bảng giá.