AjaxでGASと通信した時に認証エラーが出る場合の対策

## 概要
AjaxでGASと通信した際にスクリプトの実行に対して権限を許可していないと認証エラーとなってしまいます。
（スプレッドシートの書き込みやカレンダーの取得等）
対策としてAjaxに失敗した場合に限り、GASでレンダリングしたHTMLを表示させることにしました。

※非同期処理の実装はこちらの記事を参考にさせていただきました。
[WebサイトのJSからAjaxでGASの関数を最短で叩く](https://qiita.com/Cyber_Hacnosuke/items/1db9085af83df2aaf42c)

## コード
まずはクライアント側です。ボタンを押したらメールアドレスやログインIDをアラート表示させる実装となっています。
また、ajaxの失敗時にも `?mode=authPage` のパラメータ付きで別タブでWEBページを開くようにしています。
URLはajaxと同様にGASのエンドポイントを利用します。

```クライアント側のサンプル.html
<!DOCTYPE html>
<html>
<head>
    <title>GASサンプル</title>
    <script src="https://code.jquery.com/jquery-3.3.1.js"></script>
</head>
<body>
    <button onclick="getMailAddress();">メールアドレスを取得</button>
    <button onclick="getUserName();">ユーザー名を取得</button>
    <script>
        function getMailAddress(){
            callAppsScript("mailAddress", function(data) {
                window.alert(data['rs']);
            });
        }
        function getUserName(){
            callAppsScript("userName", function(data) {
                window.alert(data['rs']);
            });
        }
        $(document).ready(function($) {
            // ajaxの処理
            window.callAppsScript = function(mode, callbackFunc) {
                const GAS_URL = "https://example.com/xxxxxxxxxxxxxxxxxxxxxx";
                let request = {'mode': mode};
                $.ajax({
                    type:"get",
                    url: GAS_URL,
                    data: request,
                    dataType: "jsonp",
                    success: function(data) {
                        callbackFunc(data);
                    }
                }).fail(function(jqXHR, textStatus, errorThrown ) {
                    console.log("XMLHttpRequest : " + jqXHR.status + "\n" + "textStatus : " + textStatus + "\n" + "errorThrown : " + errorThrown.message);
                    if(confirm('通信に失敗しました。認証ページを開きますか？')){
                        window.open(GAS_URL+'?mode=authPage', '_blank');
                    }
                });
            }
        });
    </script>
</body>
</html>
```

続いてGAS側です。パラメータに応じてメールアドレスやログインIDを返していますが、
`mode=authPage` の場合のみ `index.html` をレンダリングさせています。

```main.js
function doGet(e) {
  let params = e.parameter;
  let res = {};
  switch(params.mode) {
    case 'mailAddress':
      res['rs'] = Session.getActiveUser().getEmail();
      break;
    case 'userName':
      res['rs'] = Session.getActiveUser().getUserLoginId();
      break;
    case 'authPage':
      let htmlOutput = HtmlService.createTemplateFromFile('index.html').evaluate();
      htmlOutput.setTitle('GAS認証ページ').addMetaTag('viewport','width=device-width, initial-scale=1')
      return htmlOutput;
    default:
      res['rs'] = 'パラメータが定義されていません。';
      break;
  }
  let callback = params.callback;
  let output = ContentService.createTextOutput();
  let responseText;
  if (callback) {
    responseText = callback + "(" + JSON.stringify(res) + ")";
    output.setMimeType(ContentService.MimeType.JAVASCRIPT);
  } else {
    responseText = JSON.stringify(res);
    output.setMimeType(ContentService.MimeType.JSON);
  }
  output.setContent(responseText);
  return output;
}
```
描画するのは簡易的なHTMLです。
GASのエディタページで `ファイル->NEW->HTML` と操作すると作成できます。

```index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <span>認証が完了しました。このタブを閉じてアプリケーションに戻ってください。</span>
  </body>
</html>
```

ここまで出来たら `公開 -> WEBアプリケーションとして導入` から公開範囲を設定します。
例：
`Execute the app as:` `User accessing the web app`
`Who has access to the app:` `全ユーザー`
![gas.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503672/b5ca7db1-f1b2-a7e1-cf9b-bbfe1661b0e4.png)

##動作

実際の挙動です。紙芝居でご覧ください。

クライアント側HTMLのボタン押下でconfirmが表示されます。
![confirm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503672/7c10fdc3-9001-ec34-029f-409b66165aec.png)

GAS側のHTMLにアクセスする際にいつもの認証ダイアログが表示されます。
「許可」を押して進みます。
![kengen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503672/851a0467-5bb4-eff7-b6fd-05ec7d3ea813.png)

GAS側のHTMLが表示されます。
![fin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503672/1f02d037-1278-1a69-b1d6-85b90c705221.png)

元のページに戻って再度ボタンを押すと正常に機能します。
![confirm - コピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503672/457dd88c-2022-834c-b897-036160e74b18.png)

## 備考

簡単な実装ですが、問題の解決策としては十分かと思います。
switch文の中で他と違う処理を実行しているのが気持ち悪いのでパラメータ自体を別に用意した方がいいかもしれません。