ええやんブログ

ええやないかのええやんブログ

SafariでFacebookアプリが動かない

ニンニキニキニキ ニンニキニキニキニニンがeeyanaikaです。
いいですよねスチャダラパー

というわけで、今回はFacebookアプリがSafariで動かない現象についてと対応方法です。
SafariもIE同様、iframe上で表示される別ドメインのサイトはクッキーが有効になりません。
で、さらにP3Pのポリシー宣言をしてもSafariでは無効のままです。
なので、Facebookにログインした状態でアプリを開始しようとすると、二回目以降のアクセスではPHP SDKの getUser() の戻り値が必ず0になってしまいます。

SDKを調べてみると、base_facebook.phpにある $this->getSignedRequest() が認証情報を取得しているんですが、そのメソッド中の $_COOKIE[$this->getSignedRequestCookieName()] が、クッキーが無効になっているせいで、空になってしまうのが原因のようです。

<?php
  /**
   * Retrieve the signed request, either from a request parameter or,
   * if not present, from a cookie.
   *
   * @return string the signed request, if available, or null otherwise.
   */
  public function getSignedRequest() {
    if (!$this->signedRequest) {
      if (!empty($_REQUEST['signed_request'])) {
        $this->signedRequest = $this->parseSignedRequest(
          $_REQUEST['signed_request']);
      } else if (!empty($_COOKIE[$this->getSignedRequestCookieName()])) {
        $this->signedRequest = $this->parseSignedRequest(
          $_COOKIE[$this->getSignedRequestCookieName()]);
      }
    }
    return $this->signedRequest;
  }

んで、いろいろ検索してみると、やり方は2つくらいあるようで、ひとつはこんな感じで、別のiframeを用意し、ロード時にformをsubmitしてsession_startする手法です。
これは以前GoogleがSafariの設定を迂回していた件と同じ感じですかね。

これを試してみたのですが、うまくいきませんでした。すみません。

次にパラメータを引き渡していく手法です。
こちらは調べてもほとんどやり方が出てこなかったので、独自の方法になります。
もととなるソースはこちら

1. 初回アクセス時にキャッシュに保存

初回のアクセスではUser IDおよびsigned_requestは取得できることを確認したので、まずはそれをキャッシュに保存したいと思います。 User IDをキーにしてキャッシュにsigned_requestを保存する処理を、indexメソッドにて実装します。

<?php
    /**
     * 初期画面アクション
     */
    function index() {

        //----------------------------------------
        // データを取得する
        //----------------------------------------
        try {

            // 自分の情報を取得
            $this->me = $this->facebook->api('/me');

        } catch (FacebookApiException $e) {
            //
            // エラー処理
            //
        }

        //-----------------------------------------
        // キャッシュにデータを保存する
        //-----------------------------------------
        $cache_data = array();
        $cache_data["user_id"]        = $this->user_id;
        $cache_data["signed_request"] = $_REQUEST["signed_request"];

        $this->cache->save("eeyan_cache_key".$this->user_id, $cache_data, 3600);

        //-----------------------------------------
        // データを設定する
        //-----------------------------------------
        $view = array();
        $view["user_id"] = $this->user_id;   // 会員ID
        $view["me"] = $this->me;             // ユーザー情報

        // ビューを指定
        $this->load->view("eeyan/index", $view);

    }
2. User IDを持ちまわる

次はUser IDをページ遷移で持ちまわるようにします。
今回はGETで送るようにしますが、POSTでもかまいません。

<!doctype html>
<html xmlns:fb="http://www.facebook.com/2008/fbml">
<head>
<title>eeyan index</title>
</head>
<body>
    <h1>次のページへ</h1>
    <form action="/eeyan/process/0?user_id=<?php echo $user_id; ?>" id="frm" name="frm" method="post">
        <input type="submit" value="送信する">                               
    </form>
</body>
</html>
3. User IDを元にsigned_requestを設定

あとはコンストラクタで、GETで渡ってきたUser IDを元にキャッシュを取得し、キャッシュ中にあるsigned_requestを再設定してあげればOKです。
$_REQUEST["signed_request"] に設定すると、getUser() は正常にUser IDを返してくれるようになります。

<?php
    function __construct() {

        parent::__construct();

        // キャッシュドライバーの指定
        $this->load->driver('cache', array('adapter' => 'file'));

        $ua = $_SERVER['HTTP_USER_AGENT'];
        // IEのcookieを許可する
        if (strpos($ua, 'MSIE') !== FALSE) {
            header('p3p: CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"');
        }

        require_once APPPATH."third_party/Facebook/facebook.php";

        // Facebookクラスのインスタンス生成
        $this->facebook = new Facebook(array(
                "appId"  => アプリID,
                "secret" => アプリシークレットコード,
                "cookie" => true
        ));

        // Safari用に引き渡したuser_idを設定する
        if (isset($_GET["user_id"])) {
            $this->user_id = $_GET["user_id"];
        }

        // キャッシュからsigned_requestを取得する
        $cache_data = $this->cache->get("eeyan_cache_key".$this->user_id);

        if (isset($cache_data["user_id"])) {
            $_REQUEST["signed_request"] = $cache_data["signed_request"];
        }

        try {

            // 現在ログイン中のユーザー情報を取得する
            $this->user_id = $this->facebook->getUser();

        } catch (FacebookApiException $e) {
            //
            // エラー処理
            //
        }

        if (!$this->user_id) {

            // ログインURLを生成
            $url = $this->facebook->getLoginUrl(array(
                        "redirect_uri" => "http://apps.facebook.com/".アプリ名,
                        "scope"        => "publish_stream"
            ));

            // アプリ未登録ユーザーなら facebook の認証ページへ遷移
            echo "<script type='text/javascript'>top.location.href = '{$url}';</script>";
            exit;

        }

    }

あとはform_helperを拡張して、自動でUser IDを持ちまわるようにしてあげると、ページ数が多いアプリでもそれほど気にならずに実装できるかと思います。

SDKを使ってFacebookアプリを作ろうとすると結構大変ですね。ではでは。