

# IVS Chat Client Messaging SDK: React Native のチュートリアルパート 1: Chat Rooms (チャットルーム)
<a name="chat-sdk-react-tutorial-chat-rooms"></a>

これは 2 部構成のチュートリアルの第 1 部です。React Native を使用してフル機能のアプリケーションを構築し、Amazon IVS Chat Client Messaging JavaScript SDK を使用する際の基本を学びます。このアプリケーションは、Chatterbox と呼ばれます。

本チュートリアルは、デベロッパーとしての経験があり、Amazon IVS Chat Messagin SDK を初めて利用する人を対象としています。TypeScript または JavaScript のプログラミング言語と React Native ライブラリに精通していることが望まれます。

便宜上、Amazon IVS Chat Client Messaging JavaScript SDK は Chat JS SDK とします。

**注**: JavaScript と TypeScript のコード例が同じ内容である場合は、共通の例として示しています。

このチュートリアルの最初の部分は、いくつかのセクションに分かれています。

1. [ローカル認証サーバーおよび認可サーバーのセットアップ](#chat-react-rooms-auth-server)

1. [Chatterbox プロジェクトの作成](#chat-react-rooms-chatterbox)

1. [チャットルームに接続する](#chat-react-rooms-connect)

1. [トークンプロバイダーの作成](#chat-react-rooms-token-provider)

1. [接続の更新を確認する](#chat-react-rooms-connection-state)

1. [送信ボタンコンポーネントの作成](#chat-react-rooms-send-button)

1. [メッセージ入力の作成](#chat-react-rooms-message-input)

1. [次のステップ](#chat-react-rooms-next-steps)

## 前提条件
<a name="chat-react-rooms-prerequisites"></a>
+ TypeScript または JavaScript、および React Native ライブラリに精通しておいてください。React Native に慣れていない場合は、「[Intro to React Native](https://reactnative.dev/docs/tutorial)」(React Native の概要) で基本を学んでください。
+ [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\$1 環境をインストールしてください。

## ローカル認証サーバーおよび認可サーバーのセットアップ
<a name="chat-react-rooms-auth-server"></a>

バックエンドアプリケーションは、チャットルームのクライアントを認証および認可するために、Chat JS SDK に必要なチャットトークンの生成とチャットルームの作成の両方を行います。モバイルアプリでは、巧妙な攻撃者により AWS キーが抽出され AWS アカウントにアクセスされる可能性があるため、キーを安全に保存することはできません。そのため、独自のバックエンドを使用する必要があります。

「*Amazon IVS Chat の開始方法*」の「[チャットトークンを作成する](getting-started-chat-auth.md)」を参照してください。フローチャートで示されているように、チャットトークンの作成はサーバー側のアプリケーションで行われます。つまり、サーバー側のアプリケーションからリクエストし、独自の方法でチャットトークンを生成する必要があります。

このセクションでは、バックエンドでトークンプロバイダーを作成するための基本について説明します。Express フレームワークを使用して、ローカルの AWS 環境でチャットトークンの作成を管理するライブローカルサーバーを作成します。

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` コマンドに入力した名前でルートディレクトリにエントリポイントファイルを作成します。ここでは、`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 メソッドを作成したら、コア機能の認証および認可のため、`createChatToken` を `aws-sdk` と統合します。

```
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-react-rooms-chatterbox"></a>

まず、`chatterbox` という名前の React Native プロジェクトを作成します。次のコマンドを実行します。

```
npx create-expo-app
```

または、TypeScript テンプレートを使用してエキスポプロジェクトを作成します。

```
npx create-expo-app -t expo-template-blank-typescript
```

Chat Client Messaging JS SDK は、[Node Package Manager](https://www.npmjs.com/) または [Yarn Package Manager](https://yarnpkg.com/) を使用して統合できます。
+ Npm: `npm install amazon-ivs-chat-messaging`
+ Yarn: `yarn add amazon-ivs-chat-messaging`

## チャットルームに接続する
<a name="chat-react-rooms-connect"></a>

ここでは、`ChatRoom` を作成し、非同期メソッドを使用して接続します。Chat JS SDK へのユーザーの接続は、`ChatRoom` クラスで管理されます。チャットルームに正常に接続するには、React アプリケーション内に `ChatToken` のインスタンスを提供する必要があります。

デフォルトの `chatterbox` プロジェクトで作成された `App` ファイルに移動し、機能コンポーネントが返すすべてのものを削除します。事前入力されているコードは必要ありません。この時点で、`App` はほとんど空です。

**TypeScript/JavaScript**:

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

import * as React from 'react';
import { Text } from 'react-native';

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

新しい `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)のページを参照してください。

**TypeScript/JavaScript**:

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

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

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

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

## トークンプロバイダーの作成
<a name="chat-react-rooms-token-provider"></a>

次のステップとして、`ChatRoom` コンストラクターに必要なパラメータなしの `tokenProvider` 関数を作成する必要があります。まず、[ローカル認証サーバーおよび認可サーバーのセットアップ](#chat-react-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 と同じものです。

------
#### [ 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-react-rooms-connection-state"></a>

チャットアプリを作る上では、チャットルームの接続状態の変化に対応することが重要です。まずは関連イベントをサブスクライブします。

**TypeScript/JavaScript**:

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

import React, { useState, useEffect } from 'react';
import { Text } from 'react-native';
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']),
      }),
  );

  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 <Text>Hello!</Text>;
}
```

次に、接続状態を読み取る機能を提供する必要があります。`useState` フックを使用して `App` でローカルステートを作成し、各リスナー内で接続状態を設定します。

**TypeScript/JavaScript**:

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

import React, { useState, useEffect } from 'react';
import { Text } from 'react-native';
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,
        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 <Text>Hello!</Text>;
}
```

接続状態をサブスクライブしたらそれを表示し、`useEffect` フック内の `room.connect` メソッドを使用してチャットルームに接続します。

**TypeScript/JavaScript**:

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

// ...

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 (
  <SafeAreaView style={styles.root}>
    <Text>Connection State: {connectionState}</Text>
  </SafeAreaView>
);

const styles = StyleSheet.create({
  root: {
    flex: 1,
  }
});

// ...
```

チャットルームへの接続が正常に実装されました。

## 送信ボタンコンポーネントの作成
<a name="chat-react-rooms-send-button"></a>

このセクションでは、接続状態ごとに異なるデザインの送信ボタンを作成します。送信ボタンを使用すると、チャットルームでのメッセージの送信が容易になります。また、接続が切断されたりチャットセッションが期限切れになった場合に、メッセージを送信できるかどうか、いつ送信できるかを視覚的に示す役割もあります。

まず、Chatterbox プロジェクトの `src` ディレクトリに新しいファイルを作成し、`SendButton` と名前を付けます。次に、チャットアプリケーションのボタンを表示するコンポーネントを作成します。`SendButton` をエクスポートし、`App` にインポートします。空の `<View></View>` に、`<SendButton />` を追加します。

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

```
// SendButton.tsx

import React from 'react';
import { TouchableOpacity, Text, ActivityIndicator, StyleSheet } from 'react-native';

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

export const SendButton = ({ onPress, disabled, loading }: Props) => {
  return (
    <TouchableOpacity style={styles.root} disabled={disabled} onPress={onPress}>
      {loading ? <Text>Send</Text> : <ActivityIndicator />}
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  root: {
    width: 50,
    height: 50,
    borderRadius: 30,
    marginLeft: 10,
    justifyContent: 'center',
    alignContent: 'center',
  }
});

// App.tsx

import { SendButton } from './SendButton';

// ...

return (
  <SafeAreaView style={styles.root}>
    <Text>Connection State: {connectionState}</Text>
    <SendButton />
  </SafeAreaView>
);
```

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

```
// SendButton.jsx

import React from 'react';
import { TouchableOpacity, Text, ActivityIndicator, StyleSheet } from 'react-native';

export const SendButton = ({ onPress, disabled, loading }) => {
  return (
    <TouchableOpacity style={styles.root} disabled={disabled} onPress={onPress}>
      {loading ? <Text>Send</Text> : <ActivityIndicator />}
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  root: {
    width: 50,
    height: 50,
    borderRadius: 30,
    marginLeft: 10,
    justifyContent: 'center',
    alignContent: 'center',
  }
});

// App.jsx

import { SendButton } from './SendButton';

// ...

return (
  <SafeAreaView style={styles.root}>
    <Text>Connection State: {connectionState}</Text>
    <SendButton />
  </SafeAreaView>
);
```

------

次に、`App` で `onMessageSend` という名前の関数を定義して `SendButton onPress` プロパティに渡します。`isSendDisabled` という名前の別の変数 (ルームが接続されていないときにメッセージが送信されないようにします) を定義し、それを `SendButton disabled` プロパティに渡します。

**TypeScript/JavaScript**:

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

// ...

const onMessageSend = () => {};

const isSendDisabled = connectionState !== 'connected';

return (
  <SafeAreaView style={styles.root}>
    <Text>Connection State: {connectionState}</Text>
    <SendButton disabled={isSendDisabled} onPress={onMessageSend} />
  </SafeAreaView>
);

// ...
```

## メッセージ入力の作成
<a name="chat-react-rooms-message-input"></a>

Chatterbox のメッセージバーは、チャットルームにメッセージを送信するときに操作するコンポーネントです。通常、メッセージを作成するためのテキスト入力とメッセージを送信するためのボタンが含まれています。

`MessageInput` コンポーネントを作成するには、まず `src` ディレクトリに新しいファイルを作成し、`MessageInput` と名前を付けます。その後、チャットアプリケーションでの入力を表示する入力コンポーネントを作成します。`MessageInput` をエクスポートして、`App` (`<SendButton />` の上) にインポートします。

`useState` フックを使用して、`messageToSend` という新しいステートを作成します。空の文字列がデフォルト値です。アプリケーションの本文で、`messageToSend` を `MessageInput` の `value` に渡し、`setMessageToSend` を `onMessageChange` プロパティに渡します。

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

```
// MessageInput.tsx

import * as React from 'react';

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

export const MessageInput = ({ value, onValueChange }: Props) => {
  return (
    <TextInput style={styles.input} value={value} onChangeText={onValueChange} placeholder="Send a message" />
  );
};

const styles = StyleSheet.create({
  input: {
    fontSize: 20,
    backgroundColor: 'rgb(239,239,240)',
    paddingHorizontal: 18,
    paddingVertical: 15,
    borderRadius: 50,
    flex: 1,
  }
})

// App.tsx

// ...

import { MessageInput } from './MessageInput';

// ...

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

// ...

return (
  <SafeAreaView style={styles.root}>
    <Text>Connection State: {connectionState}</Text>
    <View style={styles.messageBar}>
      <MessageInput value={messageToSend} onMessageChange={setMessageToSend} />
      <SendButton disabled={isSendDisabled} onPress={onMessageSend} />
    </View>
  </SafeAreaView>
);

const styles = StyleSheet.create({
  root: {
    flex: 1,
  },
  messageBar: {
    borderTopWidth: StyleSheet.hairlineWidth,
    borderTopColor: 'rgb(160,160,160)',
    flexDirection: 'row',
    padding: 16,
    alignItems: 'center',
    backgroundColor: 'white',
  }
});
```

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

```
// MessageInput.jsx

import * as React from 'react';

export const MessageInput = ({ value, onValueChange }) => {
  return (
    <TextInput style={styles.input} value={value} onChangeText={onValueChange} placeholder="Send a message" />
  );
};

const styles = StyleSheet.create({
  input: {
    fontSize: 20,
    backgroundColor: 'rgb(239,239,240)',
    paddingHorizontal: 18,
    paddingVertical: 15,
    borderRadius: 50,
    flex: 1,
  }
})

// App.jsx

// ...

import { MessageInput } from './MessageInput';

// ...

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

// ...

return (
  <SafeAreaView style={styles.root}>
    <Text>Connection State: {connectionState}</Text>
    <View style={styles.messageBar}>
      <MessageInput value={messageToSend} onMessageChange={setMessageToSend} />
      <SendButton disabled={isSendDisabled} onPress={onMessageSend} />
    </View>
  </SafeAreaView>
);

const styles = StyleSheet.create({
  root: {
    flex: 1,
  },
  messageBar: {
    borderTopWidth: StyleSheet.hairlineWidth,
    borderTopColor: 'rgb(160,160,160)',
    flexDirection: 'row',
    padding: 16,
    alignItems: 'center',
    backgroundColor: 'white',
  }
});
```

------

## 次のステップ
<a name="chat-react-rooms-next-steps"></a>

Chatterbox のメッセージバーの作成が完了したので、この React Native のチュートリアルのパート 2「[Messages and Events](chat-sdk-react-tutorial-messages-events.md)」(メッセージとイベント) に進んでください。