Gatsby と Zendesk でお問い合わせ機能

はじめに

Gatsby で構築した Web サイトにお問い合わせ機能を実装したのですが、色々調べて試行錯誤したので備忘録です。

前提

Zendesk の採用について

お問い合わせ機能は大雑把に、下記から構成されると考えられます。

  1. お問い合わせフォーム UI
  2. UI からリクエストを受けるバックエンド
  3. バックエンドによって記録されたお問い合わせを管理する機能

2.で特定メールアドレスにメールを送信するだけで 3.は割愛、とかもあり得ますが、今回のケースでは(プライベートでない限りはほとんどのケースだと思いますが)、少額であれば有償でも 3.を充実させたく、Zendesk Support のもっとも安いプランを利用することにしました。

検討内容

ゴール

Gatsby で構築した Web サイトから Zendesk における匿名ユーザー(非認証)リクエストを受け付けること。

選択肢1. ヘルプセンター内にある「リクエストを送信」フォーム

「Guide設定」でヘルプセンターをアクティブ化するだけで該当フォームが含まれるページが利用可能になるため、「お問い合わせ」として、そこへの外部リンクを貼れば完成です。

ただし、デフォルトのページがイマイチで、かつ、「リクエストを送信」等の文言変更含めたカスタマイズをしたければ、プランをアップグレードしなければいけないようです。残念・・・。

選択肢2. Web Widget

Web Widget API を使って、Web Widget を組み込むことができます。今回フォーカスする Contact Form API だけではなく、Chat API 等も含む(かつ、よく利用される)せいか、明示的に chat という言葉が含まれているプラグイン名がやや引っかかりますが、gatsby-plugin-zendesk-chat を使えば組み込めます。

ただし、Web Widget にはカスタマイズ自体がそもそもできない項目(ボタンのアイコン等)があるのと、ボタンを押してフォームが開くという UI 形式を許容できるかは気になる所ですね。

gatsby-plugin-zendesk-chat について

Web Widget 導入のためには、次のスクリプトをコピーして、WebサイトのHTMLソースコードの</body>の終了タグの前に貼り付けてください。 とのことなんですが、Gatsby を使う以上、該当箇所に貼り付けるためには onRenderBody で、setPostBodyComponents する必要があります。

本プラグインはオプションとして渡すキーを使って上記を実現するためだけのプラグインです。

Web Widget の表示・非表示制御

ページによって表示・非表示を制御したい場合、 React Helmet を利用して core commands を呼び出せば良いです。

表示
<Helmet>
  <script type="text/javascript">
    {`
      zE('webWidget', 'show');
    `}
  </script>
</Helmet>
非表示
<Helmet>
  <script type="text/javascript">
    {`
      zE('webWidget', 'hide');
    `}
  </script>
</Helmet>

選択肢3. REST API

Support APIRequests を使います。

ただし、ブラウザで動かす以上、CORS 問題が想定されるわけですが、ドキュメントを確認すると、 Client-side CORS requests are supported if the request is authenticated with an OAuth access token. The requests are not supported if the request uses basic authentication or a Zendesk API token. と記述がありました。

つまり、OAuth を利用しない今回のケースだとダメかー、対象 API を呼び出す Cloud Functions を用意し、Cloud Functions を Firebase Hosting に接続する必要があるかー(該当 Web サイトは Firebase Hosting を利用)、でもその場合、Firebase 無料プランだと Outbound networking Google services only となっているからプランをアップグレードしないとダメかー、・・・とか一気に面倒になってきたのですが、ドキュメントのリンクが貼ってあった Making cross-origin, browser-side API requests というヘルプセンター内の記事を確認すると、なんと、 Exceptions: A few Zendesk API endpoints don't require any authentication at all. They include the Create Request and Search Articles endpoints. CORS is implemented for these endpoints. とのこと。

つまり、CORS 問題はクリアですね。

ということで、下記のようなメソッドを用意して、フォームから受け取った適切なパラメータで呼び出せば OK です。

type ContactData = {
  request: {
    requester: {
      name: string;
      email: string;
    };
    subject: string;
    tags: string[];
    comment: {
      body: string;
    };
  };
};

async function callRestApi(zendeskHost: string, data: ContactData): Promise<boolean> {
  const requestUrl = `${zendeskHost}/api/v2/requests.json`;

  const url = requestUrl;
  const response = await axios.post<{
    request: {
      id: number;
    };
  }>(url, data);
  // 必要ならリクエスト ID 返しても良いかも。
  return response.status >= 200 && response.status < 300;
}

フォーム実装は割愛しますが、React Hook Form という素晴らしいライブラリを使えばそこまで手間なく実装できると思います。

おわりに

最終的には、実装量は多くても、カスタマイズ性を優先し「選択肢3. REST API」を採用しました。