From b13cd98cb19e926839bb895147d9dced1090886e Mon Sep 17 00:00:00 2001 From: antoine Date: Wed, 21 Oct 2026 14:28:47 +0200 Subject: [PATCH] Compare only the MIME type of blob responses Blob.type holds the full Content-Type header, so a reply sent as "application/json; charset=utf-8" (e.g. on Laravel Cloud) was treated as a file and downloaded instead of being handled as a command response. Co-Authored-By: Claude Opus 5.5 --- resources/js/api/interceptors.ts | 6 +++--- resources/js/commands/CommandManager.ts | 4 ++-- resources/js/utils/request.ts | 7 +++++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/resources/js/api/interceptors.ts b/resources/js/api/interceptors.ts index 64be5115d..90247c5d1 100644 --- a/resources/js/api/interceptors.ts +++ b/resources/js/api/interceptors.ts @@ -1,4 +1,4 @@ -import { parseBlobJSONContent } from "@/utils/request"; +import { blobHasMimeType, parseBlobJSONContent } from "@/utils/request"; import { handleErrorAlert } from "./errors"; import { Axios, AxiosError, isCancel } from "axios"; import { CURRENT_PAGE_URL } from "@/api/headers"; @@ -50,9 +50,9 @@ export function installInterceptors(api: Axios) { const response = error.response; if(response.data instanceof Blob) { - if(response.data.type === 'application/json') { + if(blobHasMimeType(response.data, 'application/json')) { response.data = await parseBlobJSONContent(response.data); - } else if(response.data.type === 'text/html') { + } else if(blobHasMimeType(response.data, 'text/html')) { response.data = await response.data.text(); } } diff --git a/resources/js/commands/CommandManager.ts b/resources/js/commands/CommandManager.ts index 454737025..085e3ca81 100644 --- a/resources/js/commands/CommandManager.ts +++ b/resources/js/commands/CommandManager.ts @@ -1,7 +1,7 @@ import { CommandData, CommandFormData, CommandResponseData, FormData } from "@/types"; import { api } from "@/api/api"; import { RootAlertDialog, showAlert, showConfirm } from "@/utils/dialogs"; -import { parseBlobJSONContent } from "@/utils/request"; +import { blobHasMimeType, parseBlobJSONContent } from "@/utils/request"; import { AxiosResponse } from "axios"; import { reactive } from "vue"; import { __ } from "@/utils/i18n"; @@ -140,7 +140,7 @@ export class CommandManager { } async handleCommandApiResponse(response: AxiosResponse): Promise { - if(response.data.type !== 'application/json') { + if(!blobHasMimeType(response.data, 'application/json')) { const link = document.createElement("a"); link.download = (response.headers['content-disposition'] as string)?.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)?.[1] ?? 'download'; link.href = URL.createObjectURL(response.data); diff --git a/resources/js/utils/request.ts b/resources/js/utils/request.ts index cdadd4d18..88ec97930 100644 --- a/resources/js/utils/request.ts +++ b/resources/js/utils/request.ts @@ -10,6 +10,13 @@ export function parseBlobJSONContent(blob) { }); } +/** + * Compare only the MIME type, as Blob.type may include parameters (e.g. "application/json; charset=utf-8") + */ +export function blobHasMimeType(blob: Blob, mimeType: string) { + return blob.type.split(';')[0].trim().toLowerCase() === mimeType; +} + export function getCsrfToken() { return session('_token'); }