IVS 챗 클라이언트 메시징 SDK: React Native 자습서 2부: 메시지 및 이벤트 - Amazon IVS

IVS 챗 클라이언트 메시징 SDK: React Native 자습서 2부: 메시지 및 이벤트

이 자습서의 두 번째(마지막) 부분은 여러 섹션으로 나뉩니다.

참고: 경우에 따라 JavaScript와 TypeScript의 코드 예제가 동일하므로 서로 합쳐져 있습니다.

전제 조건

이 자습서의 1부인 채팅룸을 완료해야 합니다.

채팅 메시지 이벤트 구독

ChatRoom 인스턴스는 채팅룸에서 이벤트가 발생할 때 이벤트를 사용하여 통신합니다. 채팅 환경을 구현하기 위해서는 우선 연결된 방에서 다른 사람이 메시지를 보내고 있을 때 이를 사용자에게 보여줄 수 있어야 합니다.

여기서 채팅 메시지 이벤트를 구독합니다. 다음 단계에서는 생성한 메시지 목록을 업데이트하는 방법에 대해 살펴보겠습니다. 해당 목록은 각 메시지/이벤트마다 업데이트됩니다.

AppuseEffect 후크 내에서 모든 메시지 이벤트를 구독합니다.

TypeScript/JavaScript:

// App.tsx / App.jsx useEffect(() => { // ... const unsubscribeOnMessageReceived = room.addListener('message', (message) => {}); return () => { // ... unsubscribeOnMessageReceived(); }; }, []);

받은 메시지 보기

메시지를 받는 것은 채팅 환경의 핵심 부분입니다. Chat JS SDK를 사용하여 채팅룸에 연결된 다른 사용자로부터 이벤트를 쉽게 수신하도록 코드를 설정할 수 있습니다.

나중에 여기서 생성한 구성 요소를 활용하여 채팅룸에서 작업을 수행하는 방법을 보여 드리겠습니다.

App에서 messages라는 ChatMessage 배열 형식으로 messages라는 상태를 정의합니다.

TypeScript
// App.tsx // ... import { ChatRoom, ChatMessage, ConnectionState } from 'amazon-ivs-chat-messaging'; export default function App() { const [messages, setMessages] = useState<ChatMessage[]>([]); //... }
JavaScript
// App.jsx // ... import { ChatRoom, ConnectionState } from 'amazon-ivs-chat-messaging'; export default function App() { const [messages, setMessages] = useState([]); //... }

다음으로 message 리스너 함수에서 messages 배열에 message를 추가합니다.

TypeScript/JavaScript:

// App.tsx / App.jsx // ... const unsubscribeOnMessageReceived = room.addListener('message', (message) => { setMessages((msgs) => [...msgs, message]); }); // ...

아래에서는 받은 메시지를 표시하는 작업을 단계별로 설명합니다.

메시지 구성 요소 생성

Message 구성 요소는 채팅룸에서 받은 메시지의 내용의 렌더링(rendering)을 담당합니다. 이 섹션에서는 App에서 개별 채팅 메시지를 렌더링하기 위한 메시지 구성 요소를 생성합니다.

src 디렉터리에 새 파일을 생성하고 이름을 Message로 지정합니다. 이 구성 요소의 ChatMessage 형식을 전달하고 ChatMessage 속성에서 content 문자열을 전달하여 채팅룸 메시지 리스너에서 받은 메시지 텍스트를 표시합니다. 프로젝트 탐색기에서 Message로 이동합니다.

TypeScript
// Message.tsx import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; import { ChatMessage } from 'amazon-ivs-chat-messaging'; type Props = { message: ChatMessage; } export const Message = ({ message }: Props) => { return ( <View style={styles.root}> <Text>{message.sender.userId}</Text> <Text style={styles.textContent}>{message.content}</Text> </View> ); }; const styles = StyleSheet.create({ root: { backgroundColor: 'silver', padding: 6, borderRadius: 10, marginHorizontal: 12, marginVertical: 5, marginRight: 50, }, textContent: { fontSize: 17, fontWeight: '500', flexShrink: 1, }, });
JavaScript
// Message.jsx import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; export const Message = ({ message }) => { return ( <View style={styles.root}> <Text>{message.sender.userId}</Text> <Text style={styles.textContent}>{message.content}</Text> </View> ); }; const styles = StyleSheet.create({ root: { backgroundColor: 'silver', padding: 6, borderRadius: 10, marginHorizontal: 12, marginVertical: 5, marginRight: 50, }, textContent: { fontSize: 17, fontWeight: '500', flexShrink: 1, }, });

