← Use cases

Keep tabs and devices in sync: sign-out, permissions and carts

Sign out everywhere at once, apply a revoked role right away, and keep carts and settings the same in every tab and device the user has open.

People keep your app open in several tabs and on several devices, and each copy only learns about changes when it reloads. Someone signs out on a shared computer, but the tab on their phone still shows their account. An admin removes a teammate's edit rights, but the teammate keeps editing until they refresh. A product goes into the cart on the phone, and the laptop tab still says the cart is empty. With Hibira, your server tells every open copy of the app at once.

What your users get

  • "Sign out everywhere" that really closes every open session, right away.
  • A changed role or a revoked permission that applies without a reload.
  • The same cart, settings and unread counts in every tab and on every device.

Built with

  • User authentication (pusher.signin()), as in live in-app notifications, so each signed-in socket belongs to a user.
  • sendToUser, which reaches every tab and device of that user with one call.
  • terminateUserConnections, which closes every socket signed in as that user.

Something changes for one user

Your serversign-out, new role, cart
sendToUser()
Hibira
WebSocket
Every tab and deviceof that user

Sign out everywhere

Your serversessions revoked
close all
Hibira
closes
Every socketsigned in as that user
One call from your server reaches every copy of the app the user has open

Step 1: sign out everywhere

Revoke the sessions in your database first, tell every open copy, then close the sockets:

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

The closed sockets cannot sign in again, because your user authentication endpoint no longer accepts the revoked session.

Step 2: apply a permission change at once

When an admin changes someone's role, send a nudge. The page fetches its permissions again from your server, which stays the source of truth:

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

Step 3: keep the cart the same everywhere

After every change to the cart, send the summary the header shows:

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

Step 4: react in every 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));

Tips

  • Never trust the event itself for security: it only says "check again". Your API still checks the session and the role on every request.
  • BroadcastChannel syncs tabs of one browser, but not other devices, and it cannot hear changes made by an admin or by your server.
  • Guest carts can use a private channel per cart (private-cart-<id>) authorized with the cart's cookie.
  • Send counts and totals, not the whole cart; event size depends on your plan, see pricing.