Chrome for iOS で window.openerが取得できないので対策

SPAを作っていて、ユーザーの認証にTwitterのOAuthを利用しようとした場合、認証情報を得る為に、下記手順が必要になります。

  1. OAuth認証用のAPIを新規windowで開く
  2. Twitter<->認証APIの間で認証情報をやりとり
  3. クライアント(ブラウザ)に結果を返す

今回作っていたアプリケーションは状態毎のURLを持たないものだったため、window.open()で新規windowを開き、認証後、window.opener.callback()を呼びだす事でクライアント側にデータを返すよう試んだのですが…

(index.js)

window.callback = function(oauthInfo){
  
}
window.open('http://example.com/api/oauth');

(oauth.js)

var match,
  search = /([^&=]+)=?([^&]*)/g,
  decode = function (s) { return decodeURIComponent(s.replace('+', " ")); },
  query  = window.location.search.substring(1);

var params = {};
while (match = search.exec(query)){
  params[decode(match[1])] = decode(match[2]);
}

// 認証情報を親windowへ渡す
window.opener.callback(params);
widnow.close();

大体、こんな感じの実装で、iOS Safariなどメジャーなブラウザで動作していました。
が、Chrome for iOSでは、window.openerが存在しない事が発覚。。 https://code.google.com/p/chromium/issues/detail?id=136610

対策

SPAでも、URLと状態が紐付いているなら、わざわざwindow.open()で別windowを開かず、現在のwindowを使い、URLに認証情報を含める事で対応できそう。
が、今回は状態毎のURLを持たせたくなかったので、localStorageを使った小技を使いました。

(index.js)

window.open('http://example.com/api/oauth');
checkStorage();
function checkStorage(){
  if (localStorage.OAUTH){
    var authInfo = JSON.parse(localStorage.OAUTH);
    localStorage.removeItem('OAUTH');
  }else{
    setTimeout(checkStorage, 1000); // 毎秒チェック
  }
}

(oauth.js)

var match,
  search = /([^&=]+)=?([^&]*)/g,
  decode = function (s) { return decodeURIComponent(s.replace('+', " ")); },
  query  = window.location.search.substring(1);

var params = {};
while (match = search.exec(query)){
  params[decode(match[1])] = decode(match[2]);
}

// クエリストリングをlocalStorageへ
localStorage.OAUTH = JSON.stringify(params);
window.close();

認証開始後、毎秒localStorageをチェックする事で、認証完了を検知します。
cookieを使えばAPIサーバ側で値を埋め込めるのですが、少し遠回りをしてlocalStorageを使えば、ドメインが異なっていても動作するようになります。

どうにか対応できたものの、なんだかスッキリしない…。

SHOTA

I'm WEB developer

Tokyo, Japan http://senta.me/