dataType: 'json'
}
},
- tokenCache = {};
+ // Keyed by ajax url and symbolic name for the individual request
+ deferreds = {};
+
+ // Pre-populate with fake ajax deferreds to save http requests for tokens
+ // we already have on the page via the user.tokens module (bug 34733).
+ deferreds[ defaultOptions.ajax.url ] = {};
+ $.each( mw.user.tokens.get(), function ( key, value ) {
+ // This requires #getToken to use the same key as user.tokens.
+ // Format: token-type + "Token" (eg. editToken, patrolToken, watchToken).
+ deferreds[ defaultOptions.ajax.url ][ key ] = $.Deferred()
+ .resolve( value )
+ .promise( { abort: function () {} } );
+ } );
/**
* Constructor to create an object to interact with the API of a particular MediaWiki server.
* @since 1.22
*/
postWithToken: function ( tokenType, params ) {
- var api = this, hasOwn = tokenCache.hasOwnProperty;
- if ( hasOwn.call( tokenCache, tokenType ) && tokenCache[tokenType] !== undefined ) {
- params.token = tokenCache[tokenType];
+ var api = this;
+
+ return api.getToken( tokenType ).then( function ( token ) {
+ params.token = token;
return api.post( params ).then(
+ // If no error, return to caller as-is
null,
+ // Error handler
function ( code ) {
if ( code === 'badtoken' ) {
- // force a new token, clear any old one
- tokenCache[tokenType] = params.token = undefined;
- return api.post( params );
+ // Clear from cache
+ deferreds[ this.defaults.ajax.url ][ tokenType + 'Token' ] =
+ params.token = undefined;
+
+ // Try again, once
+ return api.getToken( tokenType ).then( function ( token ) {
+ params.token = token;
+ return api.post( params );
+ } );
}
- // Pass the promise forward, so the caller gets error codes
+
+ // Different error, pass on to let caller handle the error code
return this;
}
);
- } else {
- return api.getToken( tokenType ).then( function ( token ) {
- tokenCache[tokenType] = params.token = token;
- return api.post( params );
- } );
- }
+ } );
},
/**
- * Api helper to grab any token.
+ * Get a token for a certain action from the API.
*
- * @param {string} type Token type.
+ * @param {string} type Token type
* @return {jQuery.Promise}
* @return {Function} return.done
* @return {string} return.done.token Received token.
*/
getToken: function ( type ) {
var apiPromise,
+ deferredGroup = deferreds[ this.defaults.ajax.url ],
+ d = deferredGroup && deferredGroup[ type + 'Token' ];
+
+ if ( !d ) {
d = $.Deferred();
- apiPromise = this.get( {
- action: 'tokens',
- type: type
- } )
- .done( function ( data ) {
- // If token type is not available for this user,
- // key '...token' is missing or can contain Boolean false
- if ( data.tokens && data.tokens[type + 'token'] ) {
- d.resolve( data.tokens[type + 'token'] );
- } else {
- d.reject( 'token-missing', data );
- }
- } )
- .fail( d.reject );
+ apiPromise = this.get( { action: 'tokens', type: type } )
+ .done( function ( data ) {
+ // If token type is not available for this user,
+ // key '...token' is missing or can contain Boolean false
+ if ( data.tokens && data.tokens[type + 'token'] ) {
+ d.resolve( data.tokens[type + 'token'] );
+ } else {
+ d.reject( 'token-missing', data );
+ }
+ } )
+ .fail( d.reject );
+
+ // Attach abort handler
+ d.abort = apiPromise.abort;
+
+ // Store deferred now so that we can use this again even if it isn't ready yet
+ if ( !deferredGroup ) {
+ deferredGroup = deferreds[ this.defaults.ajax.url ] = {};
+ }
+ deferredGroup[ type + 'Token' ] = d;
+ }
- return d.promise( { abort: apiPromise.abort } );
+ return d.promise( { abort: d.abort } );
}
};
this.server.respond();
} );
+ QUnit.test( 'getToken( cached )', function ( assert ) {
+ QUnit.expect( 2 );
+
+ var api = new mw.Api();
+
+ // Get editToken for local wiki, this should not make
+ // a request as it should be retrieved from user.tokens.
+ api.getToken( 'edit' )
+ .done( function ( token ) {
+ assert.ok( token.length, 'Got a token' );
+ } )
+ .fail( function ( err ) {
+ assert.equal( '', err, 'API error' );
+ } );
+
+ assert.equal( this.server.requests.length, 0, 'Requests made' );
+ } );
+
+ QUnit.test( 'getToken( uncached )', function ( assert ) {
+ QUnit.expect( 2 );
+
+ var api = new mw.Api();
+
+ // Get a token of a type that isn't prepopulated by user.tokens.
+ // Could use "block" or "delete" here, but those could in theory
+ // be added to user.tokens, use a fake one instead.
+ api.getToken( 'testaction' )
+ .done( function ( token ) {
+ assert.ok( token.length, 'Got a token' );
+ } )
+ .fail( function ( err ) {
+ assert.equal( '', err, 'API error' );
+ } );
+
+ assert.equal( this.server.requests.length, 1, 'Requests made' );
+
+ this.server.respond( function ( request ) {
+ request.respond( 200, { 'Content-Type': 'application/json' },
+ '{ "tokens": { "testactiontoken": "0123abc" } }'
+ );
+ } );
+ } );
+
}( mediaWiki ) );