FRONTEND/Next.js

[Next.js 14 + Supabase ] Supabase realtime (2) - 실시간 채팅 구현

죠으닝 2024. 8. 19. 15:29

 

 

Realtime 기능을 구현하기 전에 

 

사용하고 있는테이블에 Realtime 기능을 on 해줘야한다.

 

 

현재는 off로 설정되어 있으므로 

 

enable realtime을 통해 

 

 

on으로 활성화를 해준다.

 

 

 

이제 코드를 구현하면되는데 ,

 

현재 필요한 액션은 두 개다.

 

1. 메시지를 보내는 기능 (insert)


export async function sendMessage({ message, chatUserId }) {
  const supabase = await createServerSupabaseClient();

  //현재 접속중인 유저의 정보를 getSession으로부터 가져온다.
  const {
    data: { session },
    error,
  } = await supabase.auth.getSession();
  //에러 또는 세션에 유저가 없으면 에러 발생
  if (error || !session.user) {
    throw new Error("User is not authenticated");
  }

  //그리고 message table에 inset 해줌.
  const { data, error: sendMessageError } = await supabase
    .from("message")
    .insert({
      message,
      receiver: chatUserId,
      sender: session.user.id,
    });

  if (sendMessageError) {
    throw new Error(sendMessageError.message);
  }

  return data;
}

 

2. 특정 유저와의 메시지를 모두 가져오는 기능 (select all)


export async function getAllMessages({ chatUserId }) {
  const supabase = await createServerSupabaseClient();

  //현재 접속중인 유저의 정보를 getSession으로부터 가져온다.
  const {
    data: { session },
    error,
  } = await supabase.auth.getSession();

  if (error || !session.user) {
    throw new Error("User is not authenticated");
  }

  //쿼리문 작성
  const { data, error: getMessagesError } = await supabase
    .from("message")
    .select("*")
    .or(`receiver.eq.${chatUserId},receiver.eq.${session.user.id}`)
    .or(`sender.eq.${chatUserId},sender.eq.${session.user.id}`)
    .order("created_at", { ascending: true }); //오름차순 정렬

  if (getMessagesError) {
    return [];
  }

  return data;
}

 

 

 

쿼리문에서 테이블명이 자동완성 기능에 뜨지 않는다면 

아래의 명령문을 터미널에 입력해서  types_db.ts를 만들어준다.

npm run generate-types

 

 

ChatScreen에서 

 

input에서 입력하는 메시지를 상태관리 해주고 

  //메시지
  const [message, setMessage] = useState("");

 

input에 onchange 이벤트를 걸어준다. 

   <input
          value={message}
          onChange={(e) => setMessage(e.target.value)}
          type="text"
          className="p-3 w-full border-2 border-light-blue-600"
          placeholder="메시지를 입력하세요"
        />

 

 

이 메시지는 전송 버튼을 클릭하면 상대방에게 전송이 될거임 

 

 

useMutation을 사용한다.

mutationFn 에서 action으로 만들어준 sendMessage를 실행시키고 

인자로 작성한 메시지와, 상대방 id를 보내준다.

  const sendMessageMutation = useMutation({
    mutationFn: async () => {
      return sendMessage({
        message,
        chatUserId: selectedUserId,
      });
    },

    onSuccess: () => {
      setMessage("");
      getAllMessageQuery.refetch(); //전체 메시지 목록을 다시 가져오기 -> 실시간 성은 보장 x
    },
  });

 

성공 시 message 는 초기화 하고 

getAllMessageQuery의 refetch를 통해 메시지를 전부 다시 가져온다.

 


  const getAllMessageQuery = useQuery({
    queryKey: ["messages", selectedUserId],
    queryFn: () => getAllMessages({ chatUserId: selectedUserId }),
  });

 

 

 

getAllMessageQuery에 데이터가 있을 경우 그 메시지들을 

map으로 반복문 돌려 Message 컴포넌트를 출력한다. 

      {/* 채팅 영역 */}
      <div className="w-full flex-1 flex flex-col p-4 gap-2 overflow-y-scroll">
        {getAllMessageQuery.data?.map((message) => (
          <Message
            key={message.id}
            message={message.message}
            isFromMe={message.receiver === selectedUserId}
          />
        ))}
      </div>

 

 

 

 

 

 

 


  useEffect(() => {
    //채널 이름은 아무거나 지으면 됨
    const channel = supabase
      .channel("message_postgres_changes")
      .on(
        "postgres_changes",
        {
          event: "INSERT",
          schema: "public",
          table: "message",
        },
        (payload) => {
          console.log(payload);
        }
      )
      .subscribe(); // 위의 이벤트를 구독하겠다.

    //해당 컴포넌트가 화면상에 없을 때 아래의 함수 출력
    return () => {
      channel.unsubscribe(); //구독해지
    };
  }, []);

 

 

payload를 콘솔로 확인해보면 아래와 같다. 

 

 

이제 payload 부분을 수정하면

        (payload) => {
          // 만약 payload의 이벤트 타입이 INSERY고 에러가없을경우
          if (payload.eventType === "INSERT" && !payload.errors) {
            //재호출
            getAllMessageQuery.refetch();
          }
        }

 

 

그 뒤 테스트 하면 

 

 

실시간 채팅 구현은 마무리된다