import { AxiosResponse } from "axios"; /** * Скачивание бинарного ответа API одним способом на всю панель. * * Раньше эта последовательность — Blob, createObjectURL, временная ссылка, * клик, revokeObjectURL — была скопирована в трёх местах, и все три копии * успели разойтись: страница пиров разбирала Content-Disposition с запасным * именем файла, а обе страницы логов падали на `undefined.split(...)`, если * заголовка не оказалось. * * Хуже расхождения был порядок на страницах логов: * * const response = await exportLogApi(...); // <- запрос ВНЕ try * try { ... } catch (e) { } // <- и пустой catch * * Сетевой запрос стоял ПЕРЕД try, поэтому его отказ этим catch не ловился * вовсе, а всё, что происходило внутри, глушилось молча. Оператор нажимал * «Экспорт» и не получал ни файла, ни объяснения. */ /** Имя файла по умолчанию, если сервер его не назвал. */ const CONTENT_DISPOSITION_PREFIX = "attachment; filename="; /** * Имя файла из заголовка ответа. * * Отсутствующий или незнакомый заголовок — не отказ: у скачивания есть * разумное запасное имя, и терять из-за формата заголовка уже полученный файл * незачем. */ export function fileNameFromResponse( response: AxiosResponse, fallback: string ): string { const header = response.headers?.["content-disposition"]; if (typeof header !== "string") { return fallback; } const index = header.indexOf(CONTENT_DISPOSITION_PREFIX); if (index < 0) { return fallback; } const name = header.slice(index + CONTENT_DISPOSITION_PREFIX.length).trim(); return name === "" ? fallback : name; } /** * Отдаёт бинарный ответ пользователю как файл. * * Временная ссылка УДАЛЯЕТСЯ из документа, а objectURL освобождается в * `finally`. Прежние копии оставляли `` в body навсегда — по одному * элементу на каждое нажатие «Экспорт» за всю жизнь вкладки. */ export function saveResponseAsFile( response: AxiosResponse, fallbackFileName: string ): void { const blob = new Blob([response.data], { type: "application/octet-stream" }); const url = window.URL.createObjectURL(blob); const anchor = document.createElement("a"); try { anchor.href = url; anchor.download = fileNameFromResponse(response, fallbackFileName); anchor.style.display = "none"; document.body.appendChild(anchor); anchor.click(); } finally { anchor.remove(); window.URL.revokeObjectURL(url); } }