From caa8983dfc7cd355ef96d07b0190fe309af2e5d3 Mon Sep 17 00:00:00 2001 From: Parth Bandwal <143504541+parrth20@users.noreply.github.com> Date: Mon, 23 Feb 2026 18:26:34 +0530 Subject: [PATCH] fix(logs): add robust log endpoint fallback and html guard --- src/lib/api/logs.ts | 69 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/src/lib/api/logs.ts b/src/lib/api/logs.ts index 2d059ede..513ce3da 100644 --- a/src/lib/api/logs.ts +++ b/src/lib/api/logs.ts @@ -3,6 +3,24 @@ export interface AuthApiRequestContext { signal?: AbortSignal } +export class LogsApiError extends Error { + status?: number + code?: 'not_supported' | 'request_failed' + + constructor( + message: string, + options: { + status?: number + code?: 'not_supported' | 'request_failed' + } = {}, + ) { + super(message) + this.name = 'LogsApiError' + this.status = options.status + this.code = options.code + } +} + const buildAuthHeader = (token: string) => { return { 'x-jm-authorization': `Bearer ${token}` } } @@ -23,6 +41,9 @@ const withExpectedContentTypeOrThrow = (response: Response, expectedContentType: return response } +const LOG_ENDPOINT_PATHS = ['/api/v0/log', '/jam/api/v0/log'] as const +const LOG_FALLBACK_STATUSES = new Set([404, 405, 501]) + /** * Fetch features configuration from the server * @param token - Authentication token @@ -50,8 +71,48 @@ export const fetchLog = async ({ }: AuthApiRequestContext & { fileName: string }) => { - return await fetch(`/jam/api/v0/log/${fileName}`, { - headers: { ...buildAuthHeader(token) }, - signal, - }).then((response) => withExpectedContentTypeOrThrow(response, 'text/plain')) + const encodedFileName = encodeURIComponent(fileName) + let fallbackError: LogsApiError | undefined + + for (const path of LOG_ENDPOINT_PATHS) { + const response = await fetch(`${path}/${encodedFileName}`, { + headers: { ...buildAuthHeader(token) }, + signal, + }) + + if (response.ok) { + const contentType = response.headers.get('content-type')?.toLowerCase() || '' + const body = await response.text() + const bodyStart = body.trimStart().slice(0, 64).toLowerCase() + const isHtmlResponse = + contentType.includes('text/html') || bodyStart.startsWith('