JavaScriptでTwitterのOAuthを取り付けて通信する方法
以下の記事とTwicliのソースをコピペして作成しました
おれにこんな難しいことができるはずがない
TwitterクライアントのOAuth対応(Javascript編) | tomatomax.net
NeoCat/twicli · GitHub
JavaScriptでクロスドメイン通信したい
方法としては2つある
- XMLHttpRequest($.ajaxなど)
- JSONP
結論としては両方使いました。Firefoxの拡張ならGreeseMonkeyでGM_xmlhttpRquestがあるし、ChromeやOperaの拡張だとどこかの設定でOrigin PolicyでAllowすればXMLHttpRquestして返り値を取ってくることができる。でも今回は生でやりとりするから
という形で落ち着きました。*1
JS(jQuery)クロスドメイン通信に関しての質問です。GMや拡張ではない環境(生HTML)でクロスドメイン通信しようとするときに動的にiframeを生成してそこから通信するとかなにかありますが(Twicliを参考にしてます)、そもそもなぜiframeなのですか?
@xxxxx
基本的にはクロスドメイン通信は相手側の許可(というか特別な対応)がないとできません。
ですよね。だからOriginをサイト側で設定したり拡張の設定でAllowする必要がある。でも、じゃあなんでiframeを動的に作ってそこからポストすればSame Originにひっかからないんですか?
@yyyyyy
Same Originにはちゃんと引っかかっていて、レスポンスは受け取れません
Twitterについて、クロスドメインで結果を受け取るにはJSONPしかないですが、JSONPはGETしか送れないので、POSTしか受け取らないAPIにはアクセスできません。おそらくそこで引っかかっていると思います。
結局のところ、TwitterにJavaScriptから直接投げるのではなく、自前のサーバーを経由させるのがスタンダードですね。
OAuthをとりつける
手順はよくある通り。厳密には間違えているかもしれない。
- Consumer Key と Consumer Secretを発行(アプリ)
- Consumer Key と Consumer SecretをつかってRequst Tokenを発行するURLに問いかけ、Request Tokenと Request Token Secret を取得
- Twitterで認証
- Callbackで戻ってくるときにVerifierがクエリについてくるので、それを保存(Callbackを指定しない場合はPINが返ってくる)
- Consumer Key と Consume Secret と Verifier を使って Access Token と Access Token Secretを取得
- TwitterにはAccess Tokenやらいままで取得した値をつかって署名してリクエストを送る
各種Tokenを取得する
これがコピペプログラミングの真髄や(ドヤァ
htmlはてきとうにこんな感じにしておく
<!DOCTYPE HTML> <html lang="ja"> <head> <meta charset="UTF-8"> <title>skz3</title> <script type="text/javascript" src="./js/oauth.js"></script> <script type="text/javascript" src="./js/sha1.js"></script> <script type="text/javascript" src="./js/skz3_oauth.js"></script> <link rel="stylesheet" href="./css/skz3.css" /> </head> <body> <input type="button" onclick="TwitterOAuth.prototype.getRequestToken()" value="request_tokenを取得する"/> <form method="GET" action="https://api.twitter.com/oauth/authorize" onsubmit="return TwitterOAuth.prototype.setRequestToken(this)"> <input type="text" name="oauth_token"/> <input type="submit" value="送信っ!"/> </form> </body> </html>
Request Tokenを取得するボタンを押すと、window.openしてレスポンスが返ってくるので、コピペしてもらう(ここでクロスドメイン通信ができるなら自動的に返り値をセットすることなぞ簡単だが、それができないからこうしている)。ちなみにAccess Tokenを取得するときもほぼ全く同じHTMLなので割愛します。
JavaScriptはこんな感じ。CoffeeScriptだけど。あらかじめoauth - API needz authorized? - Google Project HostingのSource→JavaScriptから oauth.js と sha1.js を読み込ませておきます
class TwitterOAuth getRequestToken: -> accessor = consumerSecret: "YOUR CONSUMER SECRET" tokenSecret: '' message = method: "GET" action: "https://api.twitter.com/oauth/request_token" parameters: oauth_signature_method: "HMAC-SHA1" oauth_consumer_key: "YOUR CONSUMER KEY" OAuth.setTimestampAndNonce(message) OAuth.SignatureMethod.sign(message, accessor) target = OAuth.addToURL(message.action, message.parameters) window.open(target) setRequestToken: (tkn) -> key = tkn.oauth_token.value //正規表現でクエリをとってきてlocalStorageに保存する if key.match(/^oauth_token=([^&]+)&oauth_token_secret=([^&]+)/) localStorage.setItem("request_token", RegExp.$1) localStorage.setItem("request_token_secret", RegExp.$2) tkn.oauth_token.value = RegExp.$1 return true setVerifier: (href) -> //Callbackで呼ばれるURLが自動的に発火するScript //これも正規表現でクエリをとってきたらAccess Token取得ページヘリダイレクト if href.match(/oauth_verifier=([^&]+)/) localStorage.setItem("verifier", RegExp.$1) location.href = "./access_token.html" getAccessToken: -> accessor = consumerSecret: "YOUR CONSUMER SECRET" tokenSecret: localStorage.getItem("request_token_secret") # Request Token Secret message = method: "GET" action: "https://api.twitter.com/oauth/access_token" parameters: oauth_signature_method: "HMAC-SHA1" oauth_consumer_key: "YOUR CONSUMER KEY" oauth_token: localStorage.getItem("request_token") # Request Token oauth_verifier: localStorage.getItem("verifier") OAuth.setTimestampAndNonce(message) OAuth.SignatureMethod.sign(message, accessor) target = OAuth.addToURL(message.action, message.parameters) window.open(target) setAccessToken: (form) -> key = form.access_token.value if (key.match(/^oauth_token=([^&]+)&oauth_token_secret=([^&]+)/)) localStorage.setItem("access_token", RegExp.$1) localStorage.setItem("access_token_secret", RegExp.$2) if (key.match(/screen_name=([^&]+)/)) localStorage.setItem("access_user", RegExp.$1)
これで必要なAccess Token等を取得するところまではできた。次は取得したTokenを使ってクエリに含めてリクエストを送る
リクエストを作成する
こんな感じ
class TwitterBase @TWITTER_ROOT = "https://twitter.com/#!/" @TWITTER_API = "https://api.twitter.com/1/" setOAuth: (method, api, params)-> accessor = consumerSecret: "YOUR CONSUMER SECRET" tokenSecret: localStorage.access_token_secret message = method: method action: api parameters: oauth_consumer_key: "YOUR CONSUMER KEY" oauth_token: localStorage.access_token oauth_signature_method: "HMAC-SHA1" #suppress_response_codes: "True" //必要なパラメータはここで構築してSignする必要がある for key of params message.parameters[key] = params[key] OAuth.setTimestampAndNonce(message) OAuth.SignatureMethod.sign(message, accessor) authed_url= OAuth.addToURL(message.action, message.parameters) return authed_url //動的にスクリプトを生成してJSONPでコールバックする loadJSON: (url) -> //jQuery風じゃなかったら別にこれでもいい #script = document.createElement("script") #script.src = url #script.type = "text/javascript" #script.charset = "UTF-8" #document.body.appendChild(script) //jQuery方言風に script = $("<script>") script.attr('src', url) //うまくいかない #script.attr('type', "application/json") //普段はこれでいいけど、今回はだめ #script.attr('type', "text/javascript") //これだよ!!!! script.attr('type', "application/javascript") script.attr('charset', "UTF-8") $(document.body).append(script)
ここで重要なのは、OAuthで認証するわけじゃなければJSONPのmimetypeは "text/javascript" でもいいけど、Twitterの場合は "application/javascript"でなくてはならない!!!!!!!!これで半日くらいハマった。どうも401のAuthで落ちるなぁと。レスポンスヘッダみて「へェー知らないパラメータだなァー」と思ってたらこれだよ!!!!
参考::JavaScript関係の媒体型
参考::ちょっとしたメモ - スクリプトのMIMEタイプがRFCとなって公式登録へ
参考::nagenegiのブログ : JavaScriptの正しいscriptタグ
参考::JSONとJSONPのContent-typeに書くMIMEタイプ - kanonjiの日記
実際にリクエストするときはこんな感じでつかう
class API extends TwitterBase //POSTで投げる場合は返り値が帰ってこないけどリクエストは通るので$.ajax updateStatus: (text, in_reply_to_status_id)-> params = status: text in_reply_to_status_id: in_reply_to_status_id url = TwitterBase::setOAuth("POST", "#{TwitterBase.TWITTER_API}statuses/update.json", params) $.ajax type: "POST" url: url success: -> pass error: (XMLHttpRequest, textStatus, errorThrown) -> console.log textStatus //参照系のGETで取得する場合はJSONPでとってきてCallbackする getHomeTimeline: -> params = include_entities: "True" callback: "callback" count: "50" url = TwitterBase::setOAuth("GET", "#{TwitterBase.TWITTER_API}statuses/home_timeline.json", params) TwitterBase::loadJSON(url) //Callbackのサンプル。とりあえずdumpする。実際はこれを元に動的にページを構築する callback = (json) -> for arg in json console.log arg
JSONPで受け取ったリクエストを動的に組み立てるサンプルとしてはだいたい前やりました。これはOAuthしないバージョンです。CoffeeScriptじゃなくてJavaScriptです。もっといえば一枚のHTMLにHTML+CSS+JavaScript詰め込んでます。すごく便利だよ。これを誰にでも使えるようにするのが現在進行形のスカンディナヴィア半島3プロジェクトなのだ。
以上
昔は難しすぎて挫折してたけど、まがりなりにもJavaScript生でOAuth突破できたよ!おめでとう!まあ、普通はGreeseMonekyしたりChromeやOpera拡張したり、各種言語のライブラリつかって楽するのが普通だけどな!!!!
追記
しかしOperaではPOSTが投げれないのであった
"No Transport"
これを回避するためにjQueryでおまじないを唱えた
参考::PhoneGapアプリでjQuery(1.5以上)のクロスサイトなajax通信を有効にするには|開発部 in ICtriumphs
参考::How to use jquery queries: ajax, get ? - Opera Extensions Development Discussions - Opera Community
$.support.cors = true
しかし今度は
"Security violation"
無理ゲー……。Opera使いとしてはOperaで使えないとかなり困るので、これはどうにかしたいが、探しても無理ゲーなのだが……
さらに追記
Operaはiframe使うことにした
Operaの場合XHR2対応してないからiframeでクロスドメインでPOSTする - atas