diff --git a/public/src/modules/api.js b/public/src/modules/api.js index ca619ec16e..8c9485e557 100644 --- a/public/src/modules/api.js +++ b/public/src/modules/api.js @@ -37,123 +37,99 @@ function call(options, callback) { } async function xhr(options, cb) { + // Normalize body based on type + const { url } = options; + delete options.url; + + if (options.data && !(options.data instanceof FormData)) { + options.data = JSON.stringify(options.data || {}); + options.headers['content-type'] = 'application/json; charset=utf-8'; + } + + // Allow options to be modified by plugins, etc. + ({ options } = await fireHook('filter:api.options', { options })); + /** - * N.B. fetch api is only used when payload is a FormData object! - * - * This is because the passed-in options are different between fetch/jQuery .ajax() - * If we updated the code to use only fetch, it would be a breaking change. + * Note: pre-v4 backwards compatibility * - * Prior to v3.3 there was no support for sending in FormData, so the addition of fetch - * handling is not breaking. + * This module now passes in "data" to xhr(). + * This is because the "filter:api.options" hook (and plugins using it) expect "data". + * fetch() expects body, so we rename it here. * - * Break this for v4 by making everything use fetch api. + * In v4, replace all instances of "data" with "body" and record as breaking change. */ - - // Adjust options based on payload type - if (options.payload instanceof FormData) { - const url = options.url; - options.body = options.payload; - delete options.payload; - delete options.url; - - // Allow options to be modified by plugins, etc. - ({ options } = await fireHook('filter:api.fetchOptions', { options })); - - await fetch(url, { - ...options, - }).then(async (res) => { - const payload = await res.json(); - if (Math.floor(res.status / 100) === 2) { - cb(null, ( - payload && - payload.hasOwnProperty('status') && - payload.hasOwnProperty('response') ? payload.response : (payload || {}) - )); - } else { - cb(new Error(payload.status.message)); - } - }).catch(cb); - } else { - options.data = JSON.stringify(options.payload || {}); - options.contentType = 'application/json; charset=utf-8'; - delete options.payload; - - // Allow options to be modified by plugins, etc. - ({ options } = await fireHook('filter:api.options', { options })); - - $.ajax(options) - .done((res) => { - cb(null, ( - res && - res.hasOwnProperty('status') && - res.hasOwnProperty('response') ? res.response : (res || {}) - )); - }) - .fail((ev) => { - let errMessage; - if (ev.responseJSON) { - errMessage = ev.responseJSON.status && ev.responseJSON.status.message ? - ev.responseJSON.status.message : - ev.responseJSON.error; - } - - cb(new Error(errMessage || ev.statusText)); - }); + if (options.data) { + options.body = options.data; + delete options.data; } + + await fetch(url, { + ...options, + }).then(async (res) => { + const response = await res.json(); + if (Math.floor(res.status / 100) === 2) { + cb(null, ( + response && + response.hasOwnProperty('status') && + response.hasOwnProperty('response') ? response.response : (response || {}) + )); + } else { + cb(new Error(response.status.message)); + } + }).catch(cb); } -export function get(route, payload, onSuccess) { +export function get(route, data, onSuccess) { return call({ - url: route + (payload && Object.keys(payload).length ? ('?' + $.param(payload)) : ''), + url: route + (data && Object.keys(data).length ? ('?' + $.param(data)) : ''), }, onSuccess); } -export function head(route, payload, onSuccess) { +export function head(route, data, onSuccess) { return call({ - url: route + (payload && Object.keys(payload).length ? ('?' + $.param(payload)) : ''), + url: route + (data && Object.keys(data).length ? ('?' + $.param(data)) : ''), method: 'head', }, onSuccess); } -export function post(route, payload, onSuccess) { +export function post(route, data, onSuccess) { return call({ url: route, method: 'post', - payload, + data, headers: { 'x-csrf-token': config.csrf_token, }, }, onSuccess); } -export function patch(route, payload, onSuccess) { +export function patch(route, data, onSuccess) { return call({ url: route, method: 'patch', - payload, + data, headers: { 'x-csrf-token': config.csrf_token, }, }, onSuccess); } -export function put(route, payload, onSuccess) { +export function put(route, data, onSuccess) { return call({ url: route, method: 'put', - payload, + data, headers: { 'x-csrf-token': config.csrf_token, }, }, onSuccess); } -export function del(route, payload, onSuccess) { +export function del(route, data, onSuccess) { return call({ url: route, method: 'delete', - data: JSON.stringify(payload), - contentType: 'application/json; charset=utf-8', + data, headers: { 'x-csrf-token': config.csrf_token, },