: 이 구성 요소를 사용하여 메시지 행에 렌더링하려는 다양한 속성(예: 아바타 URL, 사용자 이름, 메시지가 전송된 시각의 타임스탬프)을 저장할 수 있습니다.

현재 사용자가 전송한 메시지 인식

현재 사용자가 전송한 메시지를 인식하려면 코드를 수정하고 현재 사용자의 userId를 저장하기 위한 React 컨텍스트를 만듭니다.

src 디렉터리에 새 파일을 생성하고 이름을 UserContext로 지정합니다.

TypeScript
// UserContext.tsx import React from 'react'; const UserContext = React.createContext<string | undefined>(undefined); export const useUserContext = () => { const context = React.useContext(UserContext); if (context === undefined) { throw new Error('useUserContext must be within UserProvider'); } return context; }; export const UserProvider = UserContext.Provider;
JavaScript
// UserContext.jsx import React from 'react'; const UserContext = React.createContext(undefined); export const useUserContext = () => { const context = React.useContext(UserContext); if (context === undefined) { throw new Error('useUserContext must be within UserProvider'); } return context; }; export const UserProvider = UserContext.Provider;

참고: 여기서는 useState 후크를 사용하여 userId 값을 저장했습니다. 향후에는 사용자 컨텍스트를 변경하거나 로그인 용도로 setUserId를 사용할 수 있습니다.

다음으로 이전에 생성한 컨텍스트를 사용하여 userIdtokenProvider에 전달된 첫 번째 파라미터로 교체합니다. 아래에 지정된 대로 토큰 공급자에 SEND_MESSAGE 기능을 추가해야 합니다. 메시지를 보내는 데 필요합니다.

TypeScript
// App.tsx // ... import { useUserContext } from './UserContext'; // ... export default function App() { const [messages, setMessages] = useState<ChatMessage[]>([]); const userId = useUserContext(); const [room] = useState( () => new ChatRoom({ regionOrUrl: process.env.REGION, tokenProvider: () => tokenProvider(userId, ['SEND_MESSAGE']), }), ); // ... }
JavaScript
// App.jsx // ... import { useUserContext } from './UserContext'; // ... export default function App() { const [messages, setMessages] = useState([]); const userId = useUserContext(); const [room] = useState( () => new ChatRoom({ regionOrUrl: process.env.REGION, tokenProvider: () => tokenProvider(userId, ['SEND_MESSAGE']), }), ); // ... }

Message 구성 요소에서 이전에 생성한 UserContext를 사용하고, isMine 변수를 선언(declare)하고, 발신자의 userId와 컨텍스트의 userId를 일치시킨 후 현재 사용자에게 다양한 스타일의 메시지를 적용하세요.

TypeScript
// Message.tsx import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; import { ChatMessage } from 'amazon-ivs-chat-messaging'; import { useUserContext } from './UserContext'; type Props = { message: ChatMessage; } export const Message = ({ message }: Props) => { const userId = useUserContext(); const isMine = message.sender.userId === userId; return ( <View style={[styles.root, isMine && styles.mine]}> {!isMine && <Text>{message.sender.userId}</Text>} <Text style={styles.textContent}>{message.content}</Text> </View> ); }; const styles = StyleSheet.create({ root: { backgroundColor: 'silver', padding: 6, borderRadius: 10, marginHorizontal: 12, marginVertical: 5, marginRight: 50, }, textContent: { fontSize: 17, fontWeight: '500', flexShrink: 1, }, mine: { flexDirection: 'row-reverse', backgroundColor: 'lightblue', }, });
JavaScript
// Message.jsx import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; import { ChatMessage } from 'amazon-ivs-chat-messaging'; import { useUserContext } from './UserContext'; export const Message = ({ message }) => { const userId = useUserContext(); const isMine = message.sender.userId === userId; return ( <View style={[styles.root, isMine && styles.mine]}> {!isMine && <Text>{message.sender.userId}</Text>} <Text style={styles.textContent}>{message.content}</Text> </View> ); }; const styles = StyleSheet.create({ root: { backgroundColor: 'silver', padding: 6, borderRadius: 10, marginHorizontal: 12, marginVertical: 5, marginRight: 50, }, textContent: { fontSize: 17, fontWeight: '500', flexShrink: 1, }, mine: { flexDirection: 'row-reverse', backgroundColor: 'lightblue', }, });

채팅 메시지 목록 렌더링

이제 FlatListMessage 구성 요소를 사용하여 메시지를 나열합니다.

TypeScript
// App.tsx // ... const renderItem = useCallback<ListRenderItem<ChatMessage>>(({ item }) => { return ( <Message key={item.id} message={item} /> ); }, []); return ( <SafeAreaView style={styles.root}> <Text>Connection State: {connectionState}</Text> <FlatList inverted data={messages} renderItem={renderItem} /> <View style={styles.messageBar}> <MessageInput value={messageToSend} onMessageChange={setMessageToSend} /> <SendButton disabled={isSendDisabled} onPress={onMessageSend} /> </View> </SafeAreaView> ); // ...
JavaScript
// App.jsx // ... const renderItem = useCallback(({ item }) => { return ( <Message key={item.id} message={item} /> ); }, []); return ( <SafeAreaView style={styles.root}> <Text>Connection State: {connectionState}</Text> <FlatList inverted data={messages} renderItem={renderItem} /> <View style={styles.messageBar}> <MessageInput value={messageToSend} onMessageChange={setMessageToSend} /> <SendButton disabled={isSendDisabled} onPress={onMessageSend} /> </View> </SafeAreaView> ); // ...

이제 App이 채팅룸에서 받은 메시지를 렌더링하기 위한 퍼즐 조각이 모두 다 준비되었습니다. 생성한 구성 요소를 활용하여 채팅룸에서 작업을 수행하는 방법을 보려면 다음을 계속합니다.

채팅룸에서 작업 수행

메시지를 보내고 중재자 작업을 수행하는 것은 채팅룸과 상호 작용하는 주요 방법 중 일부입니다. 여기서는 다양한 채팅 요청 객체를 사용하여 Chatterbox에서 메시지 전송, 메시지 삭제, 다른 사용자 연결 끊기와 같은 일반적인 작업을 수행하는 방법을 알아봅니다.

채팅룸의 모든 작업은 공통적인 패턴을 따릅니다. 채팅룸에서 수행하는 모든 작업에는 해당하는 요청 객체가 있습니다. 각 요청에는 요청 확인 시 받는 해당 응답 객체가 있습니다.

채팅 토큰을 생성할 때 사용자에게 올바른 기능이 부여되면 해당 사용자는 요청 객체를 사용하여 해당 작업을 성공적으로 수행하여 채팅룸에서 어떤 요청을 수행할 수 있는지 확인할 수 있습니다.

아래에서는 메시지를 전송하고 메시지를 삭제하는 방법을 설명합니다.

메시지 전송

SendMessageRequest 클래스를 사용하여 채팅룸에서 메시지를 보낼 수 있습니다 이 단계에서는 메시지 입력 생성(이 자습서의 1부)에서 생성한 구성 요소를 통해 메시지 요청을 보내도록 App을 수정합니다.

우선, useState 후크를 사용하여 isSending라는 새 boolean 속성을 정의합니다. 이 새 속성을 사용하면 isSendDisabled 상수를 사용하여 button 요소의 비활성화 상태를 전환할 수 있습니다. SendButton의 이벤트 핸들러(event handler)에서 messageToSend의 값을 지우고 isSending을 true로 설정합니다.

이 버튼으로 API를 호출하므로 isSending boolean을 추가하면 요청이 완료될 때까지 SendButton에서 사용자 상호 작용을 비활성화함으로써 여러 API 호출이 동시에 발생하는 것을 방지할 수 있습니다.

