2 var X_NET_OAUTH2_detection = new Function( 'w', 'try{return w.location.search}catch(e){}' ),
3 X_NET_OAUTH2_authorizationWindow,
4 X_NET_OAUTH2_authorizationTimerID;
9 * <dt>X.Event.NEED_AUTH<dd>window を popup して認可を行う必要あり。ポインターイベント内で oauth2.requestAuth() を呼ぶ。
10 * <dt>X.Event.CANCELED<dd>認可 window が閉じられた。([x]等でウインドウが閉じられた、oauth2.cancelAuth() が呼ばれた)
11 * <dt>X.Event.SUCCESS<dd>認可 window でユーザーが認可し、続いてコードの認可が済んだ。
12 * <dt>X.Event.ERROR<dd>コードの認可のエラー、リフレッシュトークンのエラー、ネットワークエラー
13 * <dt>X.Event.PROGRESS<dd>コードを window から受け取った、リフレッシュトークンの開始、コードの認可を header -> params に切替
18 * <dt>0 : <dd>disconnected
19 * <dt>1 : <dd>now authentication ...
20 * <dt>2 : <dd>authorization_code
21 * <dt>3 : <dd>refresh_token
22 * <dt>4 : <dd>hasAccessToken
25 X[ 'OAuth2' ] = X_EventDispatcher[ 'inherits' ](
29 /** @lends OAuth2.prototype */
31 'Constructor' : function( obj ){
32 obj = X_Object_clone( obj );
34 X_Pair_create( this, obj );
36 if( _getAccessToken( this ) ){
38 this[ 'asyncDispatch' ]( X_EVENT_SUCCESS );
40 this[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
43 obj.onAuthError = X_NET_OAUTH2_onXHR401Error;
44 obj.updateRequest = X_NET_OAUTH2_updateRequest;
50 return X_Pair_get( this ).oauth2State || 0;
53 'requestAuth' : function(){
54 // pointer event 内で呼ぶこと
56 if( X_NET_OAUTH2_authorizationWindow ) return;
58 pair = X_Pair_get( this );
60 if( pair.net || pair.oauth2State ) return;
62 X_NET_OAUTH2_authorizationWindow = window.open(
63 pair[ 'authorizeEndpoint' ] + '?' + X_URL_objToParam(
65 'response_type' : 'code',
66 'client_id' : pair[ 'clientID' ],
67 'redirect_uri' : pair[ 'redirectURI' ],
68 'scope' : ( pair[ 'scopes' ] || []).join(' ')
71 'width=' + pair[ 'authorizeWindowWidth' ]
72 + ',height=' + pair[ 'authorizeWindowHeight' ]
73 + ',left=' + (screen.width - pair[ 'authorizeWindowWidth' ] ) / 2
74 + ',top=' + (screen.height - pair[ 'authorizeWindowHeight' ] ) / 2
75 + ',menubar=no,toolbar=no');
77 X_NET_OAUTH2_authorizationTimerID = X_Timer_add( 333, 0, this, X_Net_OAuth2_detectAuthPopup );
81 this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, message : 'Start to auth.' } );
84 'cancelAuth' : function(){
85 var pair = X_Pair_get( this );
92 // http://kojikoji75.hatenablog.com/entry/2013/12/15/223839
93 X_NET_OAUTH2_authorizationWindow && X_NET_OAUTH2_authorizationWindow.open( 'about:blank','_self' ).close();
94 X_NET_OAUTH2_authorizationWindow = null;
96 X_NET_OAUTH2_authorizationTimerID && X_Timer_remove( X_NET_OAUTH2_authorizationTimerID );
97 X_NET_OAUTH2_authorizationTimerID = 0;
99 this[ 'asyncDispatch' ]( X_EVENT_CANCELED );
102 'refreshToken' : function(){
104 * var expires_at = this._getAccessTokenExpiry();
105 if (expires_at && Date.now() + millis > expires_at)
106 this._refreshAccessToken({replay: false});
109 pair = X_Pair_get( this );
111 if( pair.net ) return;
113 pair.oauth2State = 3;
116 'xhr' : pair[ 'tokenEndpoint' ],
117 'postdata' : X_URL_objToParam({
118 'client_id' : pair[ 'clientID' ],
119 'client_secret' : pair[ 'clientSecret' ],
120 'grant_type' : 'refresh_token',
121 'refresh_token' : _getRefreshToken( this )
125 'Accept' : 'application/json',
126 'Content-Type' : 'application/x-www-form-urlencoded'
129 } ).listenOnce( [ X_EVENT_SUCCESS, X_EVENT_ERROR ], this, X_Net_OAuth2_responceHandler );
131 this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, message : 'Start to refresh token.' } );
136 function X_Net_OAuth2_detectAuthPopup(){
137 var closed, search, pair = X_Pair_get( this );
139 if( X_NET_OAUTH2_authorizationWindow.closed ){
140 pair.oauth2State = 0;
143 this[ 'asyncDispatch' ]( X_EVENT_CANCELED );
145 if( search = X_NET_OAUTH2_detection( X_NET_OAUTH2_authorizationWindow ) ){
146 pair = X_Pair_get( this );
147 pair.code = X_URL_ParamToObj( search.slice( 1 ) )[ 'code' ];
149 X_NET_OAUTH2_authorizationWindow.close();
152 X_Net_OAuth2_authorizationCode( this, pair );
154 pair.oauth2State = 2;
155 this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, message : 'Get code success, then authorization code.' } );
159 X_NET_OAUTH2_authorizationWindow = null;
160 X_NET_OAUTH2_authorizationTimerID = 0;
162 return X_Callback_UN_LISTEN;
166 function X_Net_OAuth2_authorizationCode( oauth2, pair ){
168 'xhr' : pair[ 'tokenEndpoint' ],
169 'postdata' : X_URL_objToParam({
170 'client_id' : pair[ 'clientID' ],
171 'client_secret' : pair[ 'clientSecret' ],
172 'grant_type' : 'authorization_code',
174 'redirect_uri' : pair[ 'redirectURI' ]
178 'Accept' : 'application/json',
179 'Content-Type' : 'application/x-www-form-urlencoded'
182 } ).listenOnce( [ X_EVENT_SUCCESS, X_EVENT_ERROR ], oauth2, X_Net_OAuth2_responceHandler );
185 function X_Net_OAuth2_responceHandler( e ){
187 pair = X_Pair_get( this ),
188 isRefresh = pair.oauth2State === 3;
193 case X_EVENT_SUCCESS :
194 if( isRefresh && data.error ){
195 _removeRefreshToken( this );
196 pair.oauth2State = 0;
197 this[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'Refresh access token error.' + data.error, data : data } );
198 this[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
202 pair.oauth2State = 0;
203 this[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'Get new access token error.' + data.error, data : data } );
204 this[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
208 _setAccessToken( this, data[ 'access_token' ] || '' );
209 _setRefreshToken( this, data[ 'refresh_token' ] || '' );
211 if( data[ 'expires_in' ] ){
212 _setAccessTokenExpiry( this, X_Timer_now() + data[ 'expires_in' ] * 1000 );
214 if( _getAccessTokenExpiry( this ) ){
215 _removeAccessTokenExpiry( this );
218 pair.oauth2State = 4;
219 this[ 'asyncDispatch' ]( { type : X_EVENT_SUCCESS, message : isRefresh ? 'Refresh access token success.' : 'Get new access token success.' } );
224 // other error, not auth
225 pair.oauth2State = 0;
226 this[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'Refresh access token error.' } );
227 _removeRefreshToken( this );
228 this[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
230 if( _getAuthMechanism( this ) === 'param' ){
231 pair.oauth2State = 0;
232 this[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'network-error' } );
234 pair.oauth2State = 0;
235 _setAuthMechanism( this, 'param' );
236 this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, message : 'Refresh access token failed. retry header -> param. ' } );
238 X_Net_OAuth2_authorizationCode( this, pair );
244 function X_NET_OAUTH2_onXHR401Error( oauth2 ){
246 xhr, bearerParams, headersExposed = false;
248 if( _getAuthMechanism( oauth2 ) !== 'param' ){
249 xhr = X_NET_currentWrapper[ '_rawObject' ];
250 bearerParams = xhr.getResponseHeader( 'WWW-Authenticate' );
251 headersExposed = !X_Net_XHR_createXDR || !!xhr.getAllResponseHeaders(); // this is a hack for Firefox and IE
254 // http://d.hatena.ne.jp/ritou/20110402/1301679908
255 if ( bearerParams && bearerParams.indexOf( ' error="' ) === -1 ) {
256 pair.oauth2State = 0;
257 oauth2[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
259 if ((( bearerParams && bearerParams.indexOf( ' error="invalid_token"' ) !== -1 ) || !headersExposed) && _getRefreshToken( oauth2 ) ) {
260 _removeAccessToken( oauth2 ); // It doesn't work any more.
261 pair.oauth2State = 3;
262 oauth2[ 'refreshToken' ]();
264 if (!headersExposed && !_getRefreshToken( oauth2 )) {
265 pair.oauth2State = 0;
266 oauth2[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
270 function X_NET_OAUTH2_updateRequest( oauth2, request ){
271 var token = _getAccessToken( oauth2 ),
272 mechanism = _getAuthMechanism( oauth2 ),
273 url = request[ 'url' ],
276 if( token && mechanism === 'param' ){
277 request[ 'url' ] = url + ((url.indexOf('?') !== -1) ? '&' : '?') + 'bearer_token=' + encodeURIComponent( token );
280 if( token && ( !mechanism || mechanism === 'header' ) ){
281 headers = request[ 'headers' ] || ( request[ 'headers' ] = {} );
282 headers[ 'Authorization' ] = 'Bearer ' + token;
286 function _getAccessToken( that ){ return updateLocalStorage( '', that, 'accessToken' ); }
287 function _getRefreshToken( that){ return updateLocalStorage( '', that, 'refreshToken' ); }
288 function _getAccessTokenExpiry( that ){ return updateLocalStorage( '', that, 'tokenExpiry' ); }
289 function _getAuthMechanism( that ){
290 // TODO use gadget | flash ...
291 // IE's XDomainRequest doesn't support sending headers, so don't try.
292 return X_Net_XHR_createXDR ? 'param' : updateLocalStorage( '', that, 'AuthMechanism' );
294 function _setAccessToken( that, value ){ updateLocalStorage( '+', that, 'accessToken' , value); }
295 function _setRefreshToken( that, value ){ updateLocalStorage( '+', that, 'refreshToken', value); }
296 function _setAccessTokenExpiry( that, value ){ updateLocalStorage( '+', that, 'tokenExpiry', value); }
297 function _setAuthMechanism( that, value ){ updateLocalStorage( '+', that, 'AuthMechanism', value); }
299 function _removeAccessToken( that ){ updateLocalStorage( '-', that, 'accessToken' ); }
300 function _removeRefreshToken( that ){ updateLocalStorage( '-', that, 'refreshToken' ); }
301 function _removeAccessTokenExpiry( that ){ updateLocalStorage( '-', that, 'tokenExpiry' ); }
302 function _removeAuthMechanism( that ){ updateLocalStorage( '-', that, 'AuthMechanism' ); }
304 function updateLocalStorage( cmd, that, name, value ){
305 var action = cmd === '+' ? 'setItem' : cmd === '-' ? 'removeItem' : 'getItem',
308 if( window.localStorage ){
309 return window.localStorage[ action ]( X_Pair_get( that )[ 'clientID' ] + name, value );
312 pair = X_Pair_get( that );
315 pair[ name ] = value;
318 if( pair[ name ] !== undefined ) delete pair[ name ];