// Keyed by ajax url and symbolic name for the individual request
promises = {};
+ function mapLegacyToken( action ) {
+ // Legacy types for backward-compatibility with API action=tokens.
+ var csrfActions = [
+ 'edit',
+ 'delete',
+ 'protect',
+ 'move',
+ 'block',
+ 'unblock',
+ 'email',
+ 'import',
+ 'options'
+ ];
+ return $.inArray( action, csrfActions ) !== -1 ? 'csrf' : action;
+ }
+
// Pre-populate with fake ajax promises to save http requests for tokens
// we already have on the page via the user.tokens module (bug 34733).
promises[ defaultOptions.ajax.url ] = {};
* each individual request by passing them to #get or #post (or directly #ajax) later on.
*/
mw.Api = function ( options ) {
- // TODO: Share API objects with exact same config.
options = options || {};
// Force a string if we got a mw.Uri object
options.ajax = $.extend( {}, defaultOptions.ajax, options.ajax );
this.defaults = options;
+ this.requests = [];
};
mw.Api.prototype = {
+ /**
+ * Abort all unfinished requests issued by this Api object.
+ *
+ * @method
+ */
+ abort: function () {
+ $.each( this.requests, function ( index, request ) {
+ if ( request ) {
+ request.abort();
+ }
+ } );
+ },
/**
* Perform API get request
/**
* Perform API post request
*
- * TODO: Post actions for non-local hostnames will need proxy.
- *
* @param {Object} parameters
* @param {Object} [ajaxOptions]
* @return {jQuery.Promise}
* Fail: Error code
*/
ajax: function ( parameters, ajaxOptions ) {
- var token,
+ var token, requestIndex,
+ api = this,
apiDeferred = $.Deferred(),
xhr, key, formData;
}
} );
+ requestIndex = this.requests.length;
+ this.requests.push( xhr );
+ xhr.always( function () {
+ api.requests[ requestIndex ] = null;
+ } );
// Return the Promise
return apiDeferred.promise( { abort: xhr.abort } ).fail( function ( code, details ) {
if ( !( code === 'http' && details && details.textStatus === 'abort' ) ) {
/**
* Get a token for a certain action from the API.
*
- * The assert parameter is only for internal use by postWithToken.
+ * The assert parameter is only for internal use by #postWithToken.
*
- * @param {string} type Token type
- * @return {jQuery.Promise}
- * @return {Function} return.done
- * @return {string} return.done.token Received token.
* @since 1.22
+ * @param {string} type Token type
+ * @return {jQuery.Promise} Received token.
*/
getToken: function ( type, assert ) {
- var apiPromise,
- promiseGroup = promises[ this.defaults.ajax.url ],
- d = promiseGroup && promiseGroup[ type + 'Token' ];
+ var apiPromise, promiseGroup, d;
+ type = mapLegacyToken( type );
+ promiseGroup = promises[ this.defaults.ajax.url ];
+ d = promiseGroup && promiseGroup[ type + 'Token' ];
if ( !d ) {
- apiPromise = this.get( { action: 'tokens', type: type, assert: assert } );
-
+ apiPromise = this.get( {
+ action: 'query',
+ meta: 'tokens',
+ type: type,
+ assert: assert
+ } );
d = apiPromise
- .then( function ( data ) {
- if ( data.tokens && data.tokens[ type + 'token' ] ) {
- return data.tokens[ type + 'token' ];
+ .then( function ( res ) {
+ // If token type is unknown, it is omitted from the response
+ if ( !res.query.tokens[ type + 'token' ] ) {
+ return $.Deferred().reject( 'token-missing', res );
}
- // If token type is not available for this user,
- // key '...token' is either missing or set to boolean false
- return $.Deferred().reject( 'token-missing', data );
+ return res.query.tokens[ type + 'token' ];
}, function () {
// Clear promise. Do not cache errors.
delete promiseGroup[ type + 'Token' ];
+
// Pass on to allow the caller to handle the error
return this;
} )
*/
badToken: function ( type ) {
var promiseGroup = promises[ this.defaults.ajax.url ];
+
+ type = mapLegacyToken( type );
if ( promiseGroup ) {
delete promiseGroup[ type + 'Token' ];
}