refactor: avoid leaking fetch internals by introducing TimeoutError
This commit is contained in:
parent
aa9085c263
commit
e1f5192f4b
7 changed files with 19 additions and 13 deletions
1
src/error/TimeoutError.ts
Normal file
1
src/error/TimeoutError.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export class TimeoutError extends Error {}
|
||||
|
|
@ -3,3 +3,4 @@ export * from './HttpError';
|
|||
export * from './NotFoundError';
|
||||
export * from './QueueIsFullError';
|
||||
export * from './TooManyRequestsError';
|
||||
export * from './TimeoutError';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import { Manifest, ManifestConfig } from 'stremio-addon-sdk';
|
||||
|
||||
export const TIMEOUT = 'timeout';
|
||||
|
||||
export interface Context {
|
||||
id: string;
|
||||
ip: string;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import winston from 'winston';
|
|||
import fetchMock from 'fetch-mock';
|
||||
import { Fetcher } from './Fetcher';
|
||||
import { Context } from '../types';
|
||||
import { BlockedError, HttpError, NotFoundError, QueueIsFullError, TooManyRequestsError } from '../error';
|
||||
import { BlockedError, HttpError, NotFoundError, QueueIsFullError, TimeoutError, TooManyRequestsError } from '../error';
|
||||
fetchMock.mockGlobal();
|
||||
|
||||
const fetcher = new Fetcher(winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] }));
|
||||
|
|
@ -182,7 +182,7 @@ describe('fetch', () => {
|
|||
test('times out', async () => {
|
||||
fetchMock.get('https://some-timeout-url.test/', 200, { delay: 20 });
|
||||
|
||||
await expect(fetcher.text(ctx, new URL('https://some-timeout-url.test/'), { timeout: 10 })).rejects.toBeInstanceOf(DOMException);
|
||||
await expect(fetcher.text(ctx, new URL('https://some-timeout-url.test/'), { timeout: 10 })).rejects.toBeInstanceOf(TimeoutError);
|
||||
});
|
||||
|
||||
test('queues requests and throws if queue is full', async () => {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import TTLCache from '@isaacs/ttlcache';
|
|||
import winston from 'winston';
|
||||
import { Semaphore, SemaphoreInterface, withTimeout } from 'async-mutex';
|
||||
import { Cookie, CookieJar } from 'tough-cookie';
|
||||
import { BlockedReason, Context, TIMEOUT } from '../types';
|
||||
import { BlockedError, HttpError, NotFoundError, QueueIsFullError, TooManyRequestsError } from '../error';
|
||||
import { BlockedReason, Context } from '../types';
|
||||
import { BlockedError, HttpError, NotFoundError, QueueIsFullError, TimeoutError, TooManyRequestsError } from '../error';
|
||||
import { clearTimeout } from 'node:timers';
|
||||
import { envGet } from './env';
|
||||
|
||||
|
|
@ -229,11 +229,17 @@ export class Fetcher {
|
|||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(TIMEOUT), init?.timeout ?? this.DEFAULT_TIMEOUT);
|
||||
const timer = setTimeout(() => controller.abort(), init?.timeout ?? this.DEFAULT_TIMEOUT);
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(url, { ...init, keepalive: true, signal: controller.signal });
|
||||
} catch (error) {
|
||||
if (error instanceof DOMException && error.name === 'AbortError') {
|
||||
throw new TimeoutError();
|
||||
}
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import winston from 'winston';
|
|||
import { createExtractors, Extractor, ExtractorRegistry } from '../extractor';
|
||||
import { StreamResolver } from './StreamResolver';
|
||||
import { Source, SourceResult, MeineCloud, MostraGuarda } from '../source';
|
||||
import { BlockedReason, Context, CountryCode, TIMEOUT, UrlResult } from '../types';
|
||||
import { BlockedError, HttpError, NotFoundError, QueueIsFullError, TooManyRequestsError } from '../error';
|
||||
import { BlockedReason, Context, CountryCode, UrlResult } from '../types';
|
||||
import { BlockedError, HttpError, NotFoundError, QueueIsFullError, TimeoutError, TooManyRequestsError } from '../error';
|
||||
import { ImdbId } from './id';
|
||||
import { FetcherMock } from './FetcherMock';
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ describe('resolve', () => {
|
|||
{
|
||||
url: new URL('https://example2.com'),
|
||||
isExternal: true,
|
||||
error: TIMEOUT,
|
||||
error: new TimeoutError(),
|
||||
label: 'hoster.com',
|
||||
sourceId: '',
|
||||
meta: {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { ContentType, Stream } from 'stremio-addon-sdk';
|
||||
import winston from 'winston';
|
||||
import bytes from 'bytes';
|
||||
import { Context, TIMEOUT, UrlResult } from '../types';
|
||||
import { Context, UrlResult } from '../types';
|
||||
import { Source } from '../source';
|
||||
import { BlockedError, HttpError, NotFoundError, QueueIsFullError, TooManyRequestsError } from '../error';
|
||||
import { BlockedError, HttpError, NotFoundError, QueueIsFullError, TimeoutError, TooManyRequestsError } from '../error';
|
||||
import { flagFromCountryCode, languageFromCountryCode } from './language';
|
||||
import { envGetAppName } from './env';
|
||||
import { Id } from './id';
|
||||
|
|
@ -169,7 +169,7 @@ export class StreamResolver {
|
|||
return '🚦 Request was rate-limited. Please try again later or consider self-hosting.';
|
||||
}
|
||||
|
||||
if (error === TIMEOUT) {
|
||||
if (error instanceof TimeoutError) {
|
||||
this.logger.warn(`${source}: Request timed out.`, ctx);
|
||||
|
||||
return '🐢 Request timed out.';
|
||||
|
|
|
|||
Loading…
Reference in a new issue