Titanium MobileでTwitterOAuth時のエラーと解決方法
最近、Titanium Mobileにはまってます。とりあえず下記記事を参考にTwitterクライアントを開発中です。
連載:Titanium Mobileで作る! iPhone/Androidアプリ|gihyo.jp … 技術評論社
OAuth認証で結構四苦八苦したので、備忘録&情報シェアのために書きなぐります。
Parseエラー
4回目の記事でOAuthの実装が書かれているんだが、いざTwitterのログイン画面がポップアップ(Ti.UI.webview)で表示されると5秒くらいで落ちてしまう。。その時に吐き出されるエラーが下記。
[ERROR] Error Domain=com.google.GDataXML Code=-1 "The operation couldn’t be completed. (com.google.GDataXML error -1.)". in -[TiDOMDocumentProxy parseString:] (TiDOMDocumentProxy.m:50)
[ERROR] The application has crashed with an unhandled exception. Stack trace:
0 CoreFoundation 0x0232b58c __exceptionPreprocess + 156
1 libobjc.A.dylib 0x0247f313 objc_exception_throw + 44
2 ShizuTwi 0x0009c2b4 -[TiProxy throwException:subreason:location:] + 478
3 ShizuTwi 0x0010119c -[TiDOMDocumentProxy parseString:] + 572
~ 以下略 ~
oauth_adapter.js
4回目の記事では、oauth_adapter.jsを使って実装している。
oauth-adapter -
OAuth Adapter for Appcelerator Titanium - Google Project Hosting
oauth_adapter.jsでは、ポップアップでOAuthの認証画面を表示して、ログイン後に表示されるPINコードを読み込んでそれを元にAccessTokenを取得してくれる。
どうやってPINコードを読み込んでいるか?
WebViewが読み込まれると、oauth_adapter.jsのauthorizeUICallbackという関数が呼ばれる。
authorizeUICallbackでは、
- 表示されているログインページのソースをTi.XML.parseStringでパースしてDOMに変換
- getElementsByTagName('div')でdiv要素を全て取り出す
- idが「oauth_pin」であるdiv要素を探し、その要素の中身をPINとする。
// looks for the PIN everytime the user clicks on the WebView to authorize the APP // currently works with TWITTER var authorizeUICallback = function(e) { Ti.API.debug('authorizeUILoaded'); // 1. 表示されているログインページのソースをTi.XML.parseStringでパースしてDOMに変換 var xmlDocument = Ti.XML.parseString(e.source.html); // 2. getElementsByTagName('div')でdiv要素を全て取り出す var nodeList = xmlDocument.getElementsByTagName('div'); for (var i = 0; i < nodeList.length; i++) { var node = nodeList.item(i); var id = node.attributes.getNamedItem('id'); // 3. idが「oauth_pin」であるdiv要素を探し、その要素の中身をPINとする。 if (id && id.nodeValue == 'oauth_pin') { pin = node.text; if (receivePinCallback) setTimeout(receivePinCallback, 100); id = null; node = null; destroyAuthorizeUI(); break; } } nodeList = null; xmlDocument = null; };
原因
そのエラーが発生する前に下記のようなログが大量に吐き出される。
Entity: line 18: parser error : Opening and ending tag mismatch: link line 7 and head
^
Entity: line 44: parser error : Opening and ending tag mismatch: img line 42 and h3
^
DOMパースでエラーが発生しているみたい。。
解決
エラーで検索したりしたけども結局いい解決方法が見つからず、しょうがないのでoauth_adapter.jsを修正しました。修正内容としては、DOMにパースするのではなくevalJSを使ってPINを取得するように変更しました。
evalJSとは
evalJSとは、Ti.UI.webviewの関数で、webViewで表示されているページでJavascriptを実行しその結果を文字列で返す関数です。例えば、下記のようにすると
の中身をログに出力します。Ti.API.debug(webview.evalJS('window.document.body.innerHTML'));
querySelectorでPINを取得
TwitterのOAuthの画面で、PINが表示される部分の構造は下記のようになっています。
<div id="oauth_pin"> <p> <span id="code-desc">Next, return to <アプリケーション名> and enter this PIN to complete the authorization process:</span> <kbd aria-labelledby="code-desc"><code><ここにPINコードが表示される></code></kbd> </p> </div>
なので、e.sourceが表示されているログインページのWebViewだとすると、
var res = e.source.evalJS('window.document.querySelector(\'kbd[aria-labelledby="code-desc"] > code\').innerHTML');
↑でPINコードを取得することができる。
authorizeUICallbackを修正
下記のように修正しましたー。
var authorizeUICallback = function(e) { Ti.API.debug('authorizeUILoaded'); if (!e || !e.source) { return; } var res = e.source.evalJS('window.document.querySelector(\'kbd[aria-labelledby="code-desc"] > code\').innerHTML'); if (res) { pin = res; if (receivePinCallback) setTimeout(receivePinCallback, 100); destroyAuthorizeUI(); } };