Xamarin Parse SDKでTwitterログインを頑張ってみた


Xamarin Advent Calendar、[2日目](http://qiita.com/koji_yusa/items/a6878bef10577ee744b5 "XamarinでParse SDKを利用する")で今格闘してるParse SDKが出てきたので嬉しくなってもう1記事書いてみました。
Parse SDKが採り上げられたのは嬉しいのですが、「現状Xamarin用にはParse Pushが実装されていないそう」あたりにショックを受けております。
今回採り上げるTwitterログインもそうですが、iOS、Android両方に対応している機能がXamarin SDKで対応してないと思わないやん…。
どうも、Xamarin SDKは、iOS/Android SDKのラッパではなく、元々iOS/Androidに比べて対応度合いの遅れている.NET SDKの、さらにそのうちXamatin.iOS/Androidでも動く部分だけで提供されてる感じです…。

それでは本題…の前にちょっとお願い。

### 筆者はc#始めて半年のド素人です

言語仕様完璧に覚えてから作ろうというスタンスより、とりあえずなんか触ってみて、問題は知識が付いてからリファクタすればいいというスタンスでやってますので、本題以外のところはメチャクチャな可能性があります。
特にTaskだのasync/awaitだのの辺りは全く自信がありませんので、暖かく突っ込み倒していただければ幸いです。

では改めて本題。

## Xamarin ParseはTwitterログインに対応していない？

Parseを使って何をしたい、という事が決まっているわけではないのですが、何となくこんな事できればなあ、というのはたくさんあるので、とりあえず使い方覚えようと触り始めてみました。
[.NET SDKのドキュメント](https://parse.com/docs/dotnet_guide "Parse SDK .NET Guide")はこちらなのですが、触り始めて違和感。

* いくら探しても、Twitter UsersのAPIがないようなのですが、なんで？iOS、Androidにはあるのに…。  
単純な各OS SDKのラッパじゃないの？
* Facebook UsersのAPI、ドキュメントみればFacebookのAppKeyとConsumerSecret、ブラウザオブジェクト渡してやればログイン画面表示まで含めてやってくれる感じで書かれてるのに、そんなメソッドIDEが見つけてくれないぞ？  
既に認証済みのFacebook TokenをParseに渡すインタフェースしかない…。

いくら調べてもよく判らなかったので、[本家に質問](https://parse.com/questions/net-sdk-does-not-contain-parsetwitterutils)。

「おたくの.NET SDK、Twitterに対応してはりますの？」
「知らんがな他所のんつこてんか」

がーーーーーん。
Twitter/Facebook双方のソーシャルログインができると思ったからParse試し始めたのに…。
先にも書きましたが、どうもiOS/AndroidネイティブSDKにラッパしているのではなく、.NET版のAPIのうちXamarin.iOS/Androidでも動くところを提供しているような感じみたいです。
なので多分、Facebook認証のログイン画面提供I/Fも、Windowsのブラウザコンポーネントに依存してるので提供されてないんだろうな…。

## 認証はXamarin.Auth、ParseのUserオブジェクト連携はREST APIを使う事にした

しかし対応無理というなら有りモノでなんとかするしかないので、章題の通り、

* ログイン画面表示〜認証トークン取得までは[Xamarin.Auth](https://components.xamarin.com/view/xamarin.auth)を使う
* 認証トークンとParseユーザとの連携は[REST API](https://parse.com/docs/rest)を使って行う
* 全部RESTでやると大変そうなので、提供されていてできる機能についてはXamarin SDKのインタフェースで使いたいので、RESTのセッションからSDKのParseUserオブジェクト作成

という形で対応する事にしました。

1記事としてあまり長くなるのもアレなので、Xamarin.Authについてはまた別記事で。
ここでは、Xamarin.Authを使って認証情報は取れたものとして、Parseとの連携以降を紹介いたします。

### RESTを利用してTwitterとParseを紐付け後、ParseUserオブジェクト生成

RESTを叩くためのAPIは、Component Storeでも提供されている[RestSharp](https://components.xamarin.com/view/restsharp)を使う事にしました。
いきなりですが、実際のサンプルコードを示して説明します。

```csharp:Sample
using Parse;

public class RestParseUser
{
    //RESTクライアント    
    RestSharp.RestClient rest;

    public RestParseUser ()
    {
        //各種クライアント初期化
        ParseClient.Initialize(PARSE_APP_ID, PARSE_APP_KEY);
        rest = new RestSharp.RestClient ("https://api.parse.com");
    }

    //便宜上、認証結果の情報は全部authInfoに入ってるものとします
    public async ParseUser ParseLoginOrCreate(Dictionary<string,string> authInfo) 
    {
        //RESTリクエスト
        var req = new RestSharp.RestRequest ("1/users", RestSharp.Method.POST);
        req.AddHeader ("X-Parse-Application-Id", PARSE_APP_ID);
        req.AddHeader ("X-Parse-REST-API-Key", PARSE_REST_KEY);
        req.AddHeader ("Content-Type", "application/json");

        //リクエスト本体
        var payload = "{ \"authData\": { \"twitter\": { ";
        payload += "\"id\": \"" + authInfo["user_id"] + "\", ";
        payload += "\"screen_name\": \"" + authInfo["screen_name"] + "\", ";
        payload += "\"consumer_key\": \"" + authInfo["oauth_consumer_key"] + "\", ";
        payload += "\"consumer_secret\": \"" + authInfo["oauth_consumer_secret"] + "\", ";
        payload += "\"auth_token\": \"" + authInfo["oauth_token"] + "\", ";
        payload += "\"auth_token_secret\": \"" + authInfo["oauth_token_secret"] + "\" ";
        payload += "} } }";

        req.AddParameter("application/json", payload, RestSharp.ParameterType.RequestBody);

        //RESTログイン実行、レスポンス受け取り（ここで既に連携していれば存在しているParseUser情報、
        //    連携していなければ新しいParseUser情報が入手出来る）
        RestSharp.IRestResponse res = null;
        var result = await Task<JContainer>.Run(()=>{
            res = rest.Execute(req);
            var content = res.Content;
            return JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JContainer>(content);
        });
        //取得できたParseUser情報から、オブジェクトIDとセッショントークンを取得
        var sessinToken = (String)result["sessionToken"];
        var objectId = (String)result["objectId"];

        //作成された場合、デフォルトのユーザ名はむちゃくちゃなハッシュ名
        //ユーザがすぐ変更しなかった場合、これが残ってしまうので、Twitterのスクリーン名にでも変えてやる
        if (res.StatusCode == System.Net.HttpStatusCode.Created)
        {
            var req = new RestSharp.RestRequest ("1/users/" + objectId, RestSharp.Method.PUT);
            req.AddHeader ("X-Parse-Application-Id", PARSE_APP_ID);
            req.AddHeader ("X-Parse-REST-API-Key", PARSE_REST_KEY);
            req.AddHeader ("X-Parse-Session-Token", sessionToken);
            req.AddHeader ("Content-Type", "application/json");

            req.AddParameter("application/json", "{ \"username\": \"" + authInfo["screen_name"] + "\" }", RestSharp.ParameterType.RequestBody);
            result = await Task<JContainer>.Run(() => {
                res= rest.Execute(req);
                var content = res.Content;
                return JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JContainer>(content);
            });
        }

        //RESTから取得したセッションキーから、Xamarin SDK版ParseUserのオブジェクトを作成する
        await ParseUser.BecomeAsync (sessionToken);
            
        //これでここ以降、以下のstaticメソッドでログイン中のParseUserアカウントが取得できます
        //永続化も勝手にしてくれるようですが、どこに保存されるのか、復帰の際オフラインだったらどうなるのか、等までは未調査
        return ParseUser.CurrentUser;
    }
}
```

これで、XamarinでもTwitter連携したParseUserを作成する事ができます。
後は、XamarinSDK版のParseUserオブジェクトで好き勝手にメソッド叩いてもらえば…。

後いくつか、Tips的なもの

### Facebookを使う場合

Facebookを使う場合、ParseFacebookUtilsクラスが用意されているのでそっち使えば無問題。
ただし先述の通り、ブラウザログイン画面生成機能等はXamarin版では機能していないので、認証はXamarin.Auth等でやって、認証トークンだけ受け渡す形にしてやる必要があります。
それだったらむしろRESTで統一した方が、初ログイン時のユーザ名可読化修正処理等もあるので、メリット大きいかなと思い私はFacebookでもRESTログイン使ってます。
その場合は、やはり認証情報はauthInfoで引っ張ってきているとして、

```csharp:Facebookログイン
    //リクエスト本体
    var payload = "{ \"authData\": { \"facebook\": { ";
    payload += "\"id\": \"" + authInfo["user_id"] + "\", ";
    payload += "\"access_token\": \"" + authInfo["access_token"] + "\", " +
    payload += "\"expiration_date\": \"" + authInfo["expire"] + "\", " +
    payload += "} } }";
```

こんな感じになります。

### ParseUserオブジェクト側からParseセッショントークンを取る場合

RESTでログイン等行いセッショントークンを取るのは簡単なのですが、逆にXamarin版のParseUserオブジェクトから、実現されていない機能だけをRESTで叩くためにセッショントークンを取りたい場合、ParseUserオブジェクトのセッショントークン属性はprivateなので困ってしまいます。

そんな時は、[ここ](http://stackoverflow.com/questions/5031288/i-need-to-access-a-non-public-member-highlighted-item-of-a-combo-box)で公開されている、PropertyHelperクラス辺りを使って、

```csharp
    var sessionToken = PropertyHelper.GetPrivatePropertyValue<string> (ParseUser.CurrentUser, "SessionToken");
```

みたいにしてやればよいでしょう。

## さあ、Xamarin SDKにないpush機能実現のために、RESTと戯れる仕事に戻るんだ

というわけで、Xamarin SDKにないTwitter連携機能をREST経由で実現してバンジャーイバンジャーイと思ってたところが、一番興味があったところでもあるところのpush機能が提供されていないと聞いて、打ち拉がれている私がおります。
どう考えてもpushの受け取りにRESTインタフェースが一役担ってくれそうな感じが全くしませんが、とりあえずちょっと悪足掻いてみようと思います。

### 公開前追記

[こちらの記事](http://qiita.com/fakestarbaby/items/9b3a7b27f94170e191fb)によると、CoronaSDKからREST API経由でpush実現出来るようです。
あきらめずに試してみようと思います。