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();
}
}
그 뒤 테스트 하면
실시간 채팅 구현은 마무리된다