

# IVS 챗 클라이언트 메시징 SDK: React Native 자습서 2부: 메시지 및 이벤트
<a name="chat-sdk-react-tutorial-messages-events"></a>

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

1. [채팅 메시지 이벤트 구독](#chat-react-messages-events-subscribe)

1. [받은 메시지 보기](#chat-react-messages-events-show)

   1.  [메시지 구성 요소 생성](#chat-react-messages-create-component)

   1. [현재 사용자가 전송한 메시지 인식](#chat-react-messages-recognize)

   1. [채팅 메시지 목록 렌더링](#chat-react-messages-render-list)

1. [채팅룸에서 작업 수행](#chat-react-messages-events-room-actions)

   1. [메시지 전송](#chat-react-room-actions-sending-message)

   1. [메시지 삭제](#chat-react-room-actions-deleting-message)

1. [다음 단계](#chat-react-messages-events-next-steps)

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

## 사전 조건
<a name="chat-react-messages-events-prerequisite"></a>

이 자습서의 1부인 [채팅룸](chat-sdk-react-tutorial-chat-rooms.md)을 완료해야 합니다.

## 채팅 메시지 이벤트 구독
<a name="chat-react-messages-events-subscribe"></a>

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

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

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

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

useEffect(() => {
  // ...
  const unsubscribeOnMessageReceived = room.addListener('message', (message) => {});

  return () => {
    // ...
    unsubscribeOnMessageReceived();
  };
}, []);
```

## 받은 메시지 보기
<a name="chat-react-messages-events-show"></a>

메시지를 받는 것은 채팅 환경의 핵심 부분입니다. 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]);
});

// ...
```

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

1.  [메시지 구성 요소 생성](#chat-react-messages-create-component)

1. [현재 사용자가 전송한 메시지 인식](#chat-react-messages-recognize)

1. [채팅 메시지 목록 렌더링](#chat-react-messages-render-list)

### 메시지 구성 요소 생성
<a name="chat-react-messages-create-component"></a>

`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, 사용자 이름, 메시지가 전송된 시각의 타임스탬프)을 저장할 수 있습니다.

### 현재 사용자가 전송한 메시지 인식
<a name="chat-react-messages-recognize"></a>

현재 사용자가 전송한 메시지를 인식하려면 코드를 수정하고 현재 사용자의 `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',
  },
});
```

------

### 채팅 메시지 목록 렌더링
<a name="chat-react-messages-render-list"></a>

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

## 채팅룸에서 작업 수행
<a name="chat-react-messages-events-room-actions"></a>

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

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

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

아래에서는 [메시지를 전송](#chat-react-room-actions-sending-message)하고 [메시지를 삭제](#chat-react-room-actions-deleting-message)하는 방법을 설명합니다.

### 메시지 전송
<a name="chat-react-room-actions-sending-message"></a>

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

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

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

참고: 메시지 전송은 위의 [현재 사용자가 전송한 메시지 인식](#chat-react-messages-recognize)에서 설명한 것처럼 토큰 공급자에 `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` 내에 전송한 메시지가 렌더링된 것을 볼 수 있을 것입니다.

### 메시지 삭제
<a name="chat-react-room-actions-deleting-message"></a>

채팅룸에서 메시지를 삭제하려면 적절한 기능이 있어야 합니다. 기능은 채팅룸에 인증할 때 사용하는 채팅 토큰을 초기화하는 동안에 부여됩니다. 이 섹션의 목적에 따라 [로컬 인증/권한 부여 서버 설정](chat-sdk-react-tutorial-chat-rooms.md#chat-react-rooms-auth-server)(이 자습서의 1부)의 `ServerApp`에서 중재자 기능을 지정할 수 있습니다. 이 작업은 [토큰 공급자 구축](chat-sdk-react-tutorial-chat-rooms.md#chat-react-rooms-token-provider)(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();
};

// ...
```

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

## 다음 단계
<a name="chat-react-messages-events-next-steps"></a>

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