|
export class HTTPError extends Error { |
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error |
|
// https://stackoverflow.com/a/31090384/4144498 |
|
constructor(status, ...params) { |
|
super(...params); |
|
|
|
// Maintains proper detailed stack trace, but it`s available only on V8 |
|
if (Error.captureStackTrace) { |
|
Error.captureStackTrace(this, typeof(this)); |
|
} |
|
|
|
this.name = this.constructor.name; |
|
this.message = this.message || `HTTP ${status}.`; |
|
this.status = status; |
|
} |
|
} |
|
|
|
export class HTTPClientError extends HTTPError { |
|
constructor(status, data, ...params) { |
|
super(status, ...params); |
|
this.data = data; |
|
} |
|
} |
|
|
|
export async function handleJsonResponse(response){ |
|
if (response.status >= 400 && response.status < 500){ |
|
let data = await response.json(); |
|
throw new HTTPClientError(response.status, data, response.statusText); |
|
} |
|
|
|
if (!response.ok){ |
|
throw new HTTPError(response.status, response.statusText); |
|
} |
|
|
|
const responseContent = await response.json(); |
|
|
|
// Usually server send {"status": "OK"} inside JSON response. |
|
// But this attribute duplicate HTTP Status code so can stay ignored. |
|
// Besides not every API endpoint include {"status": "OK"} into JSON response |
|
// so let`s ignore it for simplicity. |
|
|
|
return responseContent; |
|
} |
|
|
|
export async function getJson(url, params={}) { |
|
// Can throw HTTPError, HTTPClientError or network errors |
|
|
|
// urlencode request params |
|
let [baseUrl, query=''] = url.split('?'); |
|
const queryParams = new URLSearchParams(query); |
|
const normalizedExtraParams = new URLSearchParams(params); |
|
let compositeParams = new URLSearchParams([ |
|
...queryParams.entries(), |
|
...normalizedExtraParams.entries(), |
|
]); |
|
|
|
let response = await fetch(baseUrl + '?' + compositeParams.toString(), { |
|
method: 'GET', |
|
credentials: 'include', // required by CORS during development on localhost, does not affect prod |
|
headers: { |
|
// Disabled because of conflict with simple CORS during development on localhost |
|
// https://learn.javascript.ru/fetch-crossorigin#prostye-zaprosy |
|
// 'X-Requested-With': 'XMLHttpRequest', // ask API for JSON response, not HTML |
|
'Accept': 'application/json', |
|
} |
|
}); |
|
|
|
return await handleJsonResponse(response); |
|
} |
|
|
|
export async function postJson(url, data={}, csrfToken=null) { |
|
// Can throw HTTPError, HTTPClientError or network errors |
|
|
|
csrfToken = csrfToken || window.csrftoken; |
|
|
|
let response = await fetch(url, { |
|
method: 'POST', |
|
credentials: 'include', // required by CORS during development on localhost, does not affect prod |
|
body: new URLSearchParams(data), |
|
headers: { |
|
'Content-Type': 'application/x-www-form-urlencoded', |
|
'Accept': 'application/json', |
|
'X-CSRFToken': csrfToken, // Django-specific code. See https://docs.djangoproject.com/en/3.2/ref/csrf/ |
|
} |
|
}); |
|
|
|
return await handleJsonResponse(response); |
|
} |