chromeの拡張機能(manifest v3)でGoogleAPIを使う

こんにちは:relaxed:
GoogleAPIを使った拡張機能を作ろうとしたら、意外とドキュメント少なめだったので記録として残してみるね。

GoogleKeepAPIを使った拡張機能を作ろうとしたのですが、色々設定して意気揚々と試した結果、GoogleKeepAPIはenterpriseプランじゃないとダメだと知った:sob:


---- 
### 概要と注意点
基本的には下記の公式に沿っています。
https://developer.chrome.com/docs/extensions/mv3/tut_oauth/

【変更点】
・表示の方法を別タブからポップアップ形式に変更しています。
・叩いたAPIは公式と同じGoogle People APIですが、今回は簡単に連絡先に登録した人の名前を表示するだけになっています。
https://developers.google.com/people/

【注意点】
・　GoogleAPIは OAuth2の認証が必要な場合だと、公開の有無に関わらず[Chrome Developer Dashboard](https://chrome.google.com/webstore/developer/dashboard)への登録が必要です。
(登録料5$ 2022/06/29時点)

＊こちらは2020/4月の改訂で登録料の発生タイミングが変更されたようです。
https://developers-jp.googleblog.com/2020/04/chrome.html

・OAuth認証の同意画面やkeyを発行するために[GoogleCloudConsole](https://console.cloud.google.com/)での作業が必要になります。


### 完成形

こんな感じで検索ボタンを押すと連絡先一覧が表示される。
わかりやすいように連絡先ページで拡張機能を実行していますが、別のページでも大丈夫です。
![ダウンロード.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/aa19621b-3183-1f46-8fc5-7618f30df6f4.gif)


## 作成手順
### 1. 必要なファイルの準備

**1-1. ファイル作成**
sample
 -manifest.json (必須)
 -background.js (必須 今回は使わないがファイルだけ作成。touch backgroud.jsとかでやってください)
 -popup.html (必須)
 -main.css (任意)

```json:manifest.json

{
  "name": "OAuth Sample",
  "description": "Use OAuth in Extensions",
  "version": "1.0",
  "manifest_version": 3,
  "action": {
    "default_popup": "popup.html"
  },
  "background": {
    "service_worker": "background.js"
  }
}
```

```popup.html

<!DOCTYPE html>
<html >
  <head>
    <title> List</title>
    <link rel="stylesheet" href="main.css">
  </head>
  <body>
    <button id="search"> search</button>
    <ul id="result"></ul>
  </body>
</html>
```


**1-2. 拡張機能のテスト**

必要なファイルが作れたら[拡張機能](chrome://extensions)を開き、ディベロッパーモードON→パッケージ化されていない拡張機能を読み込む→今作ったsampleフォルダを選択し取り込む。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/d12d9399-3c7f-de47-a165-ef65d29f07bd.png">


この時点で拡張機能を実行すると、searchボタンがついたポップアップが表示表示される。ここまで動かしてエラーが出なければOK。

---

### 2.拡張機能の公開鍵を設定する。

**2-1.アップロード**
[Chrome Developer Dashboard](https://chrome.google.com/webstore/developer/dashboard)に移動します。
作ったsampleフォルダをzip化し、
新しいアイテム→zipファイルをアップロードを行う。
完了すると一覧に今回の拡張機能が表示されます。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/2c941355-d4f9-a299-7da0-a3ec9ad54697.png">

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/9e4dd6a6-3e72-28f4-3051-e92885f22af0.png">



ちなみにこの時点ではステータス:ドラフトなので、公開されていません。


**2-2.公開鍵の設定**
アイテム名をクリックし詳細ページに移動→パッケージ→公開鍵を表示し、コピーする。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/41e4679c-61c0-6449-ceac-a06057f55290.png">


manifest.jsonに戻って、key項目を追加する。

```
{
  "name": "OAuth Sample",
  ...
  "key": "<!--コピーしたKey!-->"
  ...
}
```


<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/7f7e1f11-0550-a250-8ca4-e7eedef2303c.png">

**2-3.拡張機能の更新**
[拡張機能](chrome://extensions)に戻る。
「更新」ボタンを押して、現状のマニフェストの内容を反映させる。
画像の通り WebStore画面のIDと拡張機能画面のIDが一致していたらOK。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/9853149e-d8fc-1f82-75db-f45f69b47e8a.png">

＊アップロードのし直しでもOK。
＊IDが同じにならない場合はkeyの値が誤っている(変な改行が入っているとか）可能性が高い。

----

### 3.OAuthクライアントの作成
**3-1.プロジェクトの作成**
[GoogleCloudConsole](https://console.developers.google.com)に移動します。
認証情報の登録、および今後APIを有効化していくのに使うプロジェクトを作成します。
既存のプロジェクトを流用したい場合はスキップ。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/59d1c28c-9a77-8409-af49-c03b69193783.png">

**3-2.OAuth同意画面を設定**
公式だとこの後認証情報を作ることになっているのですが、今だと先にOAuth同意画面を作成するよう案内が出るので、それに従って進めます。

APIとサービス→OAuth同意画面で、設定内容は各々の状況で設定してもらいたいのですが、一旦はAPIを叩くことを目的にしているので必須項目だけ埋めていきます。

＊ユーザー種類を「外部」にした場合、必ず認証に使うユーザーのアドレスを登録してください。これを登録しないとドラフト状態のアプリからOAuth認証を通そうとしたときに、エラーになります。


**3-3.認証情報の登録**
APIとサービス→認証情報→OAuthクライアントIDを選択。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/16c1213d-2dc1-49c1-d020-c1dc768457d0.png">

アプリの種類は「Chromeアプリ」を選択。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/04a40fec-4337-d701-e004-111e04c4aaf5.png">

アプリケーションIDには2-3で、WebStoreと拡張機能で共通になっているIDを記入する。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/90714f4a-810d-19ec-c7b0-a36d85892103.png">

登録すると、クライアントIDが表示されるのでコピーする。
末尾は共通で、apps.googleusercontent.comぽい。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/c33668dc-c8ae-bfa4-f228-7170dc89e21a.png">

**3-4.manifestにOAuthの設定を追記する**
3-3でコピーしたIDをclient_idに、scopesは一旦空で作成する。

```
{
  "name": "OAuth Sample",
  ...
  "oauth2": {
    "client_id": "<!--コピーしたID!-->",
    "scopes":[""]
  }, 
  ...
}
```

---

### 4.APIコールに必要な設定を追加

**4-1.manifest.jsonにpermissionsを設定**
chrome.identity・・OAuth2のアクセストークンを取得するのに使われる拡張機能のAPI。
参考: https://developer.chrome.com/docs/extensions/reference/identity/


**4-2.manifest.jsonのscopesに該当APIのスコープを設定**
先ほど空にしていたscopesを追加する。

googleAPIの場合はドキュメントに必要なScope情報が載っているのでそれを参照。ちなみに、PeopleAPIの場合はこんな感じ。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/1b67d6cd-9d8d-18f2-2c5f-3eb5377ead67.png">

**4-3.認証部分の実装**
sampleフォルダ以下にoauth.jsを作成。
まずはtokenを取得するところまで作る

```js:oauth.js

window.onload = function() {
  document.querySelector('button').addEventListener('click', () => {
    chrome.identity.getAuthToken({interactive: true}, function(token) {
      console.log("--token Start:--");
      console.log(token);
      console.log("--:token End--");
    });
  });
};

```

popup側も修正。jsを読み込むようにする。

```html:popup.html
<!DOCTYPE html>
<html >
  <head>
    <title> List</title>
    <link rel="stylesheet" href="main.css">
    <!--下記を追加!-->
    <script type="text/javascript" src="oauth.js"></script>
  </head>
  <body>
    <button id="search"> search</button>
    <ul id="result"></ul>
  </body>
</html>

```

再度拡張機能を更新し、テストを行う。
Googleの認証が表示されるので、テストするユーザーを選択する。
テスト中の場合は画像のような警告が出るが、「続行」で進める。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/979fafdc-6b38-ddb0-0c51-95f8d89665e1.png">


Tokenが正しく取得できればOK。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/51e15fae-7aca-1dae-f246-c7eb62b197c4.png">

----

### 5.APIの実装
**5-1.APIkeyの取得**
[GoogleCloudConsole](https://console.developers.google.com)に移動します。
APIとサービス→認証情報→認証情報を作成　より、新しいAPIKeyを作成します。
APIを呼ぶときに使用するので、コピーしておく。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/8653d6b2-3e39-8ced-d9f0-a738f9673847.png">

**5-2.APIを有効にする**
引き続きGoogleCloudConsoleにて、APIとサービス→ライブラリに移動。
使いたいAPIを検索し、有効化する。


**5-3.oauth.jsを更新**

```js:oauth.js

window.onload = function() {
  document.querySelector('button').addEventListener('click', () => {

    chrome.identity.getAuthToken({interactive: true}, function(token) {
      let init = {
        method: 'GET',
        async: true,
        headers: {
          Authorization: 'Bearer ' + token,
          'Content-Type': 'application/json'
        },
        'contentType': 'json'
      };

      fetch(
        'https://people.googleapis.com/v1/people/me/connections?personFields=names&key=<!--コピーしたAPIKey!-->',init)
      .then((response) => response.json())
      .then(function(data) {
         let ul_ = document.querySelector('#result');

         data.connections.forEach((element) => {
           let li_  = document.createElement('li');
           let text = document.createTextNode(element.names[0].displayName);
           li_.appendChild(text);
           ul_.appendChild(li_);
         });
      })
    });
  });
};

```

**5-4.正しく動くか確認**
拡張機能を更新し、連絡先一覧が表示されればOK！
簡単だね:relaxed:


<details><summary>最終コード</summary>

main.cssとbackground.jsは今回変更がないので割愛


```json:manifest.json

  "name": "OAuth Sample",
  "description": "Use OAuth in Extensions",
  "version": "1.0",
  "manifest_version": 3,
  "action": {
    "default_popup": "popup.html"
  },
  "background": {
    "service_worker": "background.js"
  },
  "permissions": ["identity"],
  "key": "<!--WebStoreの公開鍵!-->",
  "oauth2": {
    "client_id": "<!--GoogleCloudConsoleのクライアントID!-->",
    "scopes": ["https://www.googleapis.com/auth/contacts.readonly"]
  }
}

```

```html:popup.html
<!DOCTYPE html>
<html >
  <head>
    <title> List</title>
    <link rel="stylesheet" href="main.css">
    <script type="text/javascript" src="oauth.js"></script>
  </head>
  <body>
    <button id="search"> search</button>
    <ul id="result"></ul>
  </body>
</html>


```


```js:oauth.js
window.onload = function() {
  document.querySelector('button').addEventListener('click', () => {

    chrome.identity.getAuthToken({interactive: true}, function(token) {
      let init = {
        method: 'GET',
        async: true,
        headers: {
          Authorization: 'Bearer ' + token,
          'Content-Type': 'application/json'
        },
        'contentType': 'json'
      };

      fetch(
        'https://people.googleapis.com/v1/people/me/connections?personFields=names&key=<!--GoogleCloudConsoleで作ったAPIKey!-->',init)
      .then((response) => response.json())
      .then(function(data) {
         let ul_ = document.querySelector('#result');

         data.connections.forEach((element) => {
           let li_  = document.createElement('li');
           let text = document.createTextNode(element.names[0].displayName);
           li_.appendChild(text);
           ul_.appendChild(li_);
         });
      })
    });
  });
};



```


</details>

-----



今回はGETでしたがPOSTもそのうち試したいな。
あと、公式を参考にしたので久しぶりにフレームワークの入っていないjavaScriptを書きました。見た目綺麗にしたいので、そのうち何か入れて試したいです:relaxed: