OSDN Git Service

Version 0.6.157, add X.Net.Form & fix X.EventDispatcher.
[pettanr/clientJs.git] / 0.6.x / js / 06_net / 10_XOAuth2.js
index d8f3dd1..ab34837 100644 (file)
@@ -13,14 +13,33 @@ var X_NET_OAUTH2_detection      = new Function( 'w', 'try{return w.location.sear
  * <dt>X.Event.PROGRESS<dd>コードを window から受け取った、リフレッシュトークンの開始、コードの認可を header -> params に切替
  * </dl>
  * 
- * OAuth2 state
- * <dl>
- * <dt>0 : <dd>disconnected
- * <dt>1 : <dd>now authentication ...
- * <dt>2 : <dd>authorization_code
- * <dt>3 : <dd>refresh_token
- * <dt>4 : <dd>hasAccessToken
- * </dl>
+ * original :
+ *  oauth2.js , <opendata@oucs.ox.ac.uk>
+ * 
+ * @alias X.OAuth2
+ * @class OAuth2 サービスを定義し接続状況をモニタする。適宜にトークンのアップデートなどを行う
+ * @constructs OAuth2
+ * @extends {EventDispatcher}
+ * @example // OAuth2 サービスの定義
+oauth2 = X.OAuth2({
+       'clientID'          : 'xxxxxxxx.apps.googleusercontent.com',
+       'clientSecret'      : 'xxxxxxxx',
+       'authorizeEndpoint' : 'https://accounts.google.com/o/oauth2/auth',
+       'tokenEndpoint'     : 'https://accounts.google.com/o/oauth2/token',
+       'redirectURI'       : X.URL.cleanup( document.location.href ), // 専用の軽量ページを用意してもよいが、現在のアドレスでも可能
+       'scopes'            : [ 'https://www.googleapis.com/auth/blogger' ],
+       'authorizeWindowWidth'  : 500,
+       'authorizeWindowHeight' : 500
+}).listen( [ X.Event.NEED_AUTH, X.Event.CANCELED, X.Event.SUCCESS, X.Event.ERROR, X.Event.PROGRESS ], updateOAuth2State );
+
+// XHR 時に oauth2 を渡す
+X.Net( {
+       xhr      : 'https://www.googleapis.com/blogger/v3/users/self/blogs',
+       dataType : 'json',
+       auth     : oauth2,
+       test     : 'gadget' // http -> https:xProtocol なリクエストのため、google ガジェットを proxy に使用
+       } )
+       .listen( [ X.Event.SUCCESS, X.Event.ERROR, X.Event.PROGRESS ], updateOAuth2State );
  */
 X[ 'OAuth2' ] = X_EventDispatcher[ 'inherits' ](
                'X.OAuth2',
@@ -29,29 +48,51 @@ X[ 'OAuth2' ] = X_EventDispatcher[ 'inherits' ](
                /** @lends OAuth2.prototype */
                {
                        'Constructor' : function( obj ){
+                               var expires_at;
+                               
                                obj = X_Object_clone( obj );
                                
                                X_Pair_create( this, obj );
                                
-                               if( _getAccessToken( this ) ){
-                                       obj.oauth2State = 4;
-                                       this[ 'asyncDispatch' ]( X_EVENT_SUCCESS );
+                               obj.onAuthError   = X_NET_OAUTH2_onXHR401Error;
+                               obj.updateRequest = X_NET_OAUTH2_updateRequest;                         
+                               
+                               if( _getAccessToken( this ) && ( expires_at = _getAccessTokenExpiry( this ) ) ){
+                                       if( expires_at < X_Timer_now() + ( obj[ 'refreshMargin' ] || 300000 ) ){ // 寿命が5分を切った
+                                               this[ 'refreshToken' ]();
+                                       } else {
+                                               obj.oauth2State = 4;
+                                               this[ 'asyncDispatch' ]( X_EVENT_SUCCESS );                                             
+                                       };
                                } else {
                                        this[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
-                               }
-                               
-                               obj.onAuthError   = X_NET_OAUTH2_onXHR401Error;
-                               obj.updateRequest = X_NET_OAUTH2_updateRequest;
+                               };
                                
-                               // TODO kill の cancel
+                               // TODO canUse gadgetProxy
+                               this[ 'listen' ]( [ X_EVENT_KILL_INSTANCE, X_EVENT_SUCCESS, X_EVENT_ERROR, X_EVENT_NEED_AUTH ], X_NET_OAUTH2_handleEvent );
                        },
 
+                       /**
+                        * OAuth2 の状態。
+                        * <dl>
+                        * <dt>0 : <dd>未接続
+                        * <dt>1 : <dd>認可用 window がポップアップ中
+                        * <dt>2 : <dd>コードを認可中
+                        * <dt>3 : <dd>トークンのリフレッシュ中
+                        * <dt>4 : <dd>接続
+                        * </dl>
+                        * @return {number}
+                        */
                        'state' : function(){
                                return X_Pair_get( this ).oauth2State || 0;
                        },
                        
+                       /**
+                        * 認可用 window をポップアップする。ポップアップブロックが働かないように必ず pointer event 内で呼ぶこと。
+                        */
                        'requestAuth' : function(){
-                               // pointer event 内で呼ぶこと
+                               var url, w, h;
+                               // TODO pointer event 内か?チェック
                                // 二つ以上の popup を作らない
                                if( X_NET_OAUTH2_authorizationWindow ) return;
                                
@@ -59,8 +100,12 @@ X[ 'OAuth2' ] = X_EventDispatcher[ 'inherits' ](
                                
                                if( pair.net || pair.oauth2State ) return;
                                
+                               url = pair[ 'authorizeEndpoint' ];
+                               w   = pair[ 'authorizeWindowWidth' ]  || 500;
+                               h   = pair[ 'authorizeWindowHeight' ] || 500;
+                               
                                X_NET_OAUTH2_authorizationWindow = window.open(
-                                       pair[ 'authorizeEndpoint' ] + '?' + X_URL_objToParam(
+                                       url + ( ( url.indexOf( '?' ) !== -1 ) ? '&' : '?' ) + X_URL_objToParam(
                                                {
                                                        'response_type' : 'code',
                                                        'client_id'     : pair[ 'clientID' ],
@@ -68,10 +113,10 @@ X[ 'OAuth2' ] = X_EventDispatcher[ 'inherits' ](
                                                        'scope'         : ( pair[ 'scopes' ] || []).join(' ')
                                                }
                                        ), 'oauthauthorize',
-                                       'width=' + pair[ 'authorizeWindowWidth' ]
-                                       + ',height=' + pair[ 'authorizeWindowHeight' ]
-                                       + ',left=' + (screen.width  - pair[ 'authorizeWindowWidth'  ] ) / 2
-                                       + ',top='  + (screen.height - pair[ 'authorizeWindowHeight' ] ) / 2
+                                       'width=' + w
+                                       + ',height=' + h
+                                       + ',left=' + ( screen.width  - w ) / 2
+                                       + ',top='  + ( screen.height - h ) / 2
                                        + ',menubar=no,toolbar=no');
                                
                                X_NET_OAUTH2_authorizationTimerID = X_Timer_add( 333, 0, this, X_Net_OAuth2_detectAuthPopup );
@@ -81,6 +126,9 @@ X[ 'OAuth2' ] = X_EventDispatcher[ 'inherits' ](
                                this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, message : 'Start to auth.' } );
                        },
                        
+                       /**
+                        * 認可プロセスのキャンセル。ポップアップを閉じて認可用の通信は中断する。
+                        */
                        'cancelAuth' : function(){
                                var pair = X_Pair_get( this );
                                
@@ -89,8 +137,12 @@ X[ 'OAuth2' ] = X_EventDispatcher[ 'inherits' ](
                                        delete pair.net;
                                };
                                
+                               if( pair.oauth2State !== 1 ){
+                                       return;
+                               };
+                               
                                // http://kojikoji75.hatenablog.com/entry/2013/12/15/223839
-                               X_NET_OAUTH2_authorizationWindow && X_NET_OAUTH2_authorizationWindow.open( 'about:blank','_self' ).close();
+                               X_NET_OAUTH2_authorizationWindow && X_NET_OAUTH2_authorizationWindow.open( 'about:blank', '_self' ).close();
                                X_NET_OAUTH2_authorizationWindow  = null;
                                
                                X_NET_OAUTH2_authorizationTimerID && X_Timer_remove( X_NET_OAUTH2_authorizationTimerID );
@@ -99,17 +151,19 @@ X[ 'OAuth2' ] = X_EventDispatcher[ 'inherits' ](
                                this[ 'asyncDispatch' ]( X_EVENT_CANCELED );
                        },
                        
+                       /**
+                        * アクセストークンのリフレッシュ。
+                        */
                        'refreshToken' : function(){
-                               /* TODO 自動リフレッシュ
-                                *                              var expires_at = this._getAccessTokenExpiry();
-                               if (expires_at && Date.now() + millis > expires_at)
-                                       this._refreshAccessToken({replay: false});
-                                */
-                               
-                               pair = X_Pair_get( this );
+                               var pair = X_Pair_get( this );
                                
                                if( pair.net ) return;
                                
+                               if( pair.refreshTimerID ){
+                                       X_Timer_remove( pair.refreshTimerID );
+                                       delete pair.refreshTimerID;
+                               };
+                               
                                pair.oauth2State = 3;
                                
                                pair.net = X.Net( {
@@ -133,6 +187,27 @@ X[ 'OAuth2' ] = X_EventDispatcher[ 'inherits' ](
                }
        );
 
+function X_NET_OAUTH2_handleEvent( e ){
+       var pair = X_Pair_get( this );
+       
+       switch( e.type ){
+               case X_EVENT_KILL_INSTANCE :
+                       this[ 'cancelAuth' ]();
+               
+               case X_EVENT_ERROR :
+               case X_EVENT_NEED_AUTH :
+                       pair.refreshTimerID && X_Timer_remove( pair.refreshTimerID );
+                       break;
+                       
+               case X_EVENT_SUCCESS :
+                       pair.refreshTimerID && X_Timer_remove( pair.refreshTimerID );
+                       if( _getRefreshToken( this ) ){
+                               // 自動リフレッシュ
+                               pair.refreshTimerID = X_Timer_once( _getAccessTokenExpiry( this ) - X_Timer_now() - pair[ 'refreshMargin' ], this, this[ 'refreshToken' ] );
+                       };
+       };
+};
+
 function X_Net_OAuth2_detectAuthPopup(){
        var closed, search, pair = X_Pair_get( this );
        
@@ -146,7 +221,7 @@ function X_Net_OAuth2_detectAuthPopup(){
                pair      = X_Pair_get( this );
                pair.code = X_URL_ParamToObj( search.slice( 1 ) )[ 'code' ];
 
-               X_NET_OAUTH2_authorizationWindow.close();
+               X_NET_OAUTH2_authorizationWindow.open( 'about:blank', '_self' ).close();
                closed = true;
 
                X_Net_OAuth2_authorizationCode( this, pair );
@@ -241,27 +316,30 @@ function X_Net_OAuth2_responceHandler( e ){
        };
 };
 
-function X_NET_OAUTH2_onXHR401Error( oauth2 ){
+function X_NET_OAUTH2_onXHR401Error( oauth2, e ){
        var pair = this,
+               headers = e[ 'headers' ],
                xhr, bearerParams, headersExposed = false;
        
        if( _getAuthMechanism( oauth2 ) !== 'param' ){
                xhr            = X_NET_currentWrapper[ '_rawObject' ];
-               bearerParams   = xhr.getResponseHeader( 'WWW-Authenticate' );
-               headersExposed = !X_Net_XHR_createXDR || !!xhr.getAllResponseHeaders(); // this is a hack for Firefox and IE
+               headersExposed = !X_Net_XHR_createXDR || !!headers; // this is a hack for Firefox and IE
+               bearerParams   = headersExposed && ( headers[ 'WWW-Authenticate' ] || headers[ 'www-authenticate' ] );
+               X_Type_isArray( bearerParams ) && ( bearerParams = bearerParams.join( '\n' ) );
        };
        
        // http://d.hatena.ne.jp/ritou/20110402/1301679908
-       if ( bearerParams && bearerParams.indexOf( ' error="' ) === -1 ) {
+       if ( bearerParams && bearerParams.indexOf( ' error=' ) === -1 ) {
                pair.oauth2State = 0;
                oauth2[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
        } else
-       if ((( bearerParams && bearerParams.indexOf( ' error="invalid_token"' ) !== -1 ) || !headersExposed) && _getRefreshToken( oauth2 ) ) {
+       if ((( bearerParams && bearerParams.indexOf( 'invalid_token' ) !== -1 ) || !headersExposed) && _getRefreshToken( oauth2 ) ) {
                _removeAccessToken( oauth2 ); // It doesn't work any more.
                pair.oauth2State = 3;
                oauth2[ 'refreshToken' ]();
-       } else
-       if (!headersExposed && !_getRefreshToken( oauth2 )) {
+       } else {
+       //if (!headersExposed && !_getRefreshToken( oauth2 )) {
+               _removeAccessToken( oauth2 ); // It doesn't work any more.
                pair.oauth2State = 0;
                oauth2[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
        };
@@ -285,7 +363,7 @@ function X_NET_OAUTH2_updateRequest( oauth2, request ){
 
 function _getAccessToken( that ){ return updateLocalStorage( '', that, 'accessToken' ); }
 function _getRefreshToken( that){ return updateLocalStorage( '', that, 'refreshToken' ); }
-function _getAccessTokenExpiry( that ){ return updateLocalStorage( '', that, 'tokenExpiry' ); }
+function _getAccessTokenExpiry( that ){ return parseInt( updateLocalStorage( '', that, 'tokenExpiry' ) ) || 0; }
 function _getAuthMechanism( that ){
                // TODO use gadget | flash ...
                // IE's XDomainRequest doesn't support sending headers, so don't try.