Webex Meetingsの組み込みアプリとして簡易チャットアプリを作る

この記事はシスコの有志による Cisco Systems Japan Advent Calendar 2024の一つとして投稿しています。

2024年版: https://qiita.com/advent-calendar/2024/cisco

以下、昨年までのものです。

2023年版: https://qiita.com/advent-calendar/2023/cisco
2022年版: https://qiita.com/advent-calendar/2022/cisco
2021年版(1枚目): https://qiita.com/advent-calendar/2021/cisco
2021年版(2枚目): https://qiita.com/advent-calendar/2021/cisco2
2020年版(1枚目): https://qiita.com/advent-calendar/2020/cisco
2020年版(2枚目): https://qiita.com/advent-calendar/2020/cisco2
2019年版: https://qiita.com/advent-calendar/2019/cisco
2018年版: https://qiita.com/advent-calendar/2018/cisco
2017年版: https://qiita.com/advent-calendar/2017/cisco

## やったこと

Webex Meetingsの組み込みアプリとしてWebex Messagingにメッセージや画像などのファイルを送れる簡易チャットアプリを作成しました。

![minimal-webex-chat.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176968/ef29bf75-c219-ffc6-c231-9cb8a28b8f8a.gif)

:::note info
この動画ではWebex MeetingsアプリのインターフェイスからWebex Messagingへメッセージとファイルを送信しています。きちんと送信できていることを示すためにWebex Messagingアプリを横に配置しています。
:::

## モチベーション

もちろんWebex MeetingsアプリとWebex Messagingアプリを交互に切り替えながらやればいいのですが、ミーティングを集中して聞きながらいろいろ操作したくないので、Webex Meetingsアプリのインターフェイス内で完結させたいというのが狙いです。Webex MeetingsでのメモやスクリーンキャプチャをWebex Messagingのスペースへすぐに共有したいときに（ちょっとだけ）便利です。

## そもそもWebex組み込みアプリについて

Webex MeetingsアプリもしくはWebex Messagingアプリのインターフェイス内に他のアプリを組み込むことができます。今回はWebex Meetingsの組み込みアプリを作ってます。

Webex組み込みアプリを自分以外のWebex Meetings参加者に共有したり、Webex組み込みアプリを実行したユーザーの情報を取得したいときなどはWebex組み込みアプリ用のJavascript SDKを利用する必要があるのですが、今回作成するWebex組み込みアプリのように自分だけ利用できればよく、ユーザーの情報も必要としない場合には、通常のWebアプリをWebex組み込みアプリとして利用できます。Webアプリを作成できる人であれば、自分専用のWebex組み込みアプリを特別な知識なしに簡単に作れてしまいます。

:::note warn
この記事で解説するWebex組み込みアプリの登録方法やアプリそのものは自分専用のWebex組み込みアプリを作るためのものです。自分以外のWebex Meetings参加者にも共有するようなWebex組み込みアプリについてはこの記事で解説する以上の考慮が必要です。
:::

**[Webex for Developers] Embedded Apps Developer Guide**
https://developer.webex.com/docs/embedded-apps-guide#developer-quick-start

**[Qiita] WebexのAppを自作してみる！**
https://qiita.com/KomiKoh/items/46aa112cce174abc044c

## Webex組み込みアプリの登録

先にWebex組み込みアプリの登録について解説します。

以下のWebex組み込みアプリ登録ページから必要な項目を入力して`Add Embedded app`ボタンをクリックします。

**[Webex for Developers] New Embedded app**
https://developer.webex.com/my-apps/new/embedded-app

以下の項目を入力します。
それ以外の項目は未記入もしくはデフォルトのままで結構です。

* **Where does your app work?:** Meeting
* **Embedded app name:** 任意
* **App Hub Description:** 任意
* **Icon:** 任意
* **Valid domains:** アプリをホストしたドメイン名（例 example.com）
* **Start page URL:** アプリのURL（例 https://example.com）

## 作成したWebex 組み込みアプリの概要

繰り返しになりますが、自分専用のWebex組み込みアプリを作る分には特別なことは必要ありません。なので、Webex Messagingのための簡易チャットWebアプリの解説と思っていただいても差し支えないです。

WebフレームワークにはNuxt3を用いました。

フロントエンドからバックエンドのAPIサービスに（メッセージやファイルなどの）必要な情報を送信して、バックエンドからWebex APIをコールする構成にしています。

