UMD版Hexabase JavaScript SDKを使ってチャットアプリを作る

[Hexabase（ヘキサベース）](https://www.hexabase.com/)は企業においても安心して利用できるBaaS（Backend as a Service）を提供しています。多くのBaaSがそうであるように、主にフロントエンド開発者に利用してもらいたいと考えています。そこで現在、TypeScript SDKの開発が進められています。

元々TypeScriptで作られていることもあって、Next.jsやVue.jsなどでimportして利用することができます。しかし、もっと普通のWebアプリケーションなどで使う際には、CDN経由で利用したいと考える方もいるでしょう。そこで、UMD版Hexabase JavaScript SDKを作成しました。

今回は、そのUMD版Hexabase JavaScript SDKを使ったチャットアプリのデモを紹介します。

## デモ

デモはこちらのURLで体験できます。

[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/0101aaba-04b1-d4f4-e4e1-0c9892d7335e.png)](https://jsfiddle.net/goofmint/z0m1dc4j/36/)

https://jsfiddle.net/goofmint/z0m1dc4j/36/

## SDKの読み込み

SDKは下記URLで読み込めます。

```html
<script src="https://cdn.jsdelivr.net/npm/@hexabase/hexabase-js@latest/dist/umd/hexabase.min.js"></script>
```

バージョン指定する場合は、以下のようになります。メジャーバージョンが上がらない限りはlatest指定をお勧めします。

```html
<script src="https://cdn.jsdelivr.net/npm/@hexabase/hexabase-js@2.0.6/dist/umd/hexabase.min.js"></script>
```

モジュールとして使う場合には、以下のようになります。

```html
<script type="module">
import hexabase from 'https://cdn.jsdelivr.net/npm/@hexabase/hexabase-js@latest/+esm'
</script>
```

## SDKの初期化

SDKは以下のようコードで初期化します。これさえ終われば、後はTypeScriptと同じように使えます。

```javascript
const { HexabaseClient } = hexabase;
const client = new HexabaseClient();
```

## HTMLについて

HTMLはBootstrapを使って作成しています。selectタグで認証を簡易的にできるようにしています。

```html
<div class="container" style="padding-top: 1em;">
  <div class="row justify-content-md-center">
    <div class="col-10">
      <form>
        <div class="row g-3">
          <div class="col">
            <select id="user" class="form-control">
              <option>Log in as...</option>
              <option value="demo5">Demo 5</option>
              <option value="demo6">Demo 6</option>
            </select>
          </div>
        </div>
        <div class="login">
        
        </div>
        <br />
        <div class="row g-3" style="padding-top: 1em;">
          <div class="col chat">
            
          </div>
        </div>
        <div class="row g-3">
          <div class="col">
            <input type="text" class="form-control" name="comment" aria-describedby="emailHelp" placeholder="チャットメッセージ">
          </div>
          <div class="col">
            <button type="submit" name="Button" id="button" class="btn btn-primary" disabled>メッセージ</button>
          </div>
        </div>
      </form>
    </div>
  </div>
</div>
```

## JavaScriptについて

まず設定として、認証情報や操作するワークスペース・プロジェクト・データストア・アイテムを定義します。

Hexabaseではルートオブジェクトがワークスペース、その中に複数のプロジェクト、さらにデータストアとなっています。データストアの行単位のデータがアイテムです。

```js
const { HexabaseClient } = hexabase;
const client = new HexabaseClient();

// 認証用のメールアドレスとパスワード
const logins = {
	demo5: {
  	email: 'demo5@moongift.jp',
		password: 'AAAAA',
  },
  demo6: {
  	email: 'demo6@moongift.co.jp',
		password: 'BBBBB',
  }
};

// Hexabaseのワークスペース、プロジェクト、ワークスペースのID
const workspaceId = '65b23ad3c1d10befed7a8ad3';
const projectId = '65b315e1af0bb018ee33678a';
const datastoreId = '65d30e653f7e0afce520e6e5';
const itemId = '65d30e6a3f7e0afce520e6fd';

// 複数の処理で使う変数用
const params = {
	item: null, // データストアアイテム
  histories: [], // 既存コメント
};
```

続いて、必要なDOMを定義します。

```js
// DOM
const form = document.querySelector('form');
const comment = document.querySelector('[name="comment"]');
const button = document.querySelector('#button');
const select = document.querySelector('#user');
const login = document.querySelector('.login');
```

## 認証処理

ドロップダウンでアカウントを指定したら、認証処理を実行します。認証したら、ワークスペース・プロジェクト・データストア・アイテムをそれぞれ初期化します。

また、アイテムの `subscribe` メソッドを実行し、リアルタイム通知を購読します。リアルタイム通知で実行される関数は `recieve` としています。

```js
select.onchange = async (e) => {
	// ログインする
	const { email, password } = logins[e.target.value];
  await client.login({email, password});
  // ワークスペース・プロジェクト・データストアの取得
  await client.setWorkspace(workspaceId);
  const project = await client.currentWorkspace.project(projectId);
  const datastore = await project.datastore(datastoreId);
  // チャット対象のデータストアアイテム
  params.item = await datastore.item(itemId);
  // リアルタイム通知の購読を開始
  params.item.subscribe('update', recieveComment);
  // コメントを取得。取得した際には最新が最初にあるので、リバースする
  params.histories = (await params.item.histories()).reverse();
  // チャットを描画
  renderChat();
  // ログイン情報を表示
  login.innerHTML = `Logged in as ${client.currentUser.userName}`;
  // ボタンを押せるようにする
  button.disabled = false;
  // コメントに入力フォーカスを置く
  comment.focus();
};
```

`renderChat` メソッドは既存のチャット履歴を取得、表示する関数です。今回は最新の5件だけ表示しています。表示した後、ゴミ箱アイコンのタップで `deleteHistory` 関数を呼び出す指定をしています。これはコメントを削除する機能です。

```js
// チャットを一覧表示する処理
const renderChat = () => {
  // ログインユーザー情報
	const user = client.currentUser;
  // 履歴を一覧表示
	document.querySelector('.chat').innerHTML = params.histories
  	.slice(-5)
    .map(history => `
			<div class="row" style="padding-top: 1em;">
				<div class="col-12">
					${history.comment}
				</div>
				<div class="col-12">
					👤 ${history.user.userName} 🕒 ${history.createdAt.toLocaleString()}
					${
						user.id === history.user.id ?
						`<span class="delete" data-id=${history.id}>🗑️</span>`:
						''
					}
				</div>
			</div>
  	`).join('');
  // 削除アイコンに対するイベントを追加
  document.querySelectorAll('.chat .delete')
  	.forEach(dom => dom.onclick = deleteHistory);
};
```

## コメントを送信する処理

コメントはボタンをクリックしたタイミングで送信します。アイテムの `comment` メソッドで新しいコメントを作成し、 `comment` フィールドに対して入力されたコメントをセットします。後は `save` メソッドを実行すれば、コメントが保存されます。

```js
// 新しいチャットメッセージを追加する処理
button.onclick = async (e) => {
	e.preventDefault();
  // 新しいコメントを作成して保存
  const comment = params.item.comment();
  await comment
  	.set('comment', form.elements.comment.value)
    .save();
  // フォームをリセット
  form.reset();
};
```

## コメントの削除

コメントの削除は `deleteHistory` 関数で行います。これは自分の作成したコメントしか削除できないので注意してください。

```js
// チャットを削除する処理
const deleteHistory = (e) => {
  e.preventDefault();
  // 該当するチャットを取得
  const id = e.target.getAttribute('data-id');
  const history = params.histories.find(h => h.id === id);
  // チャットデータがあれば削除
  if (history) history.delete();
  // 一覧から取り除く
  params.histories = params.histories.filter(h => h.id !== id);
  // 画面を再描画
  renderChat();
}
```

## コメントの受信

他の人が書いたコメントを受信した際の処理です。ここでは受け取った内容をJSONとして解釈し、その内容で画面に表示します。

```js
// コメントが追加された際に呼ばれる処理
const recieveComment = (data) => {
	params.histories.push(data);
	renderChat();
}
```

## まとめ

今回の流れで、Webアプリの中にチャット機能を組み込めます。データストアのアイテムを介して行う点に注意してください。

データストアのアイテムをスレッドに見立てれば、複数ルームでのチャットも可能です。ぜひ皆さんのWebアプリにチャット機能やリアルタイムコラボレーション機能を追加してください。

[Hexabase \| 新規事業向け開発・競争領域でのDX実現をサポート](https://hexabase.com/)