1 module.exports = ( function () {
4 * @classdesc Interact with the API of another MediaWiki site. mw.Foreign API creates
5 * an object like {@link mw.Api}, but automatically handle everything required to communicate
6 * with another MediaWiki wiki via cross-origin requests (CORS).
8 * The foreign wiki must be configured to accept requests from the current wiki. See
9 * <https://www.mediawiki.org/wiki/Manual:$wgCrossSiteAJAXdomains> for details.
11 * const api = new mw.ForeignApi( 'https://commons.wikimedia.org/w/api.php' );
15 * } ).done( function ( data ) {
16 * console.log( data );
20 * To ensure that the user at the foreign wiki is logged in, pass the `assert: 'user'` parameter
21 * to {@link mw.ForeignApi#get get()}/{@link mw.ForeignApi#post post()} (since MW 1.23), otherwise
22 * the API request will fail. (Note that this doesn't guarantee that it's the same user. To assert
23 * that the user at the foreign wiki has a specific username, pass the `assertuser` parameter with
24 * the desired username.)
26 * Authentication-related MediaWiki extensions may extend this class to ensure that the user
27 * authenticated on the current wiki will be automatically authenticated on the foreign one. These
28 * extension modules should be registered using the ResourceLoaderForeignApiModules hook. See
29 * CentralAuth for a practical example. The general pattern to extend and override the name is:
31 * function MyForeignApi() {};
32 * OO.inheritClass( MyForeignApi, mw.ForeignApi );
33 * mw.ForeignApi = MyForeignApi;
36 * @class mw.ForeignApi
41 * @description Create an instance of `mw.ForeignApi`.
42 * @param {string} url URL pointing to another wiki's `api.php` endpoint.
43 * @param {mw.Api.Options} [options] Also accepts all the options from {@link mw.Api.Options}.
44 * @param {boolean} [options.anonymous=false] Perform all requests anonymously. Use this option if
45 * the target wiki may otherwise not accept cross-origin requests, or if you don't need to
46 * perform write actions or read restricted information and want to avoid the overhead.
48 * @author Bartosz DziewoĆski
51 function CoreForeignApi( url, options ) {
52 if ( !url || $.isPlainObject( url ) ) {
53 throw new Error( 'mw.ForeignApi() requires a `url` parameter' );
56 this.apiUrl = String( url );
57 this.anonymous = options && options.anonymous;
59 options = $.extend( /* deep= */ true,
64 withCredentials: !this.anonymous
68 origin: this.getOrigin()
74 // Call parent constructor
75 CoreForeignApi.super.call( this, options );
78 OO.inheritClass( CoreForeignApi, mw.Api );
81 * Return the origin to use for API requests, in the required format (protocol, host and port, if
84 * @memberof mw.ForeignApi.prototype
86 * @return {string|undefined}
88 CoreForeignApi.prototype.getOrigin = function () {
89 if ( this.anonymous ) {
93 const origin = location.origin;
94 const apiOrigin = new URL( this.apiUrl, location.origin ).origin;
96 if ( origin === apiOrigin ) {
97 // requests are not cross-origin, omit parameter
107 CoreForeignApi.prototype.ajax = function ( parameters, ajaxOptions ) {
110 // 'origin' query parameter must be part of the request URI, and not just POST request body
111 if ( ajaxOptions.type === 'POST' ) {
112 let url = ( ajaxOptions && ajaxOptions.url ) || this.defaults.ajax.url;
113 const origin = ( parameters && parameters.origin ) || this.defaults.parameters.origin;
114 if ( origin !== undefined ) {
115 url += ( url.indexOf( '?' ) !== -1 ? '&' : '?' ) +
116 'origin=' + encodeURIComponent( origin );
118 newAjaxOptions = Object.assign( {}, ajaxOptions, { url: url } );
120 newAjaxOptions = ajaxOptions;
123 return CoreForeignApi.super.prototype.ajax.call( this, parameters, newAjaxOptions );
126 return CoreForeignApi;