(() => { class LumiRequestError extends Error { constructor(message, details = {}) { super(message || "The request could not be completed."); this.name = "LumiRequestError"; this.kind = details.kind || "server"; this.status = details.status || 0; this.fieldErrors = details.fieldErrors || {}; this.data = details.data || {}; } } const enhancedHeaders = { Accept: "application/json, text/html;q=0.8", "X-Lumi-Enhanced": "1", "X-Requested-With": "XMLHttpRequest" }; function normalize(payload, response) { if (!payload || typeof payload !== "object" || Array.isArray(payload)) { return { ok: response.ok, message: response.ok ? "Saved." : "The request could not be completed.", fieldErrors: {}, data: {}, patch: {}, redirect: null, reloadRequired: false }; } const explicitOk = payload.ok ?? payload.success; return { ...payload, ok: explicitOk === undefined ? response.ok : Boolean(explicitOk), message: payload.message || payload.error || (response.ok ? "Saved." : "The request could not be completed."), fieldErrors: payload.fieldErrors && typeof payload.fieldErrors === "object" ? payload.fieldErrors : {}, data: payload.data && typeof payload.data === "object" ? payload.data : {}, patch: payload.patch && typeof payload.patch === "object" ? payload.patch : {}, redirect: payload.redirect || null, reloadRequired: payload.reloadRequired === true }; } async function parseResponse(response) { const contentType = String(response.headers.get("content-type") || "").toLowerCase(); if (contentType.includes("application/json")) { return normalize(await response.json().catch(() => null), response); } const html = await response.text().catch(() => ""); if (response.ok) return normalize(null, response); const parsed = new DOMParser().parseFromString(html, "text/html"); const message = parsed.querySelector("[role='alert'], .flash.error, .error-message, main h1 + p")?.textContent?.trim(); return normalize({ ok: false, message: message || `The server returned ${response.status}.` }, response); } async function request(url, options = {}) { const headers = new Headers(options.headers || {}); Object.entries(enhancedHeaders).forEach(([name, value]) => { if (!headers.has(name)) headers.set(name, value); }); try { const response = await fetch(url, { credentials: "same-origin", redirect: "follow", ...options, headers }); const result = await parseResponse(response); if (!response.ok || !result.ok) { throw new LumiRequestError(result.message, { kind: response.status === 401 || response.status === 403 ? "permission" : response.status === 422 || response.status === 400 ? "validation" : "server", status: response.status, fieldErrors: result.fieldErrors, data: result.data }); } return result; } catch (error) { if (error instanceof LumiRequestError || error?.name === "AbortError") throw error; throw new LumiRequestError("Lumi could not be reached. Your changes are still in this page; check the connection and try again.", { kind: "network" }); } } function formRequest(form, options = {}) { const submitter = options.submitter || null; const method = String(submitter?.getAttribute?.("formmethod") || form.getAttribute("method") || "POST").toUpperCase(); const url = submitter?.getAttribute?.("formaction") || form.getAttribute("action") || window.location.href; const formData = submitter ? new FormData(form, submitter) : new FormData(form); const hasFile = Array.from(formData.values()).some((value) => value instanceof File && value.size > 0); let body = formData; const headers = new Headers(options.headers || {}); if (!hasFile && method !== "GET") { body = new URLSearchParams(); for (const [name, value] of formData.entries()) { if (typeof value === "string") body.append(name, value); } headers.set("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); } if (method === "GET") { const next = new URL(url, window.location.href); for (const [name, value] of formData.entries()) { if (typeof value === "string") next.searchParams.append(name, value); } return request(next.href, { ...options, method, body: undefined, headers }); } return request(url, { ...options, method, body, headers }); } window.LumiRequest = Object.freeze({ request, form: formRequest, Error: LumiRequestError }); })();