* `options` to mw.Api constructor.
* @property {Object} defaultOptions.parameters Default query parameters for API requests.
* @property {Object} defaultOptions.ajax Default options for jQuery#ajax.
+ * @property {boolean} defaultOptions.useUS Whether to use U+001F when joining multi-valued
+ * parameters (since 1.28). Default is true if ajax.url is not set, false otherwise for
+ * compatibility.
* @private
*/
var defaultOptions = {
options.ajax.url = String( options.ajax.url );
}
+ options = $.extend( { useUS: !options.ajax || !options.ajax.url }, options );
+
options.parameters = $.extend( {}, defaultOptions.parameters, options.parameters );
options.ajax = $.extend( {}, defaultOptions.ajax, options.ajax );
*
* @private
* @param {Object} parameters (modified in-place)
+ * @param {boolean} useUS Whether to use U+001F when joining multi-valued parameters.
*/
- preprocessParameters: function ( parameters ) {
+ preprocessParameters: function ( parameters, useUS ) {
var key;
// Handle common MediaWiki API idioms for passing parameters
for ( key in parameters ) {
// Multiple values are pipe-separated
if ( $.isArray( parameters[ key ] ) ) {
- parameters[ key ] = parameters[ key ].join( '|' );
+ if ( !useUS || parameters[ key ].join( '' ).indexOf( '|' ) === -1 ) {
+ parameters[ key ] = parameters[ key ].join( '|' );
+ } else {
+ parameters[ key ] = '\x1f' + parameters[ key ].join( '\x1f' );
+ }
}
// Boolean values are only false when not given at all
if ( parameters[ key ] === false || parameters[ key ] === undefined ) {
delete parameters.token;
}
- this.preprocessParameters( parameters );
+ this.preprocessParameters( parameters, this.defaults.useUS );
// If multipart/form-data has been requested and emulation is possible, emulate it
if (
} )
// AJAX success just means "200 OK" response, also check API error codes
.done( function ( result, textStatus, jqXHR ) {
+ var code;
if ( result === undefined || result === null || result === '' ) {
apiDeferred.reject( 'ok-but-empty',
'OK response but empty result (check HTTP headers?)',
jqXHR
);
} else if ( result.error ) {
- var code = result.error.code === undefined ? 'unknown' : result.error.code;
+ code = result.error.code === undefined ? 'unknown' : result.error.code;
apiDeferred.reject( code, result, result, jqXHR );
} else {
apiDeferred.resolve( result, jqXHR );
*/
postWithToken: function ( tokenType, params, ajaxOptions ) {
var api = this,
- abortable;
+ abortedPromise = $.Deferred().reject( 'http',
+ { textStatus: 'abort', exception: 'abort' } ).promise(),
+ abortable,
+ aborted;
- return ( abortable = api.getToken( tokenType, params.assert ) ).then( function ( token ) {
+ return api.getToken( tokenType, params.assert ).then( function ( token ) {
params.token = token;
+ // Request was aborted while token request was running, but we
+ // don't want to unnecessarily abort token requests, so abort
+ // a fake request instead
+ if ( aborted ) {
+ return abortedPromise;
+ }
+
return ( abortable = api.post( params, ajaxOptions ) ).then(
// If no error, return to caller as-is
null,
api.badToken( tokenType );
// Try again, once
params.token = undefined;
- return ( abortable = api.getToken( tokenType, params.assert ) ).then( function ( token ) {
+ abortable = null;
+ return api.getToken( tokenType, params.assert ).then( function ( token ) {
params.token = token;
- return ( abortable = api.post( params, ajaxOptions ) ).promise();
+ if ( aborted ) {
+ return abortedPromise;
+ }
+
+ return ( abortable = api.post( params, ajaxOptions ) );
} );
}
}
);
} ).promise( { abort: function () {
- abortable.abort();
+ if ( abortable ) {
+ abortable.abort();
+ } else {
+ aborted = true;
+ }
} } );
},