Chrome Extension開発を勉強してみる (13) - oauth.jsでTwitter API -

2012-08-02T00:00:00+00:00 Chrome Extension JavaScript

oauth.jsを使ってTwitter APIを利用するのは前にもやったのですが、その際にはAccess Tokenをhttp://dev.twitter.comから発行してそのままコピペしてやったのでAccess Tokenを取得して云々するという所を完全にすっ飛ばした。なので今回はその処理を含めてやってみる

概要

ブラウザアクションをクリックした際に出るポップアップにホームタイムラインを表示する。その際に認証されてなかったらOAuthで認証を要求させてAccess Tokenを取得する。あくまでAccess Token自体をソース内にコピペはしない。sha1.js、oauth.js、jquery.jsは別途でダウンロードしてくる

で今回はChrome ExtensionなのでTwitter OAuthのCallback URLは空にしてPINを画面上に出るようにする。その際にそのPINをコピペしなくてもそこはコンテントスクリプトを使って自動で処理出来るようにする

twitter.js

oauth.jsを使ってTwitter APIを云々するJavaScriptを作る

const CONSUMER_KEY = "省略";
const CONSUMER_SECRET = "省略";

const OAUTH_SIGNATURE_METHOD = "HMAC-SHA1";

var Twitter = function() {
  this.access_token = localStorage.getItem("access_token");
  this.access_token_secret = localStorage.getItem("access_token_secret");
  this.user_id = localStorage.getItem("user_id");
};

Twitter.prototype.parseToken = function(data) {
  var tokens = data.split("&");
  var parsedToken = {};

  tokens.forEach(function(token) {
    var kv = token.split("=");

    parsedToken[kv[0]] = kv[1];
  });

  return parsedToken;
};

Twitter.prototype.login = function() {
  // リクエストートークンを取得する

  var message = {
    "method": "GET",
    "action": "https://api.twitter.com/oauth/request_token",
    "parameters": {
      "oauth_consumer_key": CONSUMER_KEY,
      "oauth_signature_method": OAUTH_SIGNATURE_METHOD
    }
  };

  var accessor = {
    "consumerSecret": CONSUMER_SECRET
  };

  OAuth.setTimestampAndNonce(message);
  OAuth.SignatureMethod.sign(message, accessor);

  $.get(
    OAuth.addToURL(message.action, message.parameters),
    $.proxy(function(data) {
      var params = this.parseToken(data);
      var token = params["oauth_token"];
      var secret = params["oauth_token_secret"];

      // リクエストトークンを取得した後に認可を促すURLを開く

      message.action = "https://api.twitter.com/oauth/authorize";
      message.parameters["oauth_token"] = token;

      accessor["oauth_token_secret"] = secret;

      OAuth.setTimestampAndNonce(message);
      OAuth.SignatureMethod.sign(message, accessor);

      this.request_token = token;
      this.request_token_secret = secret;

      window.open(OAuth.addToURL(message.action, message.parameters));
    }, this)
  );
};

Twitter.prototype.sign = function(pin) {
  // アクセストークンを取得する

  var message = {
    "method": "GET",
    "action": "https://api.twitter.com/oauth/access_token",
    "parameters": {
      "oauth_consumer_key": CONSUMER_KEY,
      "oauth_signature_method": OAUTH_SIGNATURE_METHOD,
      "oauth_token": this.request_token,
      "oauth_verifier": pin
    }
  };

  var accessor = {
    "consumerSecret": CONSUMER_SECRET,
    "tokenSecret": this.request_token_secret
  };

  OAuth.setTimestampAndNonce(message);
  OAuth.SignatureMethod.sign(message, accessor);

  $.get(
    OAuth.addToURL(message.action, message.parameters),
    $.proxy(function(data) {
      var params = this.parseToken(data);

      // 取得したアクセストークン等を保管する

      this.access_token = params["oauth_token"];
      this.access_token_secret = params["oauth_token_secret"];
      this.user_id = params["user_id"];
      this.save();
    }, this)
  );
};

Twitter.prototype.save = function() {
  // 内部に保管されたトークン等をローカルストレージに保管しておく

  localStorage.setItem("access_token", this.access_token);
  localStorage.setItem("access_token_secret", this.access_token_secret);
  localStorage.setItem("user_id", this.user_id);
};

Twitter.prototype.isAuthenticated = function() {
  if (this.access_token !== null && this.access_token_secret !== null) {
    if (/^d+$/.test(this.user_id)) {
      return true;
    }
  }

  return false;
};

Twitter.prototype.fetchTimelines = function(cb) {
  // Home Timelineを取得するAPIからレスポンスを取得する

  var message = {
    "method": "GET",
    "action": "https://api.twitter.com/1/statuses/home_timeline.json",
    "parameters": {
      "oauth_consumer_key": CONSUMER_KEY,
      "oauth_signature_method": OAUTH_SIGNATURE_METHOD,
      "oauth_token": this.access_token
    }
  };

  var accessor = {
    "consumerSecret": CONSUMER_SECRET,
    "tokenSecret": this.access_token_secret
  };

  OAuth.setTimestampAndNonce(message);
  OAuth.SignatureMethod.sign(message, accessor);

  $.getJSON(OAuth.addToURL(message.action, message.parameters), function(data) {
    cb(data);
  });
};

チェック系をまったくしてないですけど、ざっくり書くとこんな感じで。まぁ認証してアクセストークンを取得して、そこからホームタイムラインを取得するAPIにリクエストを投げるという処理

んじゃこれからChrome Extensionの開発をする

manifest.json

{
  "name": "test",
  "version": "0.1",
  "content_scripts": [
    {
      "matches": ["https://api.twitter.com/oauth/authorize"],
      "js": ["content_script.js"],
      "run_at": "document_end"
    }
  ],
  "browser_action": {
    "default_title": "test",
    "default_icon": "icon.png",
    "default_popup": "popup.html"
  },
  "background": {
    "scripts":  ["jquery.js", "sha1.js", "oauth.js", "twitter.js", "background.js"]
  },
  "permissions": ["https://api.twitter.com/"]
}

今回はContent Scriptを使ってPINコードを自動でコピペして処理するが、その処理自体は主にBackground Pageに促すようにする。でBrowser Action側も主な処理は同様にBackground Pageを介して行う(getBackgroundPageを使うだけ)

background.js

var api = new Twitter();

chrome.extension.onRequest.addListener(function(req, sender) {
  // Content Scriptから来たPINコードを処理する
  api.sign(req.verifier);
});

function getTwitterAPI() {
  return api;
}

popup.html

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <link rel="stylesheet" type="text/css" href="popup.css" />
    <script type="text/javascript" src="jquery.js"></script>
  </head>
  <body>
    <div id="content">
      <div id="twitter-login">
        <a href="javascript:void(0)" onclick="login()">Twitterにログイン</a>
      </div>
    </div>
    <script type="text/javascript" src="popup.js"></script>
  </body>
</html>

popup.js

// Background Pageを取得する
var bgPage = chrome.extension.getBackgroundPage();

// Background Pageで初期化されているTwitter APIなクラスを取得する
var twitter = bgPage.getTwitterAPI();

function login() {
  twitter.login();
}

(function(undefined) {
  if (twitter.isAuthenticated()) {
    // 認証済みならログインなボタンなエレメントを消す
    $("#twitter-login").remove();

    var contentRoot = $("#content");

    twitter.fetchTimelines(function(tweets) {
      tweets.forEach(function(tweet) {
        contentRoot.append(
          $("<div>").attr("class", "tweet").append(
            $("<span>").append(
              $("<a>").attr(
                "href",
                "http://twitter.com/" + tweet.user.screen_name
              ).text("@" + tweet.user.name)
            ),
            $("<div>").text(tweet.text)
          )
        );
      });
    });
  }
})();

content_script.js

var pin = document.querySelector("div#oauth_pin > p > kbd > code");

if ((pin !== undefined && pin !== null) && /^d{7}$/.test(pin.innerText)) {
  // Background PageでonRequestさせて処理させる
  chrome.extension.sendRequest({ "verifier": pin.innerText });
}

という感じ。動かすと認証してない時にブラウザアクションをクリックすると

てな感じで認証後に行うと

というようにホームタイムラインが表示される。んまぁそんな感じでTwitter APIをアクセストークンを取得して行う場合は大体こんな感じかなと

追記

https://github.com/kinjouj/chrome-extension-twitter-oauth-demo の方にてチェックアウト出来るようにしました(但し、oauth.js・sha1.js・jquery.js等は未混入)

又、主に記事は修正せずにgithub側の方へ修正を行いますのであしからず

"manifest_version: 2"とContent-Security-Policy Chrome Extension開発を勉強してみる (12) - コンテキストメニューでブックマークを高階層で表示する -