komeの備忘録

東大理系大学院生の技術ブログ。たまに趣味。

firebase cloudfunctionsでspreadsheet連携

CloudfunctionsでGoogle SpreadSheet APIを動かしたい

基本的には以下のGoogle 公式コードを動かすまでを解説

github.com

自力でやるには、spreadsheetの認証周りが結構大変なので、サンプルコードを最大限再利用するのがオススメ。

先に手順を説明した後に解説する。

手順編

注1) 操作したいspreadsheetは事前に作成してIDをメモしておく。
注2) firebase CLIは事前にインストールしておく。

1. コードをpullする

$ git clone https://github.com/firebase/functions-samples

2. API設定

  • Firebase projectを作成
  • このリンクを踏みOAuth2クライアントIDを生成
  • ウェブアプリケーション用を選択
  • https://[プロジェクト名].firebaseapp.com/oauthcallback を認証済みのリダイレクトURIに登録
  • 保存
  • 表示されるクライアントIDクライアントシークレットをメモ

3. cloudfunctionsのコンフィグ設定

$ firebase functions:config:set googleapi.client_id="[クライアントID]"
$ firebase functions:config:set googleapi.client_secret="[クライアントシークレット]"
$ firebase functions:config:set googleapi.sheet_id="[spreadsheet ID]"

4. デプロイ

$ firebase deploy

5. 認証

https://[プロジェクト名].firebaseapp.com/authgoogleapi にアクセスするとOAuth認証が実行される。これは一度だけ実行すれば良い。

6. 連携できる!

サンプルコードをそのまま試すには、testsheetwrite()のエンドポイントにアクセスすると、Firebase Realtime Databaseの変更をフックしてSpreadSheet書き込みが実行される。

解説編

OAuth2

詳細な解説は後日書きたい。大雑把に言うと、あるサービスのリソースをそのサービスのID/credentialを譲渡せずに第三者が利用可能になるよう認可するようなサービス。
最終的に、spreadsheetAPIへのアクセストークンが発行されればゴール。

5.認証 の部分で一度だけ実行すればいいと言うのは、一回認可すればトークンが発行され、以後そのトークンを使ってAPIを利用できるからである。

サンプルコード概説

./firebase.json

地味にここがキモなんですが、しれっと "hosting" : {"rewrites": [ ... と言うオプションが入っている。 firebase HostingのURL書き換えを利用して、cloud functionのエンドポイントをfirebase Hostingのドメインにしている。

なぜこんなことをするかというと、OAuth時のリダイレクト先のURLが認証済みドメインである必要があるためである。

OAuthの流れとしては、後述の/authgoogleapi/oauthcallback と言うcloud functionが呼び出される。 /authgoogleapi でOAuth2認証・認可を行い、spreadsheetAPI用のトークンを取得。そのトークンを保存する処理を/oauthcallbackで実行する。このコールバック処理がどこでも実行されてしまうのは困るわけだから、事前に登録されたドメインのみに制限されている。
cloud functionのデフォルトのエンドポイントは、https://[REGION_NAME]-[PROJECT_NAME].cloudfunctions.net/[FUNC_NAME] になっており、このドメインはGCPのデフォルトで承認されていないようである。

なのでやり方としては、

  • .cloudfunctions.netのドメインをGCP projectに登録
  • .cloudfunctions.netを登録済みドメインにredirect

があるわけだが、GCPコンソールでの操作が最小限で済む後者をサンプルコードでは採用している。

firebaseapp.comドメインはデフォルトで登録されている(hosting用ドメインだからね...)ので、こちらにURI rewriteを使って転送している。

従って$ firebase deploy を叩くと、firebase hostingとfirebase functionsの両者がデプロイ対象になる。

./funcitons/index.js

まず実装されている関数を整理すると以下の通りとなる。

exports.authgoogleapi = functions.https.onRequest()
exports.oauthcallback = functions.https.onRequest()
exports.appendrecordtospreadsheet = functions.database.ref.onCreate()
function appendPromise()
async function getAuthorizedClient()
exports.testsheetwrite()

exportsされている関数はcloud functionで実行管理されるものとなる。
authgoogleapioauthcallbackは、OAuthに使われるエンドポイントである。
appendrecordtospreadsheetがspreadsheet書き込みの関数になる。サンプルコードではfirebase realtime databaseの要素追加でフックしている。

OAuth用のクライアントIDとクライアントシークレットは、cloudfunctionの環境変数に設定してあるので(config:setのコマンド)、関数実行時に取得できる。
API tokenは初期状態ではもちろん存在しない。OAuth後にoauthcallbackで受け、サンプルではrealtime databaseに保存している。
そして次回実行以降はそこから読み出すようにしている。

処理実行の本体はappendrecordtospreadsheet()関数である。
spreadsheet APIの実行には、clientインスタンスが必要だが、これをgetAuthorizedClient()で取得している。 最終的にPromissをreturnしているが、これはcloudfunctionの要件である。

注) ちなみにasyncを使っているので、Nodeのバージョンは8以降でないといけない。
CloudFunctionsでver8以降を使うには、package.jsonに

"engines": { "node": "8" }

を追加しておく必要がある。

要はOAuth2をgoogleapiを使って実装するということである。しかしただでさえfirebase CLIとかspreadsheet APIとかを調べるので大変で、更にgoogleapiでOAuthの部分を調べて実装するコストを考えると、せっかく本家のコードがあるんでこれを丸っと使わない手はないだろうと言う感じで、今回の手法となった。

参考にしたサイト

functions-samples/google-sheet-sync at Node-8 · firebase/functions-samples · GitHub