Titanium ＋Alloy＋ACSの構成でユーザーログインでFacebookアカウントを利用する

## はじめに

Titanium SDK 3.1以降、Titianium.Facebookモジュールではなく、Modules.Facebookに変更になってます。（詳しくは[公式サイト](http://docs.appcelerator.com/titanium/3.0/#!/api/Titanium.Facebook)を）

モジュールの導入時のセットアップで多少異なる点があるけど基本的な使い方としては大きな変更は無くその点での戸惑いはなかったのですが、Modules.Facebookを利用してACSへのユーザーログインでFacebookアカウントを利用する処理方法について情報がまとまっておらず、仕事で必要だったので調べたものをまとめることにしました。

## 想定する方

一応以下のような方を想定してまとめてみました。

- Titanium Mobileでサンプルコードを書いたことはある
- ACSという言葉は聞いたことがある
- OAuthの概念はなんとなく知ってる

やりたいことはシンプルなのですがそれに関連するベースの知識が色々問われてくるため関連情報を以下簡単にまとめておきます

### OAuthがわかりづらい人向けに

昔まとめたスライドがあってそのキャプチャー貼っておきます

![shot-2015-01-04-8.13.09.png](https://qiita-image-store.s3.amazonaws.com/0/10187/032b4665-a1a5-2fb6-14ad-d5f9237e4f31.png)


![shot-2015-01-04-8.11.10.png](https://qiita-image-store.s3.amazonaws.com/0/10187/9f9f6d61-e8de-22f9-1e67-5427e6f727cd.png)


### ACSについて補足情報

- ACSというのはTitanium Mobileの開発元であるAppceleratorが提供してるMBaaS（Mobile Backend as a Service)です。
    - MBaaSとしてはFacebookが買収したParseが最も有名なサービスです
    - [Parseの解説記事ですがQiitaのこちらのもの](http://qiita.com/kyproject/items/b9891aa7bb596b958d7e)を読むと、MBaaSが何なのか、どんなことが出来るのか概要がつかめると思います。
- AppceleratorCloudServicesの頭文字を取ってACSという言い方がよくされてます。ただ大分まえに名称がTitanium Cloud Servicesになってるので、TCSという言い方のほうが正しいのかな？？
- ACSの詳細は、英語ですが[公式サイト](http://www.appcelerator.com/cloud/)をご覧ください

## 開発環境

- Mac OS X 10.9.5
- Titanium SDK 3.3.0.GA
- XCode 5.1.1
- alloy 1.4.1

## 開発前に必要な準備

### プロジェクト設定

今回、ACSを利用するのでそれに合わせてプロジェクト設定を行います。

![shot-2015-01-04-8.33.02.png](https://qiita-image-store.s3.amazonaws.com/0/10187/e5d7bdfe-1d07-498a-66ae-f7bafdb65e7c.png)


![shot-2015-01-04-8.33.46.png](https://qiita-image-store.s3.amazonaws.com/0/10187/d73b86d9-1253-4431-bb0f-36f92d6db5db.png)

### TitaniumのFacebookモジュールの読み込み設定

プロジェクト設定完了後にtiapp.xmlを以下のように編集します


#### 編集前

```xml
    <modules>
        <module platform="commonjs">ti.cloud</module>
    </modules>
```

#### 編集後

```xml
    <modules>
        <module platform="commonjs">ti.cloud</module>
        <module platform="iphone">facebook</module>
    </modules>
```

### Alloy用の ACS Sync Adapter の配置


1. PROJECT_FOLDER/app/assets配下に、**alloy**フォルダを作成して、その中に**sync**フォルダを作成します
2. 上記フォルダ配下にacs.jsというファイルを作成します
3. [aaronksaunders さんが作ったacs.js](https://github.com/aaronksaunders/ci.alloy.acs.books/blob/master/app/assets/alloy/sync/acs.js)のソースコードを上記２．で作成したacs.jsにコピペします

### Facebook アプリID

これはTitaniumでの開発固有の話ではないのですが、Facebookと連携するアプリ開発をする場合にこの作業が必須になります。

#### [開発者ページ](https://developers.facebook.com/)に行きます

![shot-2015-01-04-8.38.32.png](https://qiita-image-store.s3.amazonaws.com/0/10187/6b5c4b9d-8781-d98a-fa24-bdcbb8f146cd.png)

#### 今回はiOS向けアプリなのでこちらを選択

![shot-2015-01-04-8.38.43.png](https://qiita-image-store.s3.amazonaws.com/0/10187/500ba0ec-761a-cebb-f523-5458f804eb61.png)

#### 右上のリンクをクリックします

![shot-2015-01-04-8.39.12.png](https://qiita-image-store.s3.amazonaws.com/0/10187/0b7b7333-4489-0783-8f3d-a4bdc311e129.png)

#### AppID登録に必要な内容を入力します

![shot-2015-01-04-8.39.39.png](https://qiita-image-store.s3.amazonaws.com/0/10187/b9e01e3c-6785-31f7-542d-3cc80466cdf0.png)

#### 登録時に必要なセキュリティチェックの文字を入力します

![shot-2015-01-04-8.39.47.png](https://qiita-image-store.s3.amazonaws.com/0/10187/179c3d0d-296d-29d2-b744-63d6e98a5da4.png)

#### 登録完了

画面キャプチャーでは消去してますがここにAppIDが数字で記載されてるのでそれを控えておきます

![shot-2015-01-04-8.40.28.png](https://qiita-image-store.s3.amazonaws.com/0/10187/90da4956-f4a6-c558-ed99-33c302d79b6f.png)



## ソースコードの概要

このページの最後にソースコード全体を記載しておきましたがAlloyのModelの処理とControllerの処理でちょっとハマり所があったのでその辺りちょっとだけ書いておきます

### Modelの処理

Cloud.SocialIntegrations.externalAccountLoginを利用できるアダプターを準備します。

ソースコード全体は後述しますがポイントになる所の処理を抜粋します

```javascript
  // 省略
  extendModel: function(Model) {
    _.extend(Model.prototype, {
      fbLogin: function(token, email, birthday){
        this.config.Cloud.SocialIntegrations.externalAccountLogin({
          type: 'facebook',
          token: token,
          email: email,         // (1)
          custom_fields: {
            birthday: birthday  // (2)
          }
        }, function (e) {
          // 省略
        });                
      }
    });
    return Model;
  }
```

1. Web上で見かけるサンプルコードでは見かけなかったのですが、こういう感じでemailプロパティに、設定したいメールアドレスの情報を指定してあげることで、SocialIntegrations.externalAccountLoginの情報に対してメールアドレスの設定を行うことが出来ます。
2. ACSのUserオブジェクトにはメールアドレスの設定項目が元々有るのですが、例えば誕生日のような項目は存在していません。その場合にはカスタムフィールドという仕組みを通じ独自の項目を設定することが出来ます。このような記述をしてあげることでカスタムフィールドに、birthdayという項目を設定して、任意の値を設定できます

### Controllerの処理

Facebookアカウントを利用した認可処理のサンプルコードはWeb上にいくつもあったのですが、

- Facebookアカウントでの**ユーザー認証**処理
- ユーザー認証が終わったあとに、このアプリがあなたのxxにアクセスしますがよろしいでしょうか？というよくみる**認可**のポップアップ処理

という処理の区分けをどこでどのようにしてるのかがわからずに大分苦労しました。

しかも、TitaniumのModules.Facebookの仕組みを通じて、login/logoutのイベントがあるようだったので、ログインという言葉の印象から、その段階でFacebookのアカウントの属性情報をすべて取得してるのかとおもったらそれはまた別処理になるようでこの辺りがかなりハマり所かなと個人的には思いました。


#### Facebookの情報にアクセスするために必要な各種設定を行う

```javascript
var fb = require('facebook');
fb.appid = Ti.App.Properties.getString('ti.facebook.appid') // (1);
fb.permissions = ['publish_stream','email','user_birthday']// (2);
fb.forceDialogAuth = false;
```

1. 今回のサンプルでは取得したFacebookのappidをtiapp.xml内にti.facebook.appidというプロパティ名で記述してます。
2. 今回作成するアプリから、Facebookアカウントの情報へのアクセスする時に、publish_streamだけだと、氏名程度しか取得できないため、permissionsの項目に**email**と**user_birthday**という項目を追加します


#### ログインイベント発火した時の処理を書く

```javascript
fb.addEventListener('login', function(e) {
  if (e.success) {
    fb.requestWithGraphPath('me',{},"GET",function(e) {
      // 省略
    );

  } else if (e.error) {
    Ti.API.info(e.error);
  } else if (e.cancelled) {
    Ti.API.info("Canceled");
  }
});
```

#### ログインボタン的なものを準備

iconというID属性を付与したButton要素を配置しておき、クリックイベントで、Facebookの**認可**処理を開始する処理を呼び出します

```javascript
$.icon.addEventListener('click', function(e) {
  fb.authorize();
});
```



### ソースコード全体

#### controllers/index.js

```javascript
var fb = require('facebook');
fb.appid = Ti.App.Properties.getString('ti.facebook.appid');
fb.permissions = ['publish_stream','email','user_birthday'];
fb.forceDialogAuth = false;
fb.addEventListener('login', function(e) {
  if (e.success) {
    fb.requestWithGraphPath('me',{},"GET",function(e) {
        if (e.success) {
          var obj = JSON.parse(e.result),
              moment = require('alloy/moment'),
              token, user, email, birthday;

          email = obj.email;
          birthday = moment(obj.birthday, "MM/DD/YYYY");
          token = fb.accessToken;
          Ti.API.info("Success: " +
                      "email" + email +
                      "birthday" + birthday +
                      "token" + token
                     );
          user = Alloy.createModel('social');
          user.fbLogin(token,email, birthday);
        }
      }
    );

  } else if (e.error) {
    Ti.API.info(e.error);
  } else if (e.cancelled) {
    Ti.API.info("Canceled");
  }
});

$.fbloginLabel.addEventListener('click', function(e) {
  fb.authorize();
});

$.index.open();


```

#### models/social.js

```javascript
exports.definition = {
  config: {
    columns: {
      active: "boolean"
    },
    adapter: {
      type: "acs",
      collection_name: "socialIntegrations"
    },
    settings: {
      object_name: "socialIntegrations",
      object_method: "socialIntegrations"
    }
  },
  extendModel: function(Model) {
    _.extend(Model.prototype, {
      fbLogin: function(token, email, birthday){
        this.config.Cloud.SocialIntegrations.externalAccountLogin({
          type: 'facebook',
          token: token,
          email: email,
          custom_fields: {
            birthday: birthday
          }
        }, function (e) {
          if (e.success) {
            var user = e.users[0];
            Ti.API.info('Success:\n' +
                  'id: ' + user.id + '\n' +
                  'first name: ' + user.first_name + '\n' +
                  'last name: ' + user.last_name);
          } else {
            Ti.API.info('Error:\n' +
                  ((e.error && e.message) || JSON.stringify(e)));
          }
        });        
      }
    });
    return Model;
  },
  extendCollection: function(Collection) {
    _.extend(Collection.prototype, {});
    return Collection;
  }
};
```

#### index.xml

```xml
<Alloy>
	<Window class="container">
    <Label id="fbloginLabel">Facebookでログイン</Label>
	</Window>
</Alloy>
```

#### index.tss

```css
".container": {
	  backgroundColor:"white"
},

"#fbloginLabel": {
    top:"40%",
    left:"30%",
    width:"60%",
    height:"10%"
}
"#icon" :{
    top:"40%",
    left:"5%",
    image: "facebook-icon.png"
}
```

#### tiapp.xml

tiapp.xmlの中にFacebookのappidを記載しておきます

```xml
<property name="ti.ui.defaultunit" type="string">dp</property>
<!-- ここから追加箇所 -->
<property name="ti.facebook.appid">xxxxxxxxxxx</property>
<!-- ここまで追加箇所 -->
```

## 終わりに

OAuthについての理解＆それをTitaniumのモジュールを使った場合にどのように実装するのかという所について何かの参考になればと思ってまとめてみました。

自分自身でOAuthの概念は理解してるつもりなのですが、文章にまとめて気づいたこととして、Module.Facebookの機能が自分の想像してるものと一部異なる印象があるためどうもこの辺りが要因になってなかなか理解が進まないのかなとふと思いました