diff --git a/src/interfaces/http/controllers/file-controller.ts b/src/interfaces/http/controllers/file-controller.ts index 972c15a..0e7f830 100644 --- a/src/interfaces/http/controllers/file-controller.ts +++ b/src/interfaces/http/controllers/file-controller.ts @@ -106,7 +106,12 @@ export const handleFileRedirect = async (req: RequestWithParams): Promise = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Expose-Headers': 'Content-Disposition, Content-Length, Accept-Ranges', + 'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS', + 'Access-Control-Allow-Headers': 'Range, Content-Type', + Vary: 'Origin', + }; + + try { + const upstream = await fetch(telegramUrl); + if (!upstream.ok) { + logger.error('Telegram file download failed', { + publicId, + status: upstream.status, + }); + return Response.json( + { error: 'Upstream download failed' }, + { + status: upstream.status === 404 ? 404 : 502, + headers: corsHeaders, + }, + ); + } + + const upstreamHeaders = new Headers(upstream.headers); + const contentType = + file.mimeType || upstreamHeaders.get('content-type') || 'application/octet-stream'; + const headers = { + 'Content-Type': contentType, + 'Content-Disposition': `inline; filename="${sanitizeFilenameHeader(file.fileName)}"`, + 'Content-Length': String(file.sizeBytes ?? 0), + 'Cache-Control': 'public, max-age=300', + ...corsHeaders, + }; + + // Stream the Telegram CDN body back to the client (no cross-origin hop + // in the browser → no CORS block for fetch/XHR audio playback). + return new Response(upstream.body, { + status: 200, + headers, + }); + } catch (error: unknown) { + logger.error('Telegram file proxy error', { + publicId, + error: getErrorMessage(error), + }); + return Response.json( + { error: 'Upstream download failed' }, + { + status: 502, + headers: corsHeaders, + }, + ); + } } catch (error: unknown) { logger.error('File redirect error', { publicId, error: getErrorMessage(error) }); return fail(500, 'Server error');