VueアプリにGoogleアカウントを使ったサインイン機能をつける際に使っているvue-google-oauth2プラグインのコードを読んだ。
レポジトリ https://github.com/guruahn/vue-google-oauth2
全体像の把握
まずは初期化の部分から全体像を見ていく。
export default installGoogleAuthPlugin;
installGoogleAuthPluginが実際にエクスポートされて読み込まれる内容となっている。
function installGoogleAuthPlugin(Vue: typeof _Vue, options?: any): void {
// set config
...オプションの設定...
// Install Vue plugin
Object.defineProperties(Vue.prototype, {
$gAuth: {
get: () => {
return googleAuth;
},
},
});
googleAuth.load(GoogleAuthConfig, prompt);
}
installGoogleAuthPluginの内容を大枠で見ていくと、オプション(scopeやpromptなど)の調整を行った上で、Vue上で$gAuthというプロパティでgoogleAuthを参照できるようにし、その上でgoogleAuthのloadメソッドを呼び出している。
const googleAuth = ((): any => {
const installClient = () => { ... };
const initClient = (config: any) => { ... };
const Auth = function (this: any) {
this.GoogleAuth = null; /* window.gapi.auth2.getAuthInstance() */
this.isAuthorized = false;
this.isInit = false;
this.prompt = null;
...
this.load = (config: any, prompt: string) => {
installClient()
.then(() => {
return initClient(config);
})
.then((gapi: any) => {
this.GoogleAuth = gapi.auth2.getAuthInstance();
this.isInit = true;
this.prompt = prompt;
this.isAuthorized = this.GoogleAuth.isSignedIn.get();
}).catch((error) => {
console.error(error);
});
};
this.signIn = (successCallback: any, errorCallback: any) => { ... };
this.getAuthCode = (successCallback: any, errorCallback: any) => { ... };
this.signOut = (successCallback: any, errorCallback: any) => { ... };
};
return new (Auth as any);
})();
googleAuthにはコードがimportされたタイミングで生成されたAuthのインスタンスが代入されている。loadメソッド内では、installClientが実行された後にinitClientが実行され、その結果をAuthインスタンスのプロパティにセットし、外部(アプリ側)から参照できるようにしている。
Google API Javascript クライアントライブラリの読み込み
const installClient = () => {
const apiUrl = 'https://apis.google.com/js/api.js';
return new Promise((resolve) => {
const script: any = document.createElement('script');
script.src = apiUrl;
script.onreadystatechange = script.onload = () => {
if (!script.readyState || /loaded|complete/.test(script.readyState)) {
setTimeout(() => {
resolve();
}, 500);
}
};
document.getElementsByTagName('head')[0].appendChild(script);
});
};
installClientは、ページのheadタグにGoogle APIのJavascriptクライアントライブラリ(api.js)のscriptタグを追加し、ロード完了を待つPromiseを返す。このPromiseは、scriptタグを追加するだけでなく、scriptの読み込みが完了したタイミングでresolveが呼ばれ、結果が次のPromiseに渡されるようになっている。scriptの読み込み完了のハンドリング処理は、ブラウザ間の互換性対応のためonreadystatechangeとonloadの両方にセットされている。scriptタグのreadyStateがloadedまたはcompleteになったらロード完了と判定している。readyStateの判定はcompleteだけで良さそうだが、loadedも対象となっているのは互換性対策だろうか、もしステータスがcompleteではなかった場合は500ms後に再度ステータスをチェックする、というのを繰り返す。
Authライブラリの読み込みと初期化
const initClient = (config: any) => {
return new Promise((resolve, reject) => {
window.gapi.load('auth2', () => {
window.gapi.auth2.init(config)
.then(() => {
resolve(window.gapi);
}).catch((error: any) => {
reject(error);
});
});
});
};
initClient内で実行されている処理は、基本的にGoogle Sign-Inの公式ドキュメントGoogle Sign-In JavaScript client referenceで案内されている内容となっている。auth2ライブラリを読み込んだ上で、Client IDやscope、promptなどの設定が含まれるconfigを渡して初期化(init)する。なお、今のところauth2以外のライブラリ(clientなど)を読み込めるようにはなっていないため、このプラグインを使って得られた認証情報をもとに、Google API Javascriptクライアントライブラリ内のDriveやSpreadsheetの機能を使うことはできない。(requestを使ってのAPI呼び出しはできる)DriveやSpreadsheetをクライアントライブラリの機能を使って利用したい場合は、window.gapi.loadで読み込むライブラリをauth2:clientなどのように指定し、clientのinitも呼び出すようにプラグインを改造する必要がある。
initが完了したら、resolveして次のPromiseを実行する。
.then((gapi: any) => {
this.GoogleAuth = gapi.auth2.getAuthInstance();
this.isInit = true;
this.prompt = prompt;
this.isAuthorized = this.GoogleAuth.isSignedIn.get();
})
initの完了が完了すると、Google APIのauth2のインスタンスをGoogleAuthプロパティに、初期化済フラグ(isInit)をtrueにセットしている。サインイン済かどうかは、isSignedIn.get()で取得している。promptの値は、APIの初期化結果に関係がなく、またgetAuthCodeメソッドをアプリ側から呼び出したときしか参照されないので、ここでセットする必然性はないが、わかりやすさのためにここでまとめてセットしていると思われる。
mounted(){
let that = this
let checkGauthLoad = setInterval(function(){
that.isInit = that.$gAuth.isInit
that.isSignIn = that.$gAuth.isAuthorized
if(that.isInit) clearInterval(checkGauthLoad)
}, 1000);
}
loadはresolveしないので、読み込み完了時のコールバックは用意されていない。そのため、アプリ側ではsetIntervalを使って定期的に初期化が完了しているかをチェックする必要がある。
signIn, signOut, getAuthCodeメソッドについては、Google APIのメソッドをラップしているだけなので説明は省略。