← 활용 사례

탭과 기기 동기화: 로그아웃, 권한, 장바구니

모든 곳에서 한 번에 로그아웃하고, 회수한 권한을 바로 적용하고, 장바구니와 설정을 사용자가 연 모든 탭과 기기에서 똑같이 유지합니다.

사용자는 앱을 여러 탭과 여러 기기에 열어 두고, 각각은 새로고침해야만 변화를 압니다. 공용 컴퓨터에서 로그아웃했는데 휴대폰 탭에는 여전히 그 계정이 보입니다. 관리자가 동료의 편집 권한을 회수했는데 동료는 새로고침할 때까지 계속 편집합니다. 휴대폰에서 장바구니에 상품을 담았는데 노트북 탭에는 장바구니가 비어 있다고 나옵니다. 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>)을 두고 장바구니 쿠키로 인가하면 됩니다.
  • 장바구니 전체가 아니라 개수와 합계를 보내세요. 이벤트 크기는 요금제에 따라 다르니 요금을 확인하세요.