mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-09-25 03:08:42 +00:00
Use built-in mw.Api 'badtoken' handling, also 'assert'/'assertuser'
When the user is saving their edit, we want to ensure that they understand how it will be attributed. If the user gets logged out or logs in in another tab, we want to display a message about it before saving. Instead of manually managing tokens and handling the 'badtoken' error to detect this, use the 'assert'/'assertuser' parameters for the API to detect it for us. Thanks to this we can rely on automatic retrying for 'badtoken' errors in mw.Api#postWithToken. It will be possible to share some of this code with other extensions that already use ArticleTargetSaver, namely DiscussionTools, now that it doesn't need to manage tokens for VisualEditor. Bug: T245327 Depends-On: I485f99e1f5f493262b0c9af22370da01adf1e09c Change-Id: I37f8e89b6d92c419d1b6569891612256342f8139
This commit is contained in:
parent
6f7ef2fd76
commit
58757d4e3e
|
@ -155,7 +155,7 @@ ve.ui.MWExportWikitextDialog.prototype.export = function () {
|
||||||
format: 'text/x-wiki',
|
format: 'text/x-wiki',
|
||||||
model: 'wikitext',
|
model: 'wikitext',
|
||||||
wpTextbox1: wikitext,
|
wpTextbox1: wikitext,
|
||||||
wpEditToken: ve.init.target.editToken,
|
wpEditToken: mw.user.tokens.get( 'csrfToken' ),
|
||||||
// MediaWiki function-verification parameters, mostly relevant to the
|
// MediaWiki function-verification parameters, mostly relevant to the
|
||||||
// classic editpage, but still required here:
|
// classic editpage, but still required here:
|
||||||
wpUnicodeCheck: 'ℳ𝒲♥𝓊𝓃𝒾𝒸ℴ𝒹ℯ',
|
wpUnicodeCheck: 'ℳ𝒲♥𝓊𝓃𝒾𝒸ℴ𝒹ℯ',
|
||||||
|
|
|
@ -694,23 +694,13 @@ ve.init.mw.ArticleTarget.prototype.saveFail = function ( doc, saveData, wasRetry
|
||||||
for ( i = 0; i < data.errors.length; i++ ) {
|
for ( i = 0; i < data.errors.length; i++ ) {
|
||||||
error = data.errors[ i ];
|
error = data.errors[ i ];
|
||||||
|
|
||||||
// Handle token errors
|
|
||||||
if ( error.code === 'badtoken' ) {
|
if ( error.code === 'badtoken' ) {
|
||||||
if ( wasRetry ) {
|
this.saveErrorBadTokenOrNewUser( null, true );
|
||||||
this.saveErrorBadToken( null, true );
|
} else if ( error.code === 'assertanonfailed' || error.code === 'assertuserfailed' || error.code === 'assertnameduserfailed' ) {
|
||||||
return;
|
this.refreshUser().then( function ( username ) {
|
||||||
}
|
target.saveErrorBadTokenOrNewUser( username, false );
|
||||||
this.refreshEditToken().done( function ( userChanged ) {
|
}, function () {
|
||||||
// target.editToken has been refreshed
|
target.saveErrorUnknown( data );
|
||||||
if ( userChanged ) {
|
|
||||||
target.saveErrorBadToken( mw.user.isAnon() ? null : mw.user.getName(), false );
|
|
||||||
} else {
|
|
||||||
// New session is the same user still; retry
|
|
||||||
target.emit( 'saveErrorBadToken', true );
|
|
||||||
target.save( doc, saveData, true );
|
|
||||||
}
|
|
||||||
} ).fail( function () {
|
|
||||||
target.saveErrorBadToken( null, true );
|
|
||||||
} );
|
} );
|
||||||
return;
|
return;
|
||||||
} else if ( error.code === 'editconflict' ) {
|
} else if ( error.code === 'editconflict' ) {
|
||||||
|
@ -794,14 +784,14 @@ ve.init.mw.ArticleTarget.prototype.saveErrorHookAborted = function ( data ) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle token fetch indicating another user is logged in, and token fetch errors.
|
* Handle assert error indicating another user is logged in, and token fetch errors.
|
||||||
*
|
*
|
||||||
* @param {string|null} username Name of newly logged-in user, or null if anonymous
|
* @param {string|null} username Name of newly logged-in user, or null if anonymous
|
||||||
* @param {boolean} [error=false] Whether there was an error trying to figure out who we're logged in as
|
* @param {boolean} [error=false] Whether this is a token fetch error
|
||||||
* @fires saveErrorBadToken
|
* @fires saveErrorBadToken
|
||||||
* @fires saveErrorNewUser
|
* @fires saveErrorNewUser
|
||||||
*/
|
*/
|
||||||
ve.init.mw.ArticleTarget.prototype.saveErrorBadToken = function ( username, error ) {
|
ve.init.mw.ArticleTarget.prototype.saveErrorBadTokenOrNewUser = function ( username, error ) {
|
||||||
var $msg = $( document.createTextNode( mw.msg( 'visualeditor-savedialog-error-badtoken' ) + ' ' ) );
|
var $msg = $( document.createTextNode( mw.msg( 'visualeditor-savedialog-error-badtoken' ) + ' ' ) );
|
||||||
|
|
||||||
if ( error ) {
|
if ( error ) {
|
||||||
|
@ -1480,8 +1470,8 @@ ve.init.mw.ArticleTarget.prototype.save = function ( doc, options, isRetry ) {
|
||||||
basetimestamp: this.baseTimeStamp,
|
basetimestamp: this.baseTimeStamp,
|
||||||
starttimestamp: this.startTimeStamp,
|
starttimestamp: this.startTimeStamp,
|
||||||
etag: this.etag,
|
etag: this.etag,
|
||||||
// Pass in token to prevent automatic badtoken retries
|
assert: mw.user.isAnon() ? 'anon' : 'user',
|
||||||
token: this.editToken
|
assertuser: mw.user.getName() || undefined
|
||||||
} );
|
} );
|
||||||
|
|
||||||
if ( mw.config.get( 'wgVisualEditorConfig' ).useChangeTagging && !data.vetags ) {
|
if ( mw.config.get( 'wgVisualEditorConfig' ).useChangeTagging && !data.vetags ) {
|
||||||
|
@ -1588,7 +1578,7 @@ ve.init.mw.ArticleTarget.prototype.submit = function ( wikitext, fields ) {
|
||||||
wpStarttime: this.startTimeStamp,
|
wpStarttime: this.startTimeStamp,
|
||||||
wpEdittime: this.baseTimeStamp,
|
wpEdittime: this.baseTimeStamp,
|
||||||
wpTextbox1: wikitext,
|
wpTextbox1: wikitext,
|
||||||
wpEditToken: this.editToken,
|
wpEditToken: mw.user.tokens.get( 'csrfToken' ),
|
||||||
// MediaWiki function-verification parameters, mostly relevant to the
|
// MediaWiki function-verification parameters, mostly relevant to the
|
||||||
// classic editpage, but still required here:
|
// classic editpage, but still required here:
|
||||||
wpUnicodeCheck: 'ℳ𝒲♥𝓊𝓃𝒾𝒸ℴ𝒹ℯ',
|
wpUnicodeCheck: 'ℳ𝒲♥𝓊𝓃𝒾𝒸ℴ𝒹ℯ',
|
||||||
|
|
|
@ -20,7 +20,6 @@ ve.init.mw.Target = function VeInitMwTarget( config ) {
|
||||||
|
|
||||||
this.active = false;
|
this.active = false;
|
||||||
this.pageName = mw.config.get( 'wgRelevantPageName' );
|
this.pageName = mw.config.get( 'wgRelevantPageName' );
|
||||||
this.editToken = mw.user.tokens.get( 'csrfToken' );
|
|
||||||
this.recovered = false;
|
this.recovered = false;
|
||||||
this.fromEditedState = false;
|
this.fromEditedState = false;
|
||||||
this.originalHtml = null;
|
this.originalHtml = null;
|
||||||
|
@ -459,26 +458,21 @@ ve.init.mw.Target.prototype.teardown = function () {
|
||||||
* the current user.
|
* the current user.
|
||||||
*
|
*
|
||||||
* @param {ve.dm.Document} [doc] Document to associate with the API request
|
* @param {ve.dm.Document} [doc] Document to associate with the API request
|
||||||
* @return {jQuery.Promise} Promise resolved with whether we switched users
|
* @return {jQuery.Promise} Promise resolved with new username, or null if anonymous
|
||||||
*/
|
*/
|
||||||
ve.init.mw.Target.prototype.refreshEditToken = function ( doc ) {
|
ve.init.mw.Target.prototype.refreshUser = function ( doc ) {
|
||||||
var api = this.getContentApi( doc ),
|
var api = this.getContentApi( doc ),
|
||||||
deferred = ve.createDeferred(),
|
deferred = ve.createDeferred();
|
||||||
target = this;
|
|
||||||
api.get( {
|
api.get( {
|
||||||
action: 'query',
|
action: 'query',
|
||||||
meta: 'tokens|userinfo',
|
meta: 'userinfo'
|
||||||
type: 'csrf'
|
|
||||||
} )
|
} )
|
||||||
.done( function ( data ) {
|
.done( function ( data ) {
|
||||||
var
|
var
|
||||||
userInfo = data.query && data.query.userinfo,
|
userInfo = data.query && data.query.userinfo,
|
||||||
editToken = data.query && data.query.tokens && data.query.tokens.csrftoken,
|
|
||||||
isAnon = mw.user.isAnon();
|
isAnon = mw.user.isAnon();
|
||||||
|
|
||||||
if ( userInfo && editToken ) {
|
if ( userInfo ) {
|
||||||
target.editToken = editToken;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
( isAnon && userInfo.anon !== undefined ) ||
|
( isAnon && userInfo.anon !== undefined ) ||
|
||||||
// Comparing id instead of name to protect against possible
|
// Comparing id instead of name to protect against possible
|
||||||
|
@ -486,7 +480,7 @@ ve.init.mw.Target.prototype.refreshEditToken = function ( doc ) {
|
||||||
mw.config.get( 'wgUserId' ) === userInfo.id
|
mw.config.get( 'wgUserId' ) === userInfo.id
|
||||||
) {
|
) {
|
||||||
// New session is the same user still
|
// New session is the same user still
|
||||||
deferred.resolve( false );
|
deferred.resolve( mw.user.getName() );
|
||||||
} else {
|
} else {
|
||||||
// The now current session is a different user
|
// The now current session is a different user
|
||||||
if ( userInfo.anon !== undefined ) {
|
if ( userInfo.anon !== undefined ) {
|
||||||
|
@ -502,7 +496,7 @@ ve.init.mw.Target.prototype.refreshEditToken = function ( doc ) {
|
||||||
// New session is a different user
|
// New session is a different user
|
||||||
mw.config.set( { wgUserId: userInfo.id, wgUserName: userInfo.name } );
|
mw.config.set( { wgUserId: userInfo.id, wgUserName: userInfo.name } );
|
||||||
}
|
}
|
||||||
deferred.resolve( true );
|
deferred.resolve( mw.user.getName() );
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
deferred.reject();
|
deferred.reject();
|
||||||
|
@ -519,15 +513,12 @@ ve.init.mw.Target.prototype.refreshEditToken = function ( doc ) {
|
||||||
*
|
*
|
||||||
* @param {ve.dm.Document} doc Document
|
* @param {ve.dm.Document} doc Document
|
||||||
* @param {boolean} [useRevision=true] Whether to use the revision ID + ETag
|
* @param {boolean} [useRevision=true] Whether to use the revision ID + ETag
|
||||||
* @param {boolean} [isRetry=false] Whether this call is retrying a prior call
|
|
||||||
* @return {jQuery.Promise} Abortable promise which resolves with a wikitext string
|
* @return {jQuery.Promise} Abortable promise which resolves with a wikitext string
|
||||||
*/
|
*/
|
||||||
ve.init.mw.Target.prototype.getWikitextFragment = function ( doc, useRevision, isRetry ) {
|
ve.init.mw.Target.prototype.getWikitextFragment = function ( doc, useRevision ) {
|
||||||
var promise, xhr,
|
var promise, xhr,
|
||||||
target = this,
|
|
||||||
params = {
|
params = {
|
||||||
action: 'visualeditoredit',
|
action: 'visualeditoredit',
|
||||||
token: this.editToken,
|
|
||||||
paction: 'serialize',
|
paction: 'serialize',
|
||||||
html: ve.dm.converter.getDomFromModel( doc ).body.innerHTML,
|
html: ve.dm.converter.getDomFromModel( doc ).body.innerHTML,
|
||||||
page: this.getPageName()
|
page: this.getPageName()
|
||||||
|
@ -543,7 +534,7 @@ ve.init.mw.Target.prototype.getWikitextFragment = function ( doc, useRevision, i
|
||||||
params.etag = this.etag;
|
params.etag = this.etag;
|
||||||
}
|
}
|
||||||
|
|
||||||
xhr = this.getContentApi( doc ).post(
|
xhr = this.getContentApi( doc ).postWithToken( 'csrf',
|
||||||
params,
|
params,
|
||||||
{ contentType: 'multipart/form-data' }
|
{ contentType: 'multipart/form-data' }
|
||||||
);
|
);
|
||||||
|
@ -553,12 +544,6 @@ ve.init.mw.Target.prototype.getWikitextFragment = function ( doc, useRevision, i
|
||||||
return response.visualeditoredit.content;
|
return response.visualeditoredit.content;
|
||||||
}
|
}
|
||||||
return ve.createDeferred().reject();
|
return ve.createDeferred().reject();
|
||||||
}, function ( error ) {
|
|
||||||
if ( error === 'badtoken' && !isRetry ) {
|
|
||||||
return target.refreshEditToken( doc ).then( function () {
|
|
||||||
return target.getWikitextFragment( doc, useRevision, true );
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
} );
|
} );
|
||||||
|
|
||||||
promise.abort = function () {
|
promise.abort = function () {
|
||||||
|
|
Loading…
Reference in a new issue