:::note info
Webex APIのアクセストークンを直接フロントエンドに露出させないようにするため、バックエンドを介してAPIを呼び出す構成にしてます。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176968/25e0c63a-74d1-d7ec-e6d7-a1162f4223ac.png)

:::note info
Webex組み込みアプリはインターネットからアクセスできる必要があります。
（Webex組み込みアプリとしての）Nuxt3アプリのコンテンツをホストできて、かつ、バックエンドの処理も実行できて、無償で利用できるサービスとしてVercelを用いています。
:::

**[Vercel] Nuxt on Vercel**
https://vercel.com/docs/frameworks/nuxt

**[Vercel] Vercel Functions**
https://vercel.com/docs/functions

## バックエンドコードの解説

前述の通りWebex APIとのやりとりはバックエンドで実行されるので、バックエンドの実装のみを解説します。

### ルームの取得

Botがメンバーとして帰属するルームの一覧を取得して返します。
`WEBEX_BOT_TOKEN`はVercel環境変数として渡しています。

**server/api/rooms.js**

```
export default defineEventHandler(async (event) => {
  try {
    const response = await fetch('https://webexapis.com/v1/rooms/', {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.WEBEX_BOT_TOKEN}`
      },
    });

    if (!response.ok) {
      // Handle HTTP errors
      const errorText = await response.text();
      console.error(`Error: ${response.status} - ${errorText}`);
      return { status: 'error', message: 'Failed to retrieve rooms' };
    }

    const data = await response.json();
    return { status: 'success', items: data.items };
  } catch (error) {
    console.error('Fetch error:', error);
    return { status: 'error', message: 'An error occurred while retrieving rooms' };
  }
});
```

**[Webex for Developers] List Rooms**
https://developer.webex.com/docs/api/v1/rooms/list-rooms

**[Vercel] Environment variables**
https://vercel.com/docs/projects/environment-variables

### メッセージの送信

フロントエンドから渡った`roomId`、`message`の情報からメッセージを送信します。

**server/api/message.js**

```
export default defineEventHandler(async (event) => {
  try {
    const body = await readBody(event);
    const response = await fetch('https://webexapis.com/v1/messages/', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.WEBEX_BOT_TOKEN}`
      },
      body: JSON.stringify({ roomId: body.room, text: body.message }),
    });

    if (!response.ok) {
      // Handle HTTP errors
      const errorText = await response.text();
      console.error(`Error: ${response.status} - ${errorText}`);
      return { status: 'error', message: 'Failed to send message' };
    }

    return { status: 'success', message: 'Message sent successfully' };
  } catch (error) {
    console.error('Fetch error:', error);
    return { status: 'error', message: 'An error occurred during message sending' };
  }
});
```

**[Webex for Developers] Create a Message**
https://developer.webex.com/docs/api/v1/messages/create-a-message

### ファイルの送信

フロントエンドから渡った`roomId`、`message`、`dataURL`、`filename`の情報からファイルを送信します。

ファイルを送信するには、フォームデータを送信するときに使われる`multipart/form-data`を用いる必要があります。

`toBlob`関数はフロントエンドからもらった`dataURL`形式のデータを`multipart/form-data`で送信するために`Blob`形式に変換してます。

**server/api/file.js**

```
export default defineEventHandler(async (event) => {
  try {
    const body = await readBody(event);
    const blob = toBlob(body.dataURL);
    if (!blob) {
      return { status: 'error', message: 'Invalid blob data' };
    }

    const formData = new FormData();
    formData.append('roomId', body.room);
    formData.append('text', body.message);
    formData.append('files', blob, body.filename);

    const response = await fetch('https://webexapis.com/v1/messages/', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.WEBEX_BOT_TOKEN}`
      },
      body: formData,
    });

    if (!response.ok) {
      // Handle HTTP errors
      const errorText = await response.text();
      console.error(`Error: ${response.status} - ${errorText}`);
      return { status: 'error', message: 'Failed to send file' };
    }

    return { status: 'success', message: 'File sent successfully' };
  } catch (error) {
    console.error('Error:', error);
    return { status: 'error', message: 'An error occurred during file sending' };
  }
});

```

**[Webex for Developers] Message Attachments**
https://developer.webex.com/docs/basics#message-attachments

## 成果物

作成したWebex組み込みアプリを以下に公開しています。
あくまでプロトタイプと思っていただけると幸いです。エラー処理などは不適切なところも多々あると思います。

**[github.com] minimal-webex-chat**
https://github.com/tetsusat/minimal-webex-chat