diff --git a/app/javascript/components/TypesDeChampEditor/OperationsQueue.js b/app/javascript/components/TypesDeChampEditor/OperationsQueue.js index f849e64bc..166f1891a 100644 --- a/app/javascript/components/TypesDeChampEditor/OperationsQueue.js +++ b/app/javascript/components/TypesDeChampEditor/OperationsQueue.js @@ -1,4 +1,4 @@ -import { to, getJSON } from '@utils'; +import { getJSON } from '@utils'; export default class OperationsQueue { constructor(baseUrl) { @@ -30,23 +30,27 @@ export default class OperationsQueue { async exec(operation) { const { path, method, payload, resolve, reject } = operation; const url = `${this.baseUrl}${path}`; - const [data, xhr] = await to(getJSON(url, payload, method)); - if (xhr) { - handleError(xhr, reject); - } else { + try { + const data = await getJSON(url, payload, method); resolve(data); + } catch (e) { + handleError(e, reject); } } } -function handleError(xhr, reject) { - try { - const { - errors: [message] - } = JSON.parse(xhr.responseText); +async function handleError({ response, message }, reject) { + if (response) { + try { + const { + errors: [message] + } = await response.json(); + reject(message); + } catch { + reject(await response.text()); + } + } else { reject(message); - } catch (e) { - reject(xhr.responseText); } } diff --git a/app/javascript/packs/application-old.js b/app/javascript/packs/application-old.js index 5f831fc68..e33dfc996 100644 --- a/app/javascript/packs/application-old.js +++ b/app/javascript/packs/application-old.js @@ -5,7 +5,6 @@ import jQuery from 'jquery'; import '../shared/page-update-event'; import '../shared/activestorage/ujs'; -import '../shared/rails-ujs-fix'; import '../shared/safari-11-file-xhr-workaround'; import '../shared/remote-input'; import '../shared/franceconnect'; diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index c58265eff..a742f3d29 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -7,7 +7,6 @@ import ReactRailsUJS from 'react_ujs'; import '../shared/page-update-event'; import '../shared/activestorage/ujs'; import '../shared/remote-poller'; -import '../shared/rails-ujs-fix'; import '../shared/safari-11-file-xhr-workaround'; import '../shared/remote-input'; import '../shared/franceconnect'; diff --git a/app/javascript/shared/rails-ujs-fix.js b/app/javascript/shared/rails-ujs-fix.js deleted file mode 100644 index b9031ce63..000000000 --- a/app/javascript/shared/rails-ujs-fix.js +++ /dev/null @@ -1,20 +0,0 @@ -import jQuery from 'jquery'; - -// rails-ujs installs CSRFProtection for its own ajax implementation. We might need -// CSRFProtection for jQuery initiated requests. This code is from jquery-ujs. -jQuery.ajaxPrefilter((options, originalOptions, xhr) => { - if (!options.crossDomain) { - CSRFProtection(xhr); - } -}); - -function csrfToken() { - return jQuery('meta[name=csrf-token]').attr('content'); -} - -function CSRFProtection(xhr) { - let token = csrfToken(); - if (token) { - xhr.setRequestHeader('X-CSRF-Token', token); - } -} diff --git a/app/javascript/shared/utils.js b/app/javascript/shared/utils.js index 595e277b8..ea0fd01f1 100644 --- a/app/javascript/shared/utils.js +++ b/app/javascript/shared/utils.js @@ -1,9 +1,8 @@ import Rails from '@rails/ujs'; -import $ from 'jquery'; import debounce from 'debounce'; export { debounce }; -export const { fire } = Rails; +export const { fire, csrfToken } = Rails; export function show(el) { el && el.classList.remove('hidden'); @@ -67,17 +66,20 @@ export function ajax(options) { }); } -export function getJSON(url, data, method = 'get') { - data = method !== 'get' && data ? JSON.stringify(data) : data; - return Promise.resolve( - $.ajax({ - method, - url, - data, - contentType: 'application/json', - dataType: 'json' - }) - ); +export function getJSON(url, data, method = 'GET') { + const { query, ...options } = fetchOptions(data, method); + + return fetch(`${url}${query}`, options).then((response) => { + if (response.ok) { + if (response.status === 204) { + return null; + } + return response.json(); + } + const error = new Error(response.statusText || response.status); + error.response = response; + throw error; + }); } export function scrollTo(container, scrollTo) { @@ -95,10 +97,6 @@ export function on(selector, eventName, fn) { ); } -export function to(promise) { - return promise.then((result) => [result]).catch((error) => [null, error]); -} - export function isNumeric(n) { return !isNaN(parseFloat(n)) && isFinite(n); } @@ -120,3 +118,50 @@ export function timeoutable(promise, timeoutDelay) { }); return Promise.race([promise, timeoutPromise]); } + +const FETCH_TIMEOUT = 30 * 1000; // 30 sec + +function fetchOptions(data, method = 'GET') { + const options = { + query: '', + method: method.toUpperCase(), + headers: { + accept: 'application/json', + 'x-csrf-token': csrfToken(), + 'x-requested-with': 'XMLHttpRequest' + }, + credentials: 'same-origin' + }; + + if (data) { + if (options.method === 'GET') { + options.query = objectToQuerystring(data); + } else { + options.headers['content-type'] = 'application/json'; + options.body = JSON.stringify(data); + } + } + + if (window.AbortController) { + const controller = new AbortController(); + options.signal = controller.signal; + + setTimeout(() => { + controller.abort(); + }, FETCH_TIMEOUT); + } + + return options; +} + +function objectToQuerystring(obj) { + return Object.keys(obj).reduce(function (query, key, i) { + return [ + query, + i === 0 ? '?' : '&', + encodeURIComponent(key), + '=', + encodeURIComponent(obj[key]) + ].join(''); + }, ''); +}