@beenotung/tslib
Version:
utils library in Typescript
136 lines (135 loc) • 4.42 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.fetch_retry_status_codes = void 0;
exports.checkedFetch = checkedFetch;
exports.parseRetryAfter = parseRetryAfter;
exports.fetch_with_retry = fetch_with_retry;
exports.fetch_json = fetch_json;
const error_1 = require("./error");
/**
* check if the status code is 2xx
* */
function checkedFetch({ input, init, on2xx, non2xx, }) {
return fetch(input, init).then(res => {
if (200 <= res.status && res.status < 300) {
return on2xx(res);
}
return non2xx(res);
});
}
function parseRetryAfter(retryAfter) {
if (!retryAfter) {
return null;
}
const seconds = +retryAfter;
if (seconds) {
return seconds * 1000;
}
const target = new Date(retryAfter).getTime();
if (target) {
return target - Date.now();
}
return null;
}
exports.fetch_retry_status_codes = [
// 429: Too Many Requests
429,
// 502: Bad Gateway
502,
// 503: Service Unavailable
503,
// 504: Gateway Timeout
504,
// 521: Web Server is Down (cloudflare)
521,
// 522: Connection Timed Out (cloudflare)
522,
// 523: Origin is unreachable (cloudflare)
523,
// 524: A Timeout Occurred (cloudflare)
524,
];
async function fetch_with_retry(input, init = {}, options = {}) {
for (let retryCount = 0;; retryCount++) {
const res = await fetch(input, init);
if (!exports.fetch_retry_status_codes.includes(res.status)) {
return res;
}
const retryAfter = parseRetryAfter(res.headers.get('Retry-After')) ||
options.defaultRetryAfterInterval;
if (!retryAfter ||
(options.maxRetryCount && retryCount > options.maxRetryCount)) {
throw new error_1.HttpError(res.status, res.statusText || 'Too Many Requests');
}
await new Promise(resolve => setTimeout(resolve, retryAfter));
}
}
async function fetch_json(input, init = {}, options) {
init.headers = new Headers(init.headers);
if (!init.headers.has('Accept')) {
init.headers.set('Accept', 'application/json');
}
const res = await fetch_with_retry(input, init, options);
const contentType = res.headers.get('Content-Type')?.split(';')[0];
if (contentType?.includes('json')) {
return res.json();
}
const text = await res.text();
if (contentType?.includes('html')) {
if (typeof DOMParser !== 'undefined') {
const doc = new DOMParser().parseFromString(text, 'text/html');
throw new error_1.HttpError(res.status, 'expect json response, but got html, content: ' +
preview_text(doc.body.innerText));
}
else {
const preview = preview_html(text);
const type = preview.type;
throw new error_1.HttpError(res.status, `expect json response, but got ${type}: ` + preview_text(preview.text));
}
}
if (contentType) {
throw new error_1.HttpError(res.status, `expect json response, but got ${contentType}: ` + preview_text(text));
}
else {
throw new error_1.HttpError(res.status, 'expect json response, but got: ' + preview_text(text));
}
}
// fallback for nodejs
function preview_html(text) {
let start = text.indexOf('<title');
if (start >= 0) {
start = text.indexOf('>', start) + 1;
const end = text.indexOf('</title>', start);
if (end > start) {
return { type: 'title', text: text.slice(start, end) };
}
}
start = text.indexOf('<h1');
if (start >= 0) {
start = text.indexOf('>', start) + 1;
const end = text.indexOf('</h1>', start);
if (end > start) {
return { type: 'title', text: text.slice(start, end) };
}
}
start = text.indexOf('<body');
if (start >= 0) {
start = text.indexOf('>', start) + 1;
const end = text.indexOf('</body>', start);
if (end > start) {
return { type: 'content', text: text.slice(start, end) };
}
}
return { type: 'text', text };
}
function preview_text(text) {
text = text.trim();
if (!text) {
return 'empty text';
}
const preview_length = 150;
if (text.length > preview_length) {
return text.slice(0, preview_length) + '...';
}
return text;
}