銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

WebアプリでGoogleドライブのファイルを読み込む方法

f:id:takamints:20180722212809j:plain FlickrGoogle Self-Driving Car ( Creatorsmoothgroover22, License: CC BY-SA 2.0 )

ここしばらく「できないわけがないじゃないか」と調べていた個人的な大問題、「ブラウザで動作するWebアプリから、ユーザーのGoogleドライブに保存されているファイルを読み込む方法」を解決できたので小躍りしながら書いております。

Googleドライブのファイルを読み込むサンプルコードは、下記APIの説明ページで示されています。 しかしWebページのJavaScript用のがありません。 Node.jsのはありますが、これはWebのJavaScriptでは動きません。fs(ファイルシステム)モジュールを使用しており、パイプでストリームへコンテンツを流しているからです。

先に結論としてまとめてしまうと、Drive REST API Files.get を XHRで直接叩いて読み込みます。 クエリパラメータで alt=mediaを指定して、リクエストヘッダにサインインユーザーのアクセストークンを指定する必要がありますが。 どうして Files.get API の通常の(関数としての)呼び出しで、読み込めないのかわからないのですが、エラーになるので仕方がない。

一方、保存するのはこちらから!
takamints.hatenablog.jp

Node.js のfsモジュール的なメソッドでGoogle Driveを扱うモジュールを、なんとかnpmとしてまとめましたので是非どうぞー

takamints.hatenablog.jp


目次


基礎から学ぶ Vue.js
基礎から学ぶ Vue.js
posted with amazlet at 18.07.22
シーアンドアール研究所 (2018-05-29)
売り上げランキング: 1,625

やりたいことは、それじゃない

ダウンロードではありません

ここで示しているのは、WEBアプリが利用するデータファイルをGoogle Driveから読み込む方法であって、ブラウザの機能を利用してユーザーのローカルファイルシステムへダウンロードするものではありません

Drive REST API Files.get で得られる webContentLink をhrefにセットしたアンカータグ(<a href="...">...</a>)を生成すれば、ユーザーがクリックしてファイルがダウンロードできますが、JavaScriptのコードからは利用できません(CORSのエラーが発生します)。

Google Docのファイルは読み込まない。

Google Docのファイル(Googleスプレッド、ドキュメント、スライドなど)は、以下のコードでは読み込めません。

これらは別途 Files.export API で取り出せるらしいのですが、あくまでもエクスポートで、独自のWebアプリで開く方法ではありません。 各ファイルの専用アプリで表示したり編集するには、ファイルリソースの webViewLink をたどれば可能。

WEBページのJavaScriptGoogle Driveのファイルを読み込むコード

ということで、前置きが長くなりましたが、以下にコードを示します(ざっくり説明は下のほうに)。 このコードは、現在作成中のモジュール gdrive-fs からの抜粋です。

使用しているGoogle Drive APIはV3。 WebアプリケーションがOAuth2の認証を得ている前提で動作します。 ChromeFirefox、Edgeで検証済みです。

/**
 * Get a file content as text from Google Drive.
 * Even if the file is not a text actually, it could be converted
 * to ArrayBuffer, Blob or JSON to use by Web App.
 * @async
 * @param {string} fileId The file id to download.
 * @param {boolean|null} acknowledgeAbuse A user acknowledgment
 * status for the potential to abuse. This parameter is optional.
 * default value is false.
 * @returns {Promise<string>} A downloaded content as text.
 */
GdfsUi.prototype.downloadFile = async function(
    fileId, acknowledgeAbuse)
{
    return await new Promise( (resolve, reject) => {

        // Create download URL
        let apiUrl = "https://www.googleapis.com/drive/v3/files";

        // Query parameter
        let queryParams = { "alt":"media" }; //【1】
        if(acknowledgeAbuse) {
            queryParams.acknowledgeAbuse = acknowledgeAbuse; //【2】
        }
        let urlParam = Object.keys(queryParams).map(
                key => `${key}=${encodeURI(queryParams[key])}` ).join("&");

        let downloadUrl = `${apiUrl}/${fileId}?${urlParam}`; //【3】

        // Get access-token on current session【4】
        let googleUser = gapi.auth2.getAuthInstance().currentUser.get();
        let authResponse = googleUser.getAuthResponse(true);
        let accessToken = authResponse.access_token;

        // Send a request with XHR
        let xhr = new XMLHttpRequest();
        xhr.open("GET", downloadUrl, true);
        xhr.setRequestHeader("Authorization", "Bearer " + accessToken); //【5】
        xhr.onload = e => { resolve(xhr.responseText); };
        xhr.onerror = e => { reject(new Error(xhr.statusText)); };
        xhr.ontimeout = () => { reject(new Error("request timeout")); };
        xhr.timeout = 30000;
        xhr.send(null);

    });
};

ざっくり説明

※ 以下のリストの項番がコード中の【 】内の数字と合致します。

  1. ファイルの中身を取り出したいときは、クエリパラメータ alt"media"としなくてはなりません。でないとリソース情報が返ってきます。
  2. acknowledgeAbuseはよくわかっていませんが、妙なことをするかもしれないけどユーザーがそれを認識しているかどうかってことでしょうか。少なくとも自分が試した範囲では、指定しなくてもよかったのですけど・・・。Abuseフラグみたいなのがファイルについているみたいで、そのフラグが立っていると、このリクエストパラメータをtrueにしなくてはならないようです。逆に、フラグがfalseの場合に、trueを指定すると怒られます。残念。
  3. ダウンロードURLには、fileId と 上記 alt パラメータが必須です。
  4. ユーザーがサインインしたときに発行されたアクセストークンを取得しています。gapiAPIを読み込み以降利用できるグローバルオブジェクトです。gapi.auth2.getAuthInstance().currentUser.get() で、ユーザーがサインインしたときの情報が得られ、その中にアクセストークンがあります。
  5. このアクセストークンを、リクエストヘッダに設定します。本当ならこの前にアクセストークンの有効期限をチェックして無効になっているならリフレッシュトークンで再発行する必要があるかもしれません。

モジュール全体はコチラ

上記コードを含むモジュール全体は、GitHubのgdrive-fsです。 まだ作成途中ですが、UIのスケルトンやサインインシーケンスなどのコードがあります。 UIをカスタマイズできてWebアプリからGoogle Drive のファイルを扱うモジュールを目指しています。おそらくそのうち npm でPublishすると思います。

github.com

今のところhttp://localhost:8080/ホワイトリストに入れたAPIキーをリポジトリで晒しておりますので、そのままでもご確認いただけます。 セキュリティリスク大丈夫か?って警告を受けていますが、利用者のDriveのファイルが見えるだけなので問題はありません。 利用者側でAPIKeyを取っていただいても大丈夫(というかむしろ推奨)。

所感・あとがき

こんなの、今の時代すぐにでもできると思うじゃないですか。 しかし、情報を探っても辿っても途中でぶつ切れになる感じで、同じところを逡巡して「やりたいのはそれじゃない」って情報がわんさか出てきて自分のググラビリティっていったい・・・。

しかしまあ、自分の Google Drive API に関する知識はチュートリアルをやっと乗り越えられたレベルで、実装経験はほとんどゼロです。 Google Drive APIに関して、根本的な設計思想やドキュメントの行間を読む力が足りていなかったからでしょう。 そういう意味では今回いろんなドキュメントを手繰ってどうにか解決したので少しはレベルアップできたかもしれません。

日々精進。いくつになってもお勉強です。