IVS 챗 클라이언트 메시징 SDK: React Native 자습서 2부: 메시지 및 이벤트
이 자습서의 두 번째(마지막) 부분은 여러 섹션으로 나뉩니다.
참고: 경우에 따라 JavaScript와 TypeScript의 코드 예제가 동일하므로 서로 합쳐져 있습니다.
전제 조건
이 자습서의 1부인 채팅룸을 완료해야 합니다.
채팅 메시지 이벤트 구독
ChatRoom
인스턴스는 채팅룸에서 이벤트가 발생할 때 이벤트를 사용하여 통신합니다. 채팅 환경을 구현하기 위해서는 우선 연결된 방에서 다른 사람이 메시지를 보내고 있을 때 이를 사용자에게 보여줄 수 있어야 합니다.
여기서 채팅 메시지 이벤트를 구독합니다. 다음 단계에서는 생성한 메시지 목록을 업데이트하는 방법에 대해 살펴보겠습니다. 해당 목록은 각 메시지/이벤트마다 업데이트됩니다.
App
의 useEffect
후크 내에서 모든 메시지 이벤트를 구독합니다.
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
를 사용할 수 있습니다.
다음으로 이전에 생성한 컨텍스트를 사용하여 userId
를 tokenProvider
에 전달된 첫 번째 파라미터로 교체합니다. 아래에 지정된 대로 토큰 공급자에 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',
},
});
채팅 메시지 목록 렌더링
이제 FlatList
및 Message
구성 요소를 사용하여 메시지를 나열합니다.
- 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
인스턴스를 생성하고 생성자에 메시지 콘텐츠를 전달하여 요청을 준비합니다. isSending
과 messageToSend
상태를 설정한 후 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
기능을 추가합니다.(capabilities
는 tokenProvider
함수의 두 번째 파라미터입니다.)
참고: 이는 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();
};
// ...
이제 채팅 앱의 채팅룸에서 사용자를 삭제할 수 있습니다.
다음 단계
테스트의 일환으로 방에서 다른 사용자의 연결을 끊는 등 다양한 작업을 시도해보세요.