참고: 메시지 전송은 위의 현재 사용자가 전송한 메시지 인식에서 설명한 것처럼 토큰 공급자에 SEND_MESSAGE 기능을 추가한 경우에만 작동합니다.

TypeScript/JavaScript:

// App.tsx / App.jsx // ... const [isSending, setIsSending] = useState(false); // ... const onMessageSend = () => { setIsSending(true); setMessageToSend(''); }; // ... const isSendDisabled = connectionState !== 'connected' || isSending; // ...

SendMessageRequest 인스턴스를 생성하고 생성자에 메시지 콘텐츠를 전달하여 요청을 준비합니다. isSendingmessageToSend 상태를 설정한 후 sendMessage 메서드를 호출하여 채팅룸으로 요청을 보냅니다. 마지막으로 요청 확인 또는 거절 수신 시isSending 플래그를 지웁니다.

TypeScript/JavaScript:

// App.tsx / App.jsx // ... import { ChatRoom, ConnectionState, SendMessageRequest } from 'amazon-ivs-chat-messaging' // ... const onMessageSend = async () => { const request = new SendMessageRequest(messageToSend); setIsSending(true); setMessageToSend(''); try { const response = await room.sendMessage(request); } catch (e) { console.log(e); // handle the chat error here... } finally { setIsSending(false); } }; // ...

Chatterbox를 실행해 보세요. MessageBar으로 초안을 작성하고 SendButton을 탭하여 메시지를 보내 보세요. 이전에 생성한 MessageList 내에 전송한 메시지가 렌더링된 것을 볼 수 있을 것입니다.

메시지 삭제

채팅룸에서 메시지를 삭제하려면 적절한 기능이 있어야 합니다. 기능은 채팅룸에 인증할 때 사용하는 채팅 토큰을 초기화하는 동안에 부여됩니다. 이 섹션의 목적에 따라 로컬 인증/권한 부여 서버 설정(이 자습서의 1부)의 ServerApp에서 중재자 기능을 지정할 수 있습니다. 이 작업은 토큰 공급자 구축(1부)에서 생성한 tokenProvider 객체를 사용하여 앱에서 수행됩니다.

여기서는 메시지를 삭제하는 함수를 추가하여 Message를 수정합니다.

먼저 App.tsx를 열고 DELETE_MESSAGE 기능을 추가합니다.(capabilitiestokenProvider 함수의 두 번째 파라미터입니다.)

참고: 이는 ServerApp이 IVS Chat API에 결과 채팅 토큰과 연결된 사용자는 채팅룸에서 메시지를 삭제할 수 있음을 알리는 방법입니다. 실제 상황에서는 서버 앱 인프라의 사용자 기능을 관리하기 위한 백엔드 로직이 더 복잡할 수 있습니다.

TypeScript/JavaScript:

// App.tsx / App.jsx // ... const [room] = useState(() => new ChatRoom({ regionOrUrl: process.env.REGION, tokenProvider: () => tokenProvider(userId, ['SEND_MESSAGE', 'DELETE_MESSAGE']), }), ); // ...

다음 단계에서는 삭제 버튼을 표시하도록 Message를 업데이트합니다.

문자열을 파라미터 중 하나로 받아 Promise를 반환하는 onDelete라는 새 함수를 정의합니다. 문자열 파라미터의 경우 구성 요소 메시지 ID를 전달합니다.

TypeScript
// Message.tsx import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; import { ChatMessage } from 'amazon-ivs-chat-messaging'; import { useUserContext } from './UserContext'; export type Props = { message: ChatMessage; onDelete(id: string): Promise<void>; }; export const Message = ({ message, onDelete }: Props) => { const userId = useUserContext(); const isMine = message.sender.userId === userId; const handleDelete = () => onDelete(message.id); return ( <View style={[styles.root, isMine && styles.mine]}> {!isMine && <Text>{message.sender.userId}</Text>} <View style={styles.content}> <Text style={styles.textContent}>{message.content}</Text> <TouchableOpacity onPress={handleDelete}> <Text>Delete<Text/> </TouchableOpacity> </View> </View> ); }; const styles = StyleSheet.create({ root: { backgroundColor: 'silver', padding: 6, borderRadius: 10, marginHorizontal: 12, marginVertical: 5, marginRight: 50, }, content: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, textContent: { fontSize: 17, fontWeight: '500', flexShrink: 1, }, mine: { flexDirection: 'row-reverse', backgroundColor: 'lightblue', }, });
JavaScript
// Message.jsx import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; import { ChatMessage } from 'amazon-ivs-chat-messaging'; import { useUserContext } from './UserContext'; export const Message = ({ message, onDelete }) => { const userId = useUserContext(); const isMine = message.sender.userId === userId; const handleDelete = () => onDelete(message.id); return ( <View style={[styles.root, isMine && styles.mine]}> {!isMine && <Text>{message.sender.userId}</Text>} <View style={styles.content}> <Text style={styles.textContent}>{message.content}</Text> <TouchableOpacity onPress={handleDelete}> <Text>Delete<Text/> </TouchableOpacity> </View> </View> ); }; const styles = StyleSheet.create({ root: { backgroundColor: 'silver', padding: 6, borderRadius: 10, marginHorizontal: 12, marginVertical: 5, marginRight: 50, }, content: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, textContent: { fontSize: 17, fontWeight: '500', flexShrink: 1, }, mine: { flexDirection: 'row-reverse', backgroundColor: 'lightblue', }, });

다음으로 FlatList 구성 요소의 최신 변경 사항을 반영하도록 renderItem를 업데이트합니다.

App에서 handleDeleteMessage라는 함수를 정의하고 이를 MessageList onDelete 속성에 전달합니다.

TypeScript
// App.tsx // ... const handleDeleteMessage = async (id: string) => {}; const renderItem = useCallback<ListRenderItem<ChatMessage>>(({ item }) => { return ( <Message key={item.id} message={item} onDelete={handleDeleteMessage} /> ); }, [handleDeleteMessage]); // ...
JavaScript
// App.jsx // ... const handleDeleteMessage = async (id) => {}; const renderItem = useCallback(({ item }) => { return ( <Message key={item.id} message={item} onDelete={handleDeleteMessage} /> ); }, [handleDeleteMessage]); // ...

관련 메시지 ID를 생성자 파라미터에 전달하는 DeleteMessageRequest의 새 인스턴스를 생성하여 요청을 준비하고 위에서 준비한 요청을 수락하는 deleteMessage를 호출합니다.

TypeScript
// App.tsx // ... const handleDeleteMessage = async (id: string) => { const request = new DeleteMessageRequest(id); await room.deleteMessage(request); }; // ...
JavaScript
// App.jsx // ... const handleDeleteMessage = async (id) => { const request = new DeleteMessageRequest(id); await room.deleteMessage(request); }; // ...

다음으로 방금 삭제한 메시지가 생략된 새 메시지 목록을 반영하도록 messages 상태를 업데이트합니다.

useEffect 후크에서messageDelete 이벤트를 수신하고 message 파라미터와 일치하는 ID가 있는 메시지를 삭제하여 messages 상태 배열을 업데이트합니다.

참고: 현재 사용자나 방에 있는 다른 사용자가 메시지를 삭제하는 경우 messageDelete 이벤트가 발생할 수 있습니다. 이벤트 핸들러에서(deleteMessage 요청 옆 대신) 이벤트를 처리하면 메시지 삭제 처리를 통합할 수 있습니다.

TypeScript/JavaScript:

// App.tsx / App.jsx // ... const unsubscribeOnMessageDeleted = room.addListener('messageDelete', (deleteMessageEvent) => { setMessages((prev) => prev.filter((message) => message.id !== deleteMessageEvent.id)); }); return () => { // ... unsubscribeOnMessageDeleted(); }; // ...

이제 채팅 앱의 채팅룸에서 사용자를 삭제할 수 있습니다.

다음 단계

테스트의 일환으로 방에서 다른 사용자의 연결을 끊는 등 다양한 작업을 시도해보세요.