From 5356096349ff73f1777575ca0f19f74d19f15606 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Fri, 29 May 2026 16:40:42 +0300 Subject: [PATCH] fix(api): auto-stringify object bodies in LS.api (apiFetch) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LS.api was passing raw object bodies straight to fetch(), which coerces them to '[object Object]' — the server then parsed empty JSON and 400'd on missing fields. This silently broke every POST that uses LS.api directly (EP.api.startMock, saveAttempt, mockAnswer, etc.). LS.post already stringified, so most call sites worked. Now apiFetch mirrors that behavior for plain objects, while FormData / Blob / URLSearchParams / ArrayBuffer / strings still pass through unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) --- js/api.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/js/api.js b/js/api.js index b56fed8..ad6711a 100644 --- a/js/api.js +++ b/js/api.js @@ -901,7 +901,17 @@ async function apiFetch(path, options = {}) { const token = getToken(); const headers = { 'Content-Type': 'application/json', ...(options.headers || {}) }; if (token) headers['Authorization'] = `Bearer ${token}`; - const res = await fetch(path, { ...options, headers }); + // Auto-stringify plain object bodies so callers can pass `{ body: { ... } }` + // like LS.post does. Strings / FormData / Blob / URLSearchParams pass through. + const opts = { ...options, headers }; + if (opts.body && typeof opts.body === 'object' + && !(opts.body instanceof FormData) + && !(opts.body instanceof Blob) + && !(opts.body instanceof URLSearchParams) + && !(opts.body instanceof ArrayBuffer)) { + opts.body = JSON.stringify(opts.body); + } + const res = await fetch(path, opts); if (res.status === 401) { removeToken(); removeUser(); window.location.href = '/login';