123 lines
4.1 KiB
JavaScript
123 lines
4.1 KiB
JavaScript
|
import {
|
||
|
SsrHttpClientError,
|
||
|
SsrHttpNotFoundError,
|
||
|
SsrHttpRateLimitError,
|
||
|
SsrHttpResponseError,
|
||
|
SsrHttpServerError, SsrHttpUnauthenticatedError,
|
||
|
SsrHttpUnauthorizedError,
|
||
|
SsrHttpUnprocessableEntityError,
|
||
|
SsrNetworkError,
|
||
|
} from './errors'
|
||
|
import {SsrDataFormatError} from '../others/errors'
|
||
|
import {parseRateLimitHeaders} from './utils'
|
||
|
import {MINUTE} from '../utils/date'
|
||
|
import createNetworkCache from './cache';
|
||
|
|
||
|
const networkCache = createNetworkCache();
|
||
|
|
||
|
const checkResponse = response => {
|
||
|
if (response.ok) {
|
||
|
// response.status >= 200 && response.status < 300
|
||
|
return response;
|
||
|
}
|
||
|
|
||
|
switch (true) {
|
||
|
case response.status === 401:
|
||
|
throw new SsrHttpUnauthenticatedError(response);
|
||
|
case response.status === 403:
|
||
|
throw new SsrHttpUnauthorizedError(response);
|
||
|
case response.status === 404:
|
||
|
throw new SsrHttpNotFoundError(response);
|
||
|
case response.status === 422:
|
||
|
throw new SsrHttpUnprocessableEntityError(response);
|
||
|
case response.status === 429:
|
||
|
throw new SsrHttpRateLimitError(response);
|
||
|
case response.status >= 400 && response.status < 500:
|
||
|
throw new SsrHttpClientError(response);
|
||
|
case response.status >= 500:
|
||
|
throw new SsrHttpServerError(response);
|
||
|
default:
|
||
|
throw new SsrHttpResponseError(response);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const getOptionsWithCacheKey = (url, options, cacheType = null) => {
|
||
|
if (options && options.cacheKey) {
|
||
|
if (!options.cacheTtl) options.cacheTtl = MINUTE;
|
||
|
|
||
|
return options;
|
||
|
}
|
||
|
|
||
|
if (options && options.method && options.method.toLowerCase() !== 'get') {
|
||
|
delete options.cacheKey;
|
||
|
|
||
|
delete options.cacheTtl;
|
||
|
|
||
|
return options;
|
||
|
}
|
||
|
|
||
|
const newOptions = options ? {...options} : {};
|
||
|
|
||
|
if (!newOptions || !newOptions.hasOwnProperty('cacheTtl')) {
|
||
|
newOptions.cacheTtl = MINUTE;
|
||
|
}
|
||
|
|
||
|
return {...newOptions, cacheKey: `${cacheType ? cacheType + ':' : ''}${url}`};
|
||
|
}
|
||
|
|
||
|
const setCacheIfNeeded = (response, cacheKey, cacheTtl) => {
|
||
|
if (cacheKey && cacheTtl) networkCache.set(cacheKey, response, cacheTtl);
|
||
|
|
||
|
return {...response, cached: false};
|
||
|
}
|
||
|
|
||
|
export async function fetchUrl(url, options = {}, cors = true) {
|
||
|
try {
|
||
|
const response = await fetch(url, {...options, ...(cors ? {mode: 'cors'} : null)});
|
||
|
|
||
|
return checkResponse(response);
|
||
|
} catch (err) {
|
||
|
if (err instanceof TypeError) throw new SsrNetworkError('Network error');
|
||
|
|
||
|
throw err;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export async function fetchJson(url, {cacheTtl = null, maxAge = null, ...restOptions} = {}) {
|
||
|
const options = getOptionsWithCacheKey(url, {cacheTtl, maxAge, ...restOptions}, 'json');
|
||
|
|
||
|
const {cacheKey: fetchCacheKey, cacheTtl: fetchCacheTtl, maxAge: fetchMaxAge, ...fetchOptions} = getOptionsWithCacheKey(url, options, 'json');
|
||
|
|
||
|
if (fetchCacheKey && fetchCacheTtl) {
|
||
|
const cachedResponse = networkCache.get(fetchCacheKey, fetchMaxAge);
|
||
|
if (cachedResponse !== undefined) return {...cachedResponse, cached: true};
|
||
|
}
|
||
|
|
||
|
return fetchUrl(url, fetchOptions)
|
||
|
.then(async response => {
|
||
|
const body = await response.json();
|
||
|
|
||
|
return setCacheIfNeeded({headers: response.headers, rateLimit: parseRateLimitHeaders(response), body}, fetchCacheKey, fetchCacheTtl);
|
||
|
})
|
||
|
.catch(err => {
|
||
|
throw (err instanceof SyntaxError ? new SsrDataFormatError('JSON parse error', err) : err);
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function fetchHtml(url, {cacheTtl = null, maxAge = null, ...restOptions} = {}) {
|
||
|
const options = getOptionsWithCacheKey(url, {cacheTtl, maxAge, ...restOptions}, 'json');
|
||
|
|
||
|
const {cacheKey: fetchCacheKey, cacheTtl: fetchCacheTtl, maxAge: fetchMaxAge, ...fetchOptions} = getOptionsWithCacheKey(url, options, 'html');
|
||
|
|
||
|
if (fetchCacheKey && fetchCacheTtl) {
|
||
|
const cachedResponse = networkCache.get(fetchCacheKey, fetchMaxAge);
|
||
|
if (cachedResponse !== undefined) return {...cachedResponse, cached: true};
|
||
|
}
|
||
|
|
||
|
return fetchUrl(url, fetchOptions)
|
||
|
.then(async response => {
|
||
|
const body = await response.text();
|
||
|
|
||
|
return setCacheIfNeeded({headers: response.headers, rateLimit: parseRateLimitHeaders(response), body: new DOMParser().parseFromString(body, 'text/html')}, fetchCacheKey, fetchCacheTtl);
|
||
|
})
|
||
|
}
|