

# IVS Chat Client Messaging SDK: JavaScript 자습서 1부: 채팅룸
<a name="chat-sdk-js-tutorial-chat-rooms"></a>

본 문서는 두 파트로 구성된 자습서 중 첫 번째 파트에 해당하는 자습서입니다. 이 자습서에서는 JavaScript/TypeScript를 통해 완전한 기능을 갖춘 앱을 구축하여 Amazon IVS Chat Client Messaging JavaScript SDK로 작업하기 위한 필수 사항을 설명하고 있습니다. 여기에서 지칭하는 앱은 *Chatterbox*라고 합니다.

이 자습서는 숙련된 개발자이나, Amazon IVS Chat Messaging SDK를 처음 접하는 사용자를 위해 작성되었습니다. 사용자는 이미 JavaScript/TypeScript 프로그래밍 언어와 React 라이브러리에 대한 친숙도가 있어야 합니다.

간략하게 나타내자면 Amazon IVS Chat Client Messaging JavaScript SDK를 Chat JS SDK라고 합니다.

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

이 자습서의 첫 번째 부분은 여러 섹션으로 나뉩니다.

1. [로컬 인증/권한 부여 서버 설정](#chat-js-rooms-auth-server)

1. [Chatterbox 프로젝트 생성](#chat-js-rooms-chatterbox)

1. [채팅 룸에 연결](#chat-js-rooms-connect)

1. [토큰 공급자 구축](#chat-js-rooms-token-provider)

1. [연결 업데이트 관찰](#chat-js-rooms-connection-state)

1. [전송 버튼 구성 요소 생성](#chat-js-rooms-send-button)

1. [메시지 입력 생성](#chat-js-rooms-message-input)

1. [다음 단계](#chat-js-rooms-next-steps)

전체 SDK 설명서를 보려면 우선 [Amazon IVS Chat Client Messaging SDK](chat-sdk.md)(Amazon IVS Chat 사용 설명서**에서 참조) 및 [Chat Client Messaging: SDK for JavaScript Reference](https://aws.github.io/amazon-ivs-chat-messaging-sdk-js/latest/)(GitHub)를 참조하세요.

## 사전 조건
<a name="chat-js-rooms-prerequisites"></a>
+ 사용자는 JavaScript/TypeScript 및 React 라이브러리에 익숙하여야 합니다. React 사용이 처음이라면 [React 튜토리얼](https://react.dev/learn/tutorial-tic-tac-toe)에서 기본 사항을 검토해 보시기 바랍니다.
+ [Amazon IVS Chat 시작하기](getting-started-chat.md)를 읽고 이해합니다.
+ 기존 IAM 정책에 정의된 CreateChatToken 및 CreateRoom 기능을 사용하여 AWS IAM 사용자를 생성합니다. ([Amazon IVS Chat 시작하기](getting-started-chat.md)를 참조하세요.)
+ 이 사용자의 비밀/액세스 키가 AWS 보안 인증 파일에 저장되어 있는지 확인합니다. 지침은 [AWS CLI 사용 설명서](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html)(특히 [구성 및 보안 인증 파일 설정](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html))를 참조합니다.
+ 채팅룸을 생성하고 ARN을 저장합니다. [Amazon IVS Chat 시작하기](getting-started-chat.md)을(를) 참조하세요. (ARN을 저장하지 않은 경우 나중에 콘솔이나 Chat API를 사용하여 조회할 수 있습니다.)
+ NPM 또는 Yarn 패키지 관리자를 사용하여 Node.js 14\+ 환경을 설치합니다.

## 로컬 인증/권한 부여 서버 설정
<a name="chat-js-rooms-auth-server"></a>

백엔드 애플리케이션은 채팅룸을 생성하고 Chat JS SDK가 채팅룸의 클라이언트를 인증하고 권한을 부여하는 데 필요한 채팅 토큰을 생성하는 일을 맡습니다. AWS 키는 모바일 앱에 안전하게 저장할 수 없으므로 자체 백엔드를 사용해야 합니다. 실력 좋은 공격자는 이러한 키를 추출하여 AWS 계정에 액세스할 수 있습니다.

Amazon IVS 채팅 시작하기에서 [채팅 토큰 생성](getting-started-chat-auth.md)을 참조하세요.** 순서도에서 볼 수 있듯이 서버 측 애플리케이션은 채팅 토큰 생성을 담당합니다. 즉, 앱은 서버 측 애플리케이션에서 채팅 토큰을 요청하여 채팅 토큰을 생성하는 자체 수단을 제공해야 합니다.

이 섹션에서는 백엔드에서 토큰 공급자를 생성하는 기본 사항에 대해 알아봅니다. AWS 환경을 사용하여 채팅 토큰 생성을 관리하는 로컬 서버를 만들기 위해 Express 프레임워크를 사용합니다.

NPM을 사용하여 빈 `npm` 프로젝트를 생성합니다. 애플리케이션을 보관할 디렉터리를 생성하고 이를 작업 디렉터리로 만듭니다.

```
$ mkdir backend & cd backend
```

`npm init`을 사용하여 애플리케이션의 `package.json` 파일을 생성합니다.

```
$ npm init
```

이 명령은 애플리케이션의 이름 및 버전을 비롯한 여러 항목을 입력하라는 메시지를 표시합니다. 지금은 **RETURN** 키를 눌러 대부분의 기본값을 그대로 사용할 수 있습니다. 단, 다음은 예외입니다.

```
entry point: (index.js)
```

**RETURN** 키를 눌러 제안된 기본 파일 이름 `index.js`를 그대로 사용하거나 주 파일의 이름을 원하는 대로 입력합니다.

이제 필요한 종속 항목을 설치합니다.

```
$ npm install express aws-sdk cors dotenv
```

`aws-sdk`에는 루트 디렉터리에 있는 `.env`라는 파일에서 자동으로 로드되는 구성 환경 변수가 필요합니다. 이를 구성하려면 `.env`라는 새 파일을 생성하고 누락된 구성 정보를 입력합니다.

```
# .env

# The region to send service requests to.
AWS_REGION=us-west-2

# Access keys use an access key ID and secret access key
# that you use to sign programmatic requests to AWS.

# AWS access key ID.
AWS_ACCESS_KEY_ID=...

# AWS secret access key.
AWS_SECRET_ACCESS_KEY=...
```

이제 `npm init` 명령에서 위에 입력한 이름으로 루트 디렉터리에 진입점(entry-point) 파일을 생성합니다. 이 경우 `index.js`를 사용하고 필요한 모든 패키지를 가져옵니다.

```
// index.js
import express from 'express';
import AWS from 'aws-sdk';
import 'dotenv/config';
import cors from 'cors';
```

이제 `express`의 새 인스턴스를 생성합니다.

```
const app = express();
const port = 3000;

app.use(express.json());
app.use(cors({ origin: ['http://127.0.0.1:5173'] }));
```

그런 다음 토큰 공급자를 위한 첫 번째 엔드포인트 POST 메서드를 생성할 수 있습니다. 요청 본문에서 필수 파라미터(`roomId`, `userId`, `capabilities` 및 `sessionDurationInMinutes`)를 가져옵니다.

```
app.post('/create_chat_token', (req, res) => {
  const { roomIdentifier, userId, capabilities, sessionDurationInMinutes } = req.body || {};
});
```

필수 필드의 유효성 검사를 추가합니다.

```
app.post('/create_chat_token', (req, res) => {
  const { roomIdentifier, userId, capabilities, sessionDurationInMinutes } = req.body || {};

  if (!roomIdentifier || !userId) {
    res.status(400).json({ error: 'Missing parameters: `roomIdentifier`, `userId`' });
    return;
  }
});
```

POST 메서드를 준비한 후 인증/권한 부여의 핵심 기능을 위해 `aws-sdk`와 `createChatToken`을 통합합니다.

```
app.post('/create_chat_token', (req, res) => {
  const { roomIdentifier, userId, capabilities, sessionDurationInMinutes } = req.body || {};

  if (!roomIdentifier || !userId || !capabilities) {
    res.status(400).json({ error: 'Missing parameters: `roomIdentifier`, `userId`, `capabilities`' });
    return;
  }

  ivsChat.createChatToken({ roomIdentifier, userId, capabilities, sessionDurationInMinutes }, (error, data) => {
    if (error) {
      console.log(error);
      res.status(500).send(error.code);
    } else if (data.token) {
      const { token, sessionExpirationTime, tokenExpirationTime } = data;
      console.log(`Retrieved Chat Token: ${JSON.stringify(data, null, 2)}`);

      res.json({ token, sessionExpirationTime, tokenExpirationTime });
    }
  });
});
```

파일 끝에 `express` 앱의 포트 리스너를 추가합니다.

```
app.listen(port, () => {
  console.log(`Backend listening on port ${port}`);
});
```

이제 프로젝트의 루트에서 다음 명령으로 서버를 실행할 수 있습니다.

```
$ node index.js
```

**팁**: 이 서버는 https://localhost:3000에서 URL 요청을 수락합니다.

## Chatterbox 프로젝트 생성
<a name="chat-js-rooms-chatterbox"></a>

먼저 `chatterbox`라는 React 프로젝트를 생성합니다. 다음 명령을 실행합니다.

```
npx create-react-app chatterbox
```

[Node 패키지 관리자](https://www.npmjs.com/) 또는 [Yarn 패키지 관리자](https://yarnpkg.com/)를 통해 Chat Client Messaging JS SDK를 통합할 수 있습니다.
+ Npm: `npm install amazon-ivs-chat-messaging`
+ Yarn: `yarn add amazon-ivs-chat-messaging`

## 채팅 룸에 연결
<a name="chat-js-rooms-connect"></a>

여기서는 `ChatRoom`을 생성하고 비동기 메서드를 사용하여 연결합니다. `ChatRoom` 클래스는 Chat JS SDK에 대한 사용자 연결을 관리합니다. 채팅룸에 성공적으로 연결하려면 React 애플리케이션 내에서 `ChatToken`의 인스턴스를 제공해야 합니다.

기본 `chatterbox` 프로젝트에서 생성한 `App` 파일로 이동하여 두 `<div>` 태그 사이의 모든 내용을 삭제합니다. 미리 입력된 코드는 필요하지 않습니다. 이 시점에서 `App`은 거의 비어 있습니다.

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

import * as React from 'react';

export default function App() {
  return <div>Hello!</div>;
}
```

새 `ChatRoom` 인스턴스를 생성하고 `useState` 후크를 사용하여 상태로 전달합니다. 이를 위해서는 `regionOrUrl`(채팅룸이 호스팅되는 AWS 리전) 및`tokenProvider`(이후 단계에서 생성되는 백엔드 인증/권한 부여 흐름에 사용됨)를 전달해야 합니다.

**중요**: [Amazon IVS Chat 시작하기](getting-started-chat-create-room.md)에서 방을 생성한 리전과 동일한 AWS 리전을 사용해야 합니다. API는 AWS 리전 서비스입니다. 지원되는 리전 및 Amazon IVS Chat HTTPS 서비스 엔드포인트 목록은 [Amazon IVS Chat 리전](https://docs.aws.amazon.com/general/latest/gr/ivs.html#ivs_region) 페이지를 참조하세요.

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

import React, { useState } from 'react';
import { ChatRoom } from 'amazon-ivs-chat-messaging';

export default function App() {
  const [room] = useState(() =>
    new ChatRoom({
      regionOrUrl: process.env.REGION as string,
      tokenProvider: () => {},
    }),
  );

  return <div>Hello!</div>;
}
```

## 토큰 공급자 구축
<a name="chat-js-rooms-token-provider"></a>

다음 단계로 `ChatRoom` 생성자에 필요한 파라미터 없는`tokenProvider` 함수를 구축해야 합니다. 먼저, [로컬 인증/권한 부여 서버 설정](#chat-js-rooms-auth-server)에서 설정한 백엔드 애플리케이션에 POST 요청을 하는 `fetchChatToken` 함수를 생성하겠습니다. 채팅 토큰은 SDK가 성공적으로 채팅룸 연결을 설정하는 데 필요한 정보를 포함합니다. Chat API는 사용자의 ID, 채팅룸 내 기능 및 세션 기간을 검증하는 안전한 방법으로 이러한 토큰을 사용합니다.

프로젝트 탐색기에서 `fetchChatToken`이라는 새 TypeScript/JavaScript 파일을 생성합니다. `backend` 애플리케이션에 가져오기 요청을 구축하고 응답에서 `ChatToken` 객체를 반환합니다. 채팅 토큰을 생성하는 데 필요한 요청 본문 속성을 추가합니다. [Amazon 리소스 이름(ARN)](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html)에 정의된 규칙을 사용합니다. 이러한 속성은 [CreateChatToken 작업](https://docs.aws.amazon.com//ivs/latest/ChatAPIReference/API_CreateChatToken.html#API_CreateChatToken_RequestBody)에 문서화되어 있습니다.

**참고**: 여기서 사용하는 URL은 백엔드 애플리케이션을 실행했을 때 로컬 서버에서 생성한 URL과 동일한 URL입니다.

------
#### [ TypeScript ]

```
// fetchChatToken.ts

import { ChatToken } from 'amazon-ivs-chat-messaging';

type UserCapability = 'DELETE_MESSAGE' | 'DISCONNECT_USER' | 'SEND_MESSAGE';

export async function fetchChatToken(
  userId: string,
  capabilities: UserCapability[] = [],
  attributes?: Record<string, string>,
  sessionDurationInMinutes?: number,
): Promise<ChatToken> {
  const response = await fetch(`${process.env.BACKEND_BASE_URL}/create_chat_token`, {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      userId,
      roomIdentifier: process.env.ROOM_ID,
      capabilities,
      sessionDurationInMinutes,
      attributes
    }),
  });

  const token = await response.json();

  return {
    ...token,
    sessionExpirationTime: new Date(token.sessionExpirationTime),
    tokenExpirationTime: new Date(token.tokenExpirationTime),
  };
}
```

------
#### [ JavaScript ]

```
// fetchChatToken.js

export async function fetchChatToken(
  userId,
  capabilities = [],
  attributes,
  sessionDurationInMinutes) {
  const response = await fetch(`${process.env.BACKEND_BASE_URL}/create_chat_token`, {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      userId,
      roomIdentifier: process.env.ROOM_ID,
      capabilities,
      sessionDurationInMinutes,
      attributes
    }),
  });

  const token = await response.json();

  return {
    ...token,
    sessionExpirationTime: new Date(token.sessionExpirationTime),
    tokenExpirationTime: new Date(token.tokenExpirationTime),
  };
}
```

------

## 연결 업데이트 관찰
<a name="chat-js-rooms-connection-state"></a>

채팅 앱을 생성하기 위해서는 채팅룸 연결 상태 변화에 대해 필수적으로 대응해야 합니다. 관련 이벤트 구독부터 시작해 보겠습니다.

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

import React, { useState, useEffect } from 'react';
import { ChatRoom } from 'amazon-ivs-chat-messaging';
import { fetchChatToken } from './fetchChatToken';

export default function App() {
  const [room] = useState(
    () =>
      new ChatRoom({
        regionOrUrl: process.env.REGION as string,
        tokenProvider: () => fetchChatToken('Mike', ['SEND_MESSAGE']),
      }),
  );

  useEffect(() => {
    const unsubscribeOnConnecting = room.addListener('connecting', () => {});
    const unsubscribeOnConnected = room.addListener('connect', () => {});
    const unsubscribeOnDisconnected = room.addListener('disconnect', () => {});

    return () => {
      // Clean up subscriptions.
      unsubscribeOnConnecting();
      unsubscribeOnConnected();
      unsubscribeOnDisconnected();
    };
  }, [room]);

  return <div>Hello!</div>;
}
```

다음으로 연결 상태를 읽을 수 있는 기능을 제공해야 합니다. `useState` 후크를 사용하여 `App`에서 로컬 상태를 생성하고 각 리스너 내에서 연결 상태를 설정합니다.

------
#### [ TypeScript ]

```
// App.tsx

import React, { useState, useEffect } from 'react';
import { ChatRoom, ConnectionState } from 'amazon-ivs-chat-messaging';
import { fetchChatToken } from './fetchChatToken';

export default function App() {  
  const [room] = useState(
    () =>
      new ChatRoom({
        regionOrUrl: process.env.REGION as string,
        tokenProvider: () => fetchChatToken('Mike', ['SEND_MESSAGE']),
      }),
  );
  const [connectionState, setConnectionState] = useState<ConnectionState>('disconnected');

  useEffect(() => {
    const unsubscribeOnConnecting = room.addListener('connecting', () => {
      setConnectionState('connecting');
    });

    const unsubscribeOnConnected = room.addListener('connect', () => {
      setConnectionState('connected');
    });

    const unsubscribeOnDisconnected = room.addListener('disconnect', () => {
      setConnectionState('disconnected');
    });

    return () => {
      unsubscribeOnConnecting();
      unsubscribeOnConnected();
      unsubscribeOnDisconnected();
    };
  }, [room]);

  return <div>Hello!</div>;
}
```

------
#### [ JavaScript ]

```
// App.jsx

import React, { useState, useEffect } from 'react';
import { ChatRoom } from 'amazon-ivs-chat-messaging';
import { fetchChatToken } from './fetchChatToken';

export default function App() {
  const [room] = useState(
    () =>
      new ChatRoom({
        regionOrUrl: process.env.REGION,
        tokenProvider: () => fetchChatToken('Mike', ['SEND_MESSAGE']),
      }),
  );
  const [connectionState, setConnectionState] = useState('disconnected');

  useEffect(() => {
    const unsubscribeOnConnecting = room.addListener('connecting', () => {
      setConnectionState('connecting');
    });

    const unsubscribeOnConnected = room.addListener('connect', () => {
      setConnectionState('connected');
    });

    const unsubscribeOnDisconnected = room.addListener('disconnect', () => {
      setConnectionState('disconnected');
    });

    return () => {
      unsubscribeOnConnecting();
      unsubscribeOnConnected();
      unsubscribeOnDisconnected();
    };
  }, [room]);

  return <div>Hello!</div>;
}
```

------

연결 상태를 구독한 후 연결 상태를 표시하고 `useEffect` 후크 내의 `room.connect` 메서드를 사용하여 채팅룸에 연결합니다.

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

// ...

useEffect(() => {
  const unsubscribeOnConnecting = room.addListener('connecting', () => {
    setConnectionState('connecting');
  });

  const unsubscribeOnConnected = room.addListener('connect', () => {
    setConnectionState('connected');
  });

  const unsubscribeOnDisconnected = room.addListener('disconnect', () => {
    setConnectionState('disconnected');
  });

  room.connect();

  return () => {
    unsubscribeOnConnecting();
    unsubscribeOnConnected();
    unsubscribeOnDisconnected();
  };
}, [room]);

// ...

return (
  <div>
    <h4>Connection State: {connectionState}</h4>
  </div>
);

// ...
```

채팅룸 연결을 성공적으로 구현했습니다.

## 전송 버튼 구성 요소 생성
<a name="chat-js-rooms-send-button"></a>

이 섹션에서는 연결 상태 별로 각기 다른 디자인의 전송(send) 버튼을 생성합니다. 전송 버튼을 사용하면 채팅룸에서 메시지를 쉽게 보낼 수 있습니다. 또한 연결이 끊겼거나 채팅 세션이 만료 등과 같이 메시지를 전송 가능 여부 및 시기를 시각적으로 표시하는 역할을 합니다.

먼저, Chatterbox 프로젝트의 `src` 디렉터리에 새 파일을 생성하고 이름을 `SendButton`로 지정합니다. 그런 다음 채팅 애플리케이션용 버튼을 표시할 구성 요소를 생성합니다. `SendButton`을 내보내고 `App`으로 가져옵니다. 비어 있는 `<div></div>` 사이에 `<SendButton />`을 추가합니다.

------
#### [ TypeScript ]

```
// SendButton.tsx

import React from 'react';

interface Props {
  onPress?: () => void;
  disabled?: boolean;
}

export const SendButton = ({ onPress, disabled }: Props) => {
  return (
    <button disabled={disabled} onClick={onPress}>
      Send
    </button>
  );
};

// App.tsx

import { SendButton } from './SendButton';

// ...

return (
  <div>
    <div>Connection State: {connectionState}</div>
    <SendButton />
  </div>
);
```

------
#### [ JavaScript ]

```
// SendButton.jsx

import React from 'react';

export const SendButton = ({ onPress, disabled }) => {
  return (
    <button disabled={disabled} onClick={onPress}>
      Send
    </button>
  );
};

// App.jsx

import { SendButton } from './SendButton';

// ...

return (
  <div>
    <div>Connection State: {connectionState}</div>
    <SendButton />
  </div>
);
```

------

다음으로 `App`에서 `onMessageSend`라는 함수를 정의하고 이를 `SendButton onPress` 속성에 전달합니다. `isSendDisabled`라는 다른 변수를 정의하여(방이 연결되어 있지 않을 때 메시지를 보내지 못하도록 함) `SendButton disabled` 속성에 전달합니다.

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

// ...

const onMessageSend = () => {};

const isSendDisabled = connectionState !== 'connected';

return (
  <div>
    <div>Connection State: {connectionState}</div>
    <SendButton disabled={isSendDisabled} onPress={onMessageSend} />
  </div>
);

// ...
```

## 메시지 입력 생성
<a name="chat-js-rooms-message-input"></a>

Chatterbox 메시지 표시줄은 채팅룸에 메시지를 보내기 위해 상호 작용하는 구성 요소입니다. 일반적으로 메시지 작성을 위한 텍스트 입력과 메시지를 보내기 위한 버튼을 포함합니다.

`MessageInput` 구성 요소를 생성하려면 먼저 `src` 디렉터리에 새 파일을 생성하고 이름을 `MessageInput`로 지정합니다. 그런 다음 채팅 애플리케이션용 입력을 표시할 통제된 입력 구성 요소를 생성합니다. `MessageInput`을 내보내고 `App`로 가져옵니다(`<SendButton />` 위로).

기본값으로 빈 문자열이 있는 `useState` 후크를 사용하여 `messageToSend`라는 새 상태를 생성합니다. 앱 본문에서 `messageToSend`를 `MessageInput`의 `value`로 전달하고 `onMessageChange` 속성에 `setMessageToSend`를 전달합니다.

------
#### [ TypeScript ]

```
// MessageInput.tsx

import * as React from 'react';

interface Props {
  value?: string;
  onValueChange?: (value: string) => void;
}

export const MessageInput = ({ value, onValueChange }: Props) => {
  return (
    <input type="text" value={value} onChange={(e) => onValueChange?.(e.target.value)} placeholder="Send a message" />
  );
};


// App.tsx

// ...  

import { MessageInput } from './MessageInput';

// ...

export default function App() {
  const [messageToSend, setMessageToSend] = useState('');

// ...

return (
  <div>
    <h4>Connection State: {connectionState}</h4>
    <MessageInput value={messageToSend} onMessageChange={setMessageToSend} />
    <SendButton disabled={isSendDisabled} onPress={onMessageSend} />
  </div>
);
```

------
#### [ JavaScript ]

```
// MessageInput.jsx

import * as React from 'react';

export const MessageInput = ({ value, onValueChange }) => {
  return (
    <input type="text" value={value} onChange={(e) => onValueChange?.(e.target.value)} placeholder="Send a message" />
  );
};

// App.jsx

// ...  

import { MessageInput } from './MessageInput';

// ...

export default function App() {
  const [messageToSend, setMessageToSend] = useState('');

// ...


return (
  <div>
    <h4>Connection State: {connectionState}</h4>
    <MessageInput value={messageToSend} onMessageChange={setMessageToSend} />
    <SendButton disabled={isSendDisabled} onPress={onMessageSend} />
  </div>
);
```

------

## 다음 단계
<a name="chat-js-rooms-next-steps"></a>

이제 Chatterbox용 메시지 표시줄 구축을 완료했으므로 이 JavaScript 자습서의 2부인 [메시지 및 이벤트](chat-sdk-js-tutorial-messages-events.md)로 이동하세요.