Skip to content
Loafacto 문서/참고 문서/web-ui 문서/관리자 "온라인/오프라인" 표시 — Supabase Presence 가이드

관리자 "온라인/오프라인" 표시 — Supabase Presence 가이드

관리자 사용자 관리 테이블에서 각 계정을 온라인 / 오프라인으로 보여주기 위한 구현과 설정을 정리한 문서입니다.


1. 목표

  • 관리자 페이지 사용자 목록에서 지금 접속 중인 계정 → 온라인(녹색 뱃지)
  • 접속이 끊긴 계정(로그아웃 또는 브라우저/탭 종료) → 오프라인

이걸 Supabase Realtime Presence로 구현합니다. DB 테이블에 “온라인 여부” 컬럼을 두지 않습니다.


2. 원리 (가이드 요약)

  • Presence = “지금 이 채널에 누가 연결되어 있는지”를 실시간 추적하는 기능.
  • 채널에 연결 → 다른 클라이언트에게 join 이벤트 → 해당 사용자를 온라인으로 표시.
  • 연결이 끊기면 Realtime이 leave 이벤트를 보냄 → 해당 사용자를 오프라인으로 표시.

두 가지 끊김을 구분할 수 있습니다.

상황동작우리가 할 일
브라우저/탭 닫음, 네트워크 끊김WebSocket 끊김 → Realtime이 자동으로 leave 발생 → 다른 클라이언트가 leave 수신 → 오프라인 처리별도 호출 없음
명시적 로그아웃클라이언트가 채널에서 leave(untrack + unsubscribe) 를 호출한 뒤 signOut → leave 이벤트 전송 → 오프라인 처리signOut 전에 leave 호출 필수

따라서:

  • 온라인 표시 = Presence에 track된 메타(예: user_id)를 사용.
  • 브라우저 닫힘 = 연결 끊김 → 자동 leave → 오프라인.
  • 로그아웃 = 먼저 채널에서 leave(untrack 후 구독 해제) 호출 → 그다음 signOut() 호출.
    leave를 signOut 이후에 하면 연결이 먼저 끊겨 leave가 서버에 전달되지 않을 수 있어, 반드시 signOut 전에 leave 처리해야 합니다.

(추가로 “브라우저 닫힘 vs 로그아웃”을 구분하고 싶다면, presence 메타에 session_id를 넣고 leave 수신 시 세션 유효성 검사하거나, 로그아웃 시 leave 전에 status: 'logged_out' 같은 메타를 보내는 방식이 있습니다. 단순 온/오프라인 표시만 필요하면 이 구분은 선택 사항입니다.)


3. 우리 앱 구현 요약

구분내용
채널 이름user-presence (고정)
로그인 사용자(클라이언트)앱 로드 시 useTrackPresence()로 위 채널에 참여, presence: { key: userId } 로 채널 생성 후 track({ user_id, online_at })
관리자 페이지useOnlineUserIds()로 같은 채널 구독, presenceState() / join·leave·sync 이벤트로 접속 중인 user_id Set 유지 → 테이블에 온라인 뱃지 표시
로그아웃 시signOut() 호출 전에 clearPresenceBeforeLogout() 호출 → 채널에서 untrack() → 잠시 대기(200ms) → unsubscribe(). 그다음 supabase.auth.signOut(), user = null
브라우저/탭 닫음beforeunload 에서 untrack() 시도(선택). 실제 끊김은 WebSocket 종료로 Realtime이 자동 leave 처리

핵심: 로그아웃 시 leave를 signOut보다 먼저 호출해야, 로그아웃한 계정이 관리자 화면에서 곧바로 오프라인으로 갱신됩니다.


4. Supabase에서 할 일

이미 왼쪽 사이드바에 Realtime 메뉴가 보인다면, Realtime 기능이 켜져 있는 프로젝트입니다.
온라인/오프라인(Presence) 만 쓰는 경우 아래만 확인하면 됩니다.

4-1. Realtime → Settings

  • 왼쪽에서 RealtimeSettings 로 이동합니다.
  • Realtime 서비스가 활성화(Enabled) 되어 있는지 확인합니다.
    (이미 Realtime 메뉴가 보이면 보통 켜져 있습니다. 토글이 있으면 On으로 두면 됩니다.)
  • Presence는 별도 스위치가 없어도 Realtime이 켜져 있으면 사용 가능한 경우가 많습니다.

4-2. Realtime → Policies (필수)

Presence(온라인/오프라인)를 쓰려면 Realtime → Policies 에서 정책 2개 등록이 필수입니다.
정책이 없으면 채널에 presence를 보내거나 받을 수 없어, 온라인 뱃지가 동작하지 않습니다.
오른쪽 Templates 에 있는 Presence용 템플릿을 쓰면 됩니다.

1) “누가 온라인인지 듣기”용 정책 (SELECT)

  1. RealtimePoliciesCreate policy 클릭.
  2. Policy name 에 예: Allow listening presence for authenticated 입력.
  3. Policy Command 에서 SELECT 선택.
  4. 오른쪽 Templates 에서 아래 항목을 클릭해 적용합니다.
    "Allow listening for presences on all channels for authenticated users only"
    → 채널의 presence 상태를 조회(listen) 할 수 있게 합니다. (관리자 페이지가 “누가 온라인인지” 받을 때 필요)
  5. 생성되는 SQL이 인증된 사용자(auth.role() = 'authenticated' 등)만 허용하는지 확인 후 Save policy 클릭.

2) “내가 온라인이라고 알리기”용 정책 (INSERT)

  1. 다시 Create policy 클릭.
  2. Policy name 에 예: Allow broadcasting presence for authenticated 입력.
  3. Policy Command 에서 INSERT 선택.
  4. 오른쪽 Templates 에서 아래 항목을 클릭해 적용합니다.
    "Allow broadcasting presences on all channels for authenticated users only"
    → 로그인한 사용자가 presence를 보내는(track) 할 수 있게 합니다.
  5. Save policy 클릭.

정리하면:

  • SELECT 정책 1개 → 관리자 페이지가 user-presence 채널의 presence를 받을 수 있음.
  • INSERT 정책 1개 → 로그인 사용자가 같은 채널에 자신의 presence를 보낼 수 있음.

두 정책을 모두 저장한 뒤 앱에서 로그인 → 관리자 페이지에서 온라인 뱃지가 나오는지 확인하면 됩니다.


"messages" / API DISABLED 노란 배너가 보일 때

  • 목록에 messages 옆에 "API DISABLED"가 있고, “No data will be selectable via Supabase APIs as this schema is not exposed” 라는 배너가 있을 수 있습니다.
  • 그 메시지는 일부 스키마 노출 설정에 대한 안내입니다. Realtime 채널 접근 정책은 위처럼 Create policy 로 만드는 것이 맞고, 테이블realtime.messages(Realtime 내부용)로 두고, Templates 에서 Presence용 두 개만 적용해 저장하면 됩니다.
  • 정책 저장 후에도 Presence가 동작하지 않으면, Project Settings → API (또는 API settings) 에서 Realtime/해당 스키마 노출 여부를 한 번 확인해 보세요.

4-3. 하지 않아도 되는 것

  • "Set up realtime for me" / "Create a trigger"
    → DB 트리거나 Broadcast 설정용입니다. Presence(온라인/오프라인)에는 필요 없습니다. 누르지 않아도 됩니다.
  • Database → Replication 에서 테이블 추가
    → 테이블 행 변경 구독(Postgres Changes)용입니다. Presence와 무관합니다.

4-4. 그밖에

  • Project URLanon key 가 웹 앱 .env(VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY)와 일치하는지만 확인하면 됩니다.
    (Project Settings → API 또는 API Keys에서 확인)

5. 브라우저 닫힘 vs 로그아웃 구분 (선택)

가이드에서 말한 대로, 구분이 필요하면 다음을 조합할 수 있습니다.

  • presence 메타session_id, last_active 포함.
  • 로그아웃 시
    • channel.leave() (또는 untrack + unsubscribe)
    • supabase.auth.signOut()
    • (선택) 서버에 로그아웃 이벤트 기록
  • leave 이벤트 수신 시
    • 해당 session_id의 세션이 아직 유효한지 검사
    • 유효 → 비정상 종료(브라우저 닫힘/네트워크)
    • 무효 → 정상 로그아웃
  • 세션 검사가 어렵다면: 로그아웃 시 leave 전에 메타에 status: 'logged_out' 등을 넣어 두고, leave 이벤트에 그 값이 있으면 “정상 로그아웃”으로 처리.

단순 온/오프라인 표시만 필요하면 위 구분은 구현하지 않아도 됩니다. leave만 제대로 오면 오프라인으로 표시하면 됩니다.


6. 주의사항 (가이드 반영)

  • 네트워크 끊김 후 재연결 지연·백그라운드 탭 정책 때문에 leave/join이 즉시 오지 않을 수 있음.
    필요하면 UI에서 10~30초 정도 잠정 오프라인 타임아웃을 두는 것이 좋습니다.
  • Presence 메타에는 민감한 정보를 넣지 마세요.
  • 인증 토큰 만료, 관리자에 의한 강제 로그아웃 등도 있으면, 그에 맞춰 세션/leave 처리만 일관되게 하면 됩니다.

7. 문제 해결

  • Settings에 Realtime 메뉴가 없어요 / /settings/realtime 404
    → Presence는 대시보드 설정이 필요 없습니다. 무시해도 됩니다.

  • 항상 오프라인으로만 보여요
    → Project URL·anon key가 .env와 같은지 확인.
    → F12 콘솔에 Realtime/WebSocket 오류가 없는지 확인.
    → 사용자 탭에서 앱을 연 상태에서 관리자 페이지를 새로고침해 보기.

  • 로그아웃해도 계속 온라인으로 보여요
    signOut 전에 Presence 채널에서 untrack() 후 잠시 대기한 뒤 unsubscribe() 가 호출되는지 확인 (clearPresenceBeforeLogout()).
    → 관리자 페이지 새로고침 후에도 온라인으로 남는지 확인.
    → Replication/테이블 Realtime 설정은 Presence와 무관하므로, 앱 쪽(로그아웃 시 leave, 콘솔 오류) 만 점검하면 됩니다.

  • 연결 오류
    → URL·anon key, 방화벽/네트워크(*.supabase.co, wss) 확인.


8. 관련 문서