From cc3d30be2fbd234597b1019ce3fe7313b39cf314 Mon Sep 17 00:00:00 2001 From: WebStreamr <210764791+webstreamr@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:26:54 +0000 Subject: [PATCH] chore: add host info of relevant URL to all errors --- src/error/HttpError.ts | 4 ++- src/error/QueueIsFullError.ts | 10 +++++- src/error/TimeoutError.ts | 10 +++++- src/error/TooManyRequestsError.ts | 4 ++- src/error/TooManyTimeoutsError.ts | 10 +++++- src/error/index.ts | 34 +++++++++++-------- src/utils/Fetcher.ts | 12 +++---- src/utils/StreamResolver.test.ts | 12 +++---- .../__snapshots__/StreamResolver.test.ts.snap | 18 +++++----- 9 files changed, 73 insertions(+), 41 deletions(-) diff --git a/src/error/HttpError.ts b/src/error/HttpError.ts index 28eebe4..8221f44 100644 --- a/src/error/HttpError.ts +++ b/src/error/HttpError.ts @@ -1,11 +1,13 @@ export class HttpError extends Error { + public readonly url: URL; public readonly status: number; public readonly statusText: string; public readonly headers: Record; - public constructor(status: number, statusText: string, headers: Record) { + public constructor(url: URL, status: number, statusText: string, headers: Record) { super(); + this.url = url; this.status = status; this.statusText = statusText; this.headers = headers; diff --git a/src/error/QueueIsFullError.ts b/src/error/QueueIsFullError.ts index 5e5cbf7..de7cab8 100644 --- a/src/error/QueueIsFullError.ts +++ b/src/error/QueueIsFullError.ts @@ -1 +1,9 @@ -export class QueueIsFullError extends Error {} +export class QueueIsFullError extends Error { + public readonly url: URL; + + public constructor(url: URL) { + super(); + + this.url = url; + } +} diff --git a/src/error/TimeoutError.ts b/src/error/TimeoutError.ts index 18f20e4..f230d08 100644 --- a/src/error/TimeoutError.ts +++ b/src/error/TimeoutError.ts @@ -1 +1,9 @@ -export class TimeoutError extends Error {} +export class TimeoutError extends Error { + public readonly url: URL; + + public constructor(url: URL) { + super(); + + this.url = url; + } +} diff --git a/src/error/TooManyRequestsError.ts b/src/error/TooManyRequestsError.ts index d9269ef..f9f84d0 100644 --- a/src/error/TooManyRequestsError.ts +++ b/src/error/TooManyRequestsError.ts @@ -1,9 +1,11 @@ export class TooManyRequestsError extends Error { + public readonly url: URL; public readonly retryAfter: number; - public constructor(retryAfter: number) { + public constructor(url: URL, retryAfter: number) { super(); + this.url = url; this.retryAfter = retryAfter; } } diff --git a/src/error/TooManyTimeoutsError.ts b/src/error/TooManyTimeoutsError.ts index 1dff207..7770aef 100644 --- a/src/error/TooManyTimeoutsError.ts +++ b/src/error/TooManyTimeoutsError.ts @@ -1 +1,9 @@ -export class TooManyTimeoutsError extends Error {} +export class TooManyTimeoutsError extends Error { + public readonly url: URL; + + public constructor(url: URL) { + super(); + + this.url = url; + } +} diff --git a/src/error/index.ts b/src/error/index.ts index 119ecbe..d93720a 100644 --- a/src/error/index.ts +++ b/src/error/index.ts @@ -21,46 +21,50 @@ export const logErrorAndReturnNiceString = (ctx: Context, logger: winston.Logger return '⚠️ MediaFlow Proxy authentication failed. Please set the correct password.'; } - logger.warn(`${source}: Request to ${error.url.href} was blocked, reason: ${error.reason}, headers: ${JSON.stringify(error.headers)}.`, ctx); + logger.warn(`${source}: Request to ${error.url} was blocked, reason: ${error.reason}, headers: ${JSON.stringify(error.headers)}.`, ctx); - return `⚠️ Request was blocked. Reason: ${error.reason}`; + return `⚠️ Request to ${error.url.host} was blocked. Reason: ${error.reason}`; } if (error instanceof TooManyRequestsError) { - logger.warn(`${source}: Rate limited for ${error.retryAfter} seconds.`, ctx); + logger.warn(`${source}: Request to ${error.url} was rate limited for ${error.retryAfter} seconds.`, ctx); - return '🚦 Request was rate-limited. Please try again later or consider self-hosting.'; + return `🚦 Request to ${error.url.host} was rate-limited. Please try again later or consider self-hosting.`; } if (error instanceof TooManyTimeoutsError) { - logger.warn(`${source}: Too many timeouts.`, ctx); + logger.warn(`${source}: Too many timeouts when requesting ${error.url}.`, ctx); - return '🚦 Too many recent timeouts. Please try again later.'; + return `🚦 Too many recent timeouts when requesting ${error.url.host}. Please try again later.`; } - if ( - error instanceof TimeoutError - || (error instanceof DOMException && ['AbortError', 'TimeoutError'].includes(error.name)) // sometimes this gets through, no idea why.. - ) { + if (error instanceof TimeoutError) { + logger.warn(`${source}: Request to ${error.url} timed out.`, ctx); + + return `🐢 Request to ${error.url.host} timed out.`; + } + + if (error instanceof DOMException && ['AbortError', 'TimeoutError'].includes(error.name)) { + // sometimes this gets through, no idea why.. logger.warn(`${source}: Request timed out.`, ctx); return '🐢 Request timed out.'; } if (error instanceof QueueIsFullError) { - logger.warn(`${source}: Request queue is full.`, ctx); + logger.warn(`${source}: Request queue for ${error.url.host} is full.`, ctx); - return '⏳ Request queue is full. Please try again later or consider self-hosting.'; + return `⏳ Request queue for ${error.url.host} is full. Please try again later or consider self-hosting.`; } if (error instanceof HttpError) { - logger.error(`${source}: HTTP status ${error.status} (${error.statusText}), headers: ${JSON.stringify(error.headers)}, stack: ${error.stack}.`, ctx); + logger.error(`${source}: Error when requesting url ${error.url}, HTTP status ${error.status} (${error.statusText}), headers: ${JSON.stringify(error.headers)}, stack: ${error.stack}.`, ctx); if (error.status >= 500) { - return `❌ Remote server has issues. We can't fix this, please try later again.`; + return `❌ Remote server ${error.url.host} has issues. We can't fix this, please try later again.`; } - return `❌ Request failed with status ${error.status} (${error.statusText}). Request-id: ${ctx.id}.`; + return `❌ Request to ${error.url.host} failed with status ${error.status} (${error.statusText}). Request-id: ${ctx.id}.`; } const cause = (error as Error & { cause?: unknown }).cause; diff --git a/src/utils/Fetcher.ts b/src/utils/Fetcher.ts index 84c38de..057fc86 100644 --- a/src/utils/Fetcher.ts +++ b/src/utils/Fetcher.ts @@ -213,10 +213,10 @@ export class Fetcher { await this.rateLimitedCache.set(url.host, true, retryAfter * 1000); } - throw new TooManyRequestsError(retryAfter); + throw new TooManyRequestsError(url, retryAfter); } - throw new HttpError(httpCacheItem.status, httpCacheItem.statusText, httpCacheItem.headers); + throw new HttpError(url, httpCacheItem.status, httpCacheItem.statusText, httpCacheItem.headers); }; private determineCacheKey(url: URL, init?: CustomRequestInit): string { @@ -308,12 +308,12 @@ export class Fetcher { return await this.fetchWithTimeout(ctx, url, { ...init, queueLimit: 1 }, ++tryCount); } - throw new TooManyRequestsError((isRateLimitedRaw.expires as number - Date.now()) / 1000); + throw new TooManyRequestsError(url, (isRateLimitedRaw.expires as number - Date.now()) / 1000); } const timeouts = (await this.timeoutsCountCache.get(url.host)) ?? 0; if (timeouts >= (init?.timeoutsCountThrow ?? this.DEFAULT_TIMEOUTS_COUNT_THROW)) { - throw new TooManyTimeoutsError(); + throw new TooManyTimeoutsError(url); } let response; @@ -335,7 +335,7 @@ export class Fetcher { if (error instanceof DOMException && ['AbortError', 'TimeoutError'].includes(error.name)) { await this.increaseTimeoutsCount(url); - throw new TimeoutError(); + throw new TimeoutError(url); } if (tryCount < 3) { @@ -393,7 +393,7 @@ export class Fetcher { let sem = this.semaphores.get(url.host); if (!sem) { - sem = withTimeout(new Semaphore(queueLimit), queueTimeout, new QueueIsFullError()); + sem = withTimeout(new Semaphore(queueLimit), queueTimeout, new QueueIsFullError(url)); this.semaphores.set(url.host, sem); } diff --git a/src/utils/StreamResolver.test.ts b/src/utils/StreamResolver.test.ts index c6a371a..5043925 100644 --- a/src/utils/StreamResolver.test.ts +++ b/src/utils/StreamResolver.test.ts @@ -153,7 +153,7 @@ describe('resolve', () => { url: new URL('https://example.com'), format: Format.unknown, isExternal: true, - error: new TooManyRequestsError(10), + error: new TooManyRequestsError(new URL('https://example.com'), 10), label: 'hoster.com', sourceId: '', meta: { @@ -164,7 +164,7 @@ describe('resolve', () => { url: new URL('https://example.com'), format: Format.unknown, isExternal: true, - error: new TooManyTimeoutsError(), + error: new TooManyTimeoutsError(new URL('https://example.com')), label: 'hoster.com', sourceId: '', meta: { @@ -195,7 +195,7 @@ describe('resolve', () => { url: new URL('https://example2.com'), format: Format.unknown, isExternal: true, - error: new TimeoutError(), + error: new TimeoutError(new URL('https://example2.com')), label: 'hoster.com', sourceId: '', meta: { @@ -206,7 +206,7 @@ describe('resolve', () => { url: new URL('https://example3.com'), format: Format.unknown, isExternal: true, - error: new QueueIsFullError(), + error: new QueueIsFullError(new URL('https://example3.com')), label: 'hoster.com', sourceId: '', meta: { @@ -217,7 +217,7 @@ describe('resolve', () => { url: new URL('https://example4.com'), format: Format.unknown, isExternal: true, - error: new HttpError(500, 'Internal Server Error', { 'x-foo': 'bar' }), + error: new HttpError(new URL('https://example4.com'), 500, 'Internal Server Error', { 'x-foo': 'bar' }), label: 'hoster.com', sourceId: '', meta: { @@ -228,7 +228,7 @@ describe('resolve', () => { url: new URL('https://example5.com'), format: Format.unknown, isExternal: true, - error: new HttpError(418, 'I\'m a tea pot', { 'x-foo': 'bar' }), + error: new HttpError(new URL('https://example5.com'), 418, 'I\'m a tea pot', { 'x-foo': 'bar' }), label: 'hoster.com', sourceId: '', meta: { diff --git a/src/utils/__snapshots__/StreamResolver.test.ts.snap b/src/utils/__snapshots__/StreamResolver.test.ts.snap index fde312e..d87600d 100644 --- a/src/utils/__snapshots__/StreamResolver.test.ts.snap +++ b/src/utils/__snapshots__/StreamResolver.test.ts.snap @@ -35,7 +35,7 @@ exports[`resolve adds error info 2`] = ` "externalUrl": "https://example5.com/", "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com -❌ Request failed with status 418 (I'm a tea pot). Request-id: test.", +❌ Request to example5.com failed with status 418 (I'm a tea pot). Request-id: test.", }, { "behaviorHints": { @@ -44,7 +44,7 @@ exports[`resolve adds error info 2`] = ` "externalUrl": "https://example4.com/", "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com -❌ Remote server has issues. We can't fix this, please try later again.", +❌ Remote server example4.com has issues. We can't fix this, please try later again.", }, { "behaviorHints": { @@ -53,7 +53,7 @@ exports[`resolve adds error info 2`] = ` "externalUrl": "https://example3.com/", "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com -⏳ Request queue is full. Please try again later or consider self-hosting.", +⏳ Request queue for example3.com is full. Please try again later or consider self-hosting.", }, { "behaviorHints": { @@ -62,7 +62,7 @@ exports[`resolve adds error info 2`] = ` "externalUrl": "https://example2.com/", "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com -🐢 Request timed out.", +🐢 Request to example2.com timed out.", }, { "behaviorHints": { @@ -80,7 +80,7 @@ exports[`resolve adds error info 2`] = ` "externalUrl": "https://example.com/", "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com -🚦 Too many recent timeouts. Please try again later.", +🚦 Too many recent timeouts when requesting example.com. Please try again later.", }, { "behaviorHints": { @@ -89,7 +89,7 @@ exports[`resolve adds error info 2`] = ` "externalUrl": "https://example.com/", "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com -🚦 Request was rate-limited. Please try again later or consider self-hosting.", +🚦 Request to example.com was rate-limited. Please try again later or consider self-hosting.", }, { "behaviorHints": { @@ -98,7 +98,7 @@ exports[`resolve adds error info 2`] = ` "externalUrl": "https://example.com/", "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com -⚠️ Request was blocked. Reason: unknown", +⚠️ Request to example.com was blocked. Reason: unknown", }, { "behaviorHints": { @@ -116,7 +116,7 @@ exports[`resolve adds error info 2`] = ` "externalUrl": "https://example.com/", "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com -⚠️ Request was blocked. Reason: cloudflare_censor", +⚠️ Request to example.com was blocked. Reason: cloudflare_censor", }, { "behaviorHints": { @@ -125,7 +125,7 @@ exports[`resolve adds error info 2`] = ` "externalUrl": "https://example.com/", "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com -⚠️ Request was blocked. Reason: cloudflare_challenge", +⚠️ Request to example.com was blocked. Reason: cloudflare_challenge", }, { "behaviorHints": {