2011年12月18日日曜日

Titanium Salesforce module(SalesForce Toolkit for Appcelerator)の使い方 (2)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
前回のサンプルアプリをちょっと時間が経ってから動かしてみると、

[ERROR] In the error handler looking for a 401, and have a 401

[ERROR] Handleing the 401 error...

とかエラーが出てしまい動きません…

force.comからOAuth2で取得したaccess tokenには有効期限があるのでrefresh tokenを使って再度取得する必要があるのですが…そこが動作していないようです。
force.comのOAuthについて詳しくはこちらで

エラーのコールバックも呼んでくれないので有効期限切れたかどうかも検出できず…

なんとかhackできないかと、こんな感じでモジュールの中身をダンプしてみます。

var FDC = require('com.salesforce');
for (var i in FDC.ForceOAuth) {
 Ti.API.debug(i + ':' + FDC.ForceOAuth[i]);
}

最終的にREST APIの呼び出しはここに来るようです。
[DEBUG] makeRestCall:function (path, callback, error, method, payload, retry) {var restUrl=Ti.Network.decodeURIComponent(fa.instanceUrl)+'/services/data'+path;var xhr=Ti.Network.createHTTPClient();xhr.onload=function(){Ti.API.info("REST Response: "+this.responseText);var data="";if(this.responseText){data=this.responseText;}

callback(data);};xhr.onerror=function(e){Ti.API.error("XHR, error handler..."+"\nDbDotCom.REST.OAuth.refreshToken: "+fa.refreshToken+"\nretry: "+retry+"\n e: "+e.error+"\nXHR status: "+this.status);if(!fa.refreshToken||retry){error(e.error);}else{Ti.API.error("In the error handler looking for a 401, and have a "+xhr.status);if(xhr.status===401){Ti.API.error("Handleing the 401 error...");exports.refreshAccessToken(function(oauthResponse){Ti.API.error("Refresh response... "+oauthResponse);fa.makeRestCall(path,callback,error,method,payload,true);},error);}else{Ti.API.error("Not a 401 error, re-throwing...");error(e);}}};if(fa.usePostBin===true){restUrl="http://www.postbin.org/135onm5";}

xhr.open(method||"GET",restUrl,true)

Ti.API.info("Rest url: "+restUrl);xhr.setRequestHeader("Authorization","OAuth "+Ti.Network.decodeURIComponent(fa.accessToken));xhr.setRequestHeader("Content-Type","application/json");xhr.send(payload);}

出てるログから、exports.refreshAccessToken()の呼び出しの中でエラーが起きてコールバックまで戻って来ないようです。

よく考えたら、OAuth2のrefresh tokenによるaccess token再取得時にはclient secretが必要なはずなのに、モジュールのパラメータなどでどこにもセットしていないのでrefreshできるわけないですね… 未実装なんでしょうか?
※追記:secret要らない仕様に変わってました
ダンプしたソースを参考に、こんな感じでhackしてみました。
※モジュール内で定義されてるobjectのプロパティは動的に書き換えられない?&スコープ的にアクセスできない変数があったので結構無理矢理

使い方は、requireした後にこのファイルをincludeしてパッチを当て、ForceOAuthの代わりにForceOAuth2を使うようにします。ForceOAuth2.openのパラメータにはclient idとclient secretを渡す様にします。

var FDC = require('com.salesforce');
Ti.include('fdc-patch.js');
FDC.ForceOAuth2.open('CLIENT_ID');

パッチしたポイントは2つ。refreshAccessTokenをclient secretを使用して動作する様にしたのと、REST APIのパスの固定部分に /data が含まれていたのを /apexrest が呼べる様に /services までとしたこと。

Winter '12でリリースされたApex RESTを使って公開したAPIのURLは、$instance_url/services/apexrest/... となるので、FDC.ForceOAuth2.makeRestCall('/apexrest/myapi', callback) の様な感じで使える様になります。

marketplaceのモジュールのページには、"This toolkit is maintained by the community and sponsored by salesforce.com. Salesforce.com does not officially support this product."とか書いてあるんですが、パッチとか提供したい場合どこに連絡すればいいんでしょうか… githubとかにソース上がってればforkするのに…

この記事はForce.com Advent Calendar 2011に参加しています。

6 件のコメント:

  1. 恐れ入ります。

    有益な情報をありがとうございます。
    私も同じモジュールを入れて動かしています。
    やはり時間が経つと認証でコケてしまいました。

    そこでpomuさんのパッチを当てたところ、
    ログイン画面にリダイレクトされる前に

    Script Error = Result of expression 'oauthJ' [null] is not an object. at fdc-patch.js (line 12).

    を吐くようになりました。
    パッチの当て方が正しければ、
    10行目のTi.App.Properties.getString("oauthData_preference");は
    nullにはならないという認識でよいのでしょうか...?
    恐らく本来ならばFDCで何かしらの値がセットされてこの処理に入るということですよね...

    返信削除
  2. "oauthData_preference"は認証時にモジュール内部で格納されるのですが、初回の認証前だとnullでエラーになってしまいますね…
    ちょっと修正しました。

    返信削除
  3. ちなみに、OAuth認証の情報をアプリから削除する方法が用意されていませんが、"oauthData_preference"の有無でOAuthのログイン画面に飛ばすかを判断している様なので、Ti.App.Properties.removeProperty("oauthData_preference");してやるとログイン画面が出る様に戻ります。

    返信削除
  4. なんとおお(; ・`д・´)
    さっそくのアップデート有り難うございます。
    さらに戻りの方法まで教えて頂いてしまって恐縮です。
    さっそく週明けに試してきます!

    返信削除
  5. 有益な情報をありがとうございます。
    pomuさんのパッチを当てたところ、

    [ERROR] XHR, error handler...
    DbDotCom.REST.OAuth.refreshToken: 5Aep861rEpScxnNE64xeqQ1jBsxOFB2rIxGXSMrNbE68PJhzSB0zvba.nZLWR8w94ol5KE07GwanA%3D%3D
    retry: undefined
    e: undefined
    XHR status: 400
    [ERROR] In the error handler looking for a 401, and have a 400
    [ERROR] Not a 401 error, re-throwing...

    エラーになりましたが、
    githubから見ると

    FDC.ForceOAuth2.makeRestCall('/data/v22.0/query/?...', function(e) {...});

    メソッドがあります。申し訳ございませんが、具体的にどうのように使えば良いのか
    教えられて頂きませんか?

    宜しくお願いします。

    返信削除
    返信
    1. access tokenの期限が切れた時にログにはそのエラー残ります。
      また、refresh時のsecretは要らない様に仕様が変わっていましたので、secret渡さなくても良くなってます。

      削除