前回
ブックマークレットを作ってみたものの、クロスドメインエラーが出て動かなかった
クライアント側だけで解決する方法が思いつかなかったので、Chrome extensionを作りました。
目次
Chrome extensionとは?
Chromeの機能を拡張するためのプラグインです。
Chrome ウェブストアでいろいろ公開されています。
上記でダウンロードするほかに、自分で作ったソースを読み込んで使うこともできます。
開発準備
言語
- HTML
- CSS
- JavaScript
ライブラリ
開発対象
Redmineの画面上でボタンを押すと、TaskWorldにタスクを自動追加するpage_actionを作ります。
ちなみに、似たような機能でbrowser_actionというのもありますが、
page_action:特定のページで動作するもの
browser_action:すべてのページで動作するもの
ということのようです。
page_actionの場合、対象のページ以外はextension ボタンが非活性になります。
処理概要
オプションページ
- ユーザーが入力したTaskWorldのEmailとPasswordを引数にauth APIを実行する
- 取得したaccess_tokenをlocalStorageに保存する
チケット登録(extensionボタン押下)
- Redmineから情報を収集する
- オプションページで登録したaccess_tokenをlocalStorageから取得する
- task.create APIを実行し、TaskWorldにタスクを作成する
- APIの実行結果からtask_idを取得する
- 取得したtask_idを引数にtask.update APIを実行し、作成したタスクに情報を追加する
構成
こんな感じ。
※.gitignoreはgitの除外設定ファイルなので不要
※.vscodeはVS Codeの設定ファイルなので不要
開発開始
manifest.json
extensionの設定を書きます。
今回大事なのはここ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
"permissions" : [ "declarativeContent", "tabs", "https://asia-api.taskworld.com/*" // APIで使用するドメイン(複数あれば全部指定) ], "content_scripts": [ { "matches": ["https://my.redmine.jp/*"], // scriptを読み込むページを指定 "js": [ "js/jquery-3.3.1.min.js", // 関係ある? HTMLと同じく外部ファイルから先に読み込む "js/const.js", "js/common.js", "js/script.js" ], "run_at": "document_end" } ], "background": { "scripts": [ "js/jquery-3.3.1.min.js", // backgroundでも外部JSが使える "js/const.js", "js/background.js" ] }, |
background.js
extensionの動作ルールを追加
開いているページのURLがRedmineのページだったら、extensionボタンが有効になるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
chrome.runtime.onInstalled.addListener(function() { // Replace all rules ... chrome.declarativeContent.onPageChanged.removeRules(undefined, function() { // With a new rule ... chrome.declarativeContent.onPageChanged.addRules([ { // That fires when a page's URL contains a 'g' ... conditions: [ new chrome.declarativeContent.PageStateMatcher({ pageUrl: { urlContains: redmineDomain }, // const.jsにあるRedmineのDomainを指定 }) ], // And shows the extension's page action. actions: [ new chrome.declarativeContent.ShowPageAction() ] } ]); }); }); |
extensionボタン押下時のイベントを設定
extensionボタンが押されたことをscript.jsに通知します。
1 2 3 |
chrome.pageAction.onClicked.addListener(function(tab) { chrome.tabs.sendMessage(tab.id, "Action"); }); |
backgroundで処理する内容を書く
上記と反対に、他のjsからmessageを受信したときに実行する処理を書きます。
messageの内容はrequestオブジェクトの中に入っています。
処理を終えたら、sendResponse()で呼び出し元にレスポンスを返します。
manifest.json > permissionでドメインを指定しておくと、XMLHttpRequest()で複数のドメインとやり取りできるようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { console.log("background start"); console.log(request.method); var ret; switch(request.method){ case "setLocalStorage": localStorage.setItem(request.key, request.data); console.log(localStorage.getItem(request.key)); sendResponse(true); return true; break; case "getLocalStorage": console.log(localStorage.getItem(request.key)); sendResponse(localStorage.getItem(request.key)); return true; break; case "postAPI": var xhr = new XMLHttpRequest(); xhr.timeout=3000; xhr.open("POST", request.url); xhr.setRequestHeader("Content-Type", "application/json"); xhr.responseType = "json"; xhr.onreadystatechange = function() { switch (xhr.readyState){ case 0: case 1: case 2: case 3: case 4: ret = xhr.response; if(ret !== null){ console.log(ret); sendResponse(ret); } break; }; }; xhr.send(JSON.stringify(request.sendData)); break; default: break; } console.log("background end"); return true; }); |
common.js
background.jsを呼び出す関数を作成。
非同期なので、promise()をreturnしてresolve()で戻り値を返します。
オプションページ
こんなのを作ります。
options.html
入力フォームをHTMLで作成します。
情報の送信はoptions.jsで行うので、HTMLのheadで必要なファイルを読み込んでおきます。
options.js
auth APIで認証情報を取得したaccess_tokenをLocalStorageに保存し、content_scriptsで使えるようにします。
ただし、LocalStorageは同じページ内でないと使えないので、
- options.jsからbackground.jsにaccess_tokenを渡してLocalStorageに保存
- scripts.jsからbackground.jsにkeyを渡してLocalStorageから取得
とします。
APIの実行とbackground.jsとのやり取りは非同期なので、promiseを使って非同期処理が終了してから残りの処理を実行するようにしています。
(と思ったらPOSTはpromise&common.js使ってなかった…まぁいいか…)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$.post( apiUrl.auth, sentData ).done(function(data){ console.log("Task Createの実行結果"); console.log(data); Cookies.set("token",data.access_token); // 次回表示したときもトークンを画面に表示するため、cookieに登録 var promise = setLocalStorage("token", data.access_token); // background.jsのsetLocalStorage実行 promise.done(function(data){ // backgroundから値が返ってきた後に実行 if(data){ alert("認証情報の取得に成功しました"); console.log(data); $("#token").html(Cookies.get("token")); // 画面にトークン描画 return data; } }) |
ここまで作ったら、chromeの拡張機能でソースを読み込めば、オプションページが動く、はず。
次回、content_scriptsを作成してextensionの動作確認をします。