This commit is contained in:
seb
2026-10-09 16:44:18 +02:00
parent 56274ecb1d
commit 5e62be468f
9 changed files with 175 additions and 71 deletions

View File

@@ -21,25 +21,39 @@ function readableHeaders(headers) {
return out;
}
const SSE_CLIENT_ID_KEY = 'sse.cid';
const CLIENT_ID_HEADER = 'X-Client-Id';
function getStoredClientId() {
try {
if (typeof window === 'undefined') return null;
return window.sessionStorage.getItem(SSE_CLIENT_ID_KEY);
} catch {
return null;
}
}
function clientIdHeaders() {
const cid = getStoredClientId();
return cid ? { [CLIENT_ID_HEADER]: cid } : {};
}
function isMutatingMethod(method) {
const upper = String(method || 'GET').toUpperCase();
return upper !== 'GET' && upper !== 'HEAD';
}
async function http(path, { method = 'GET', body } = {}) {
const isFormData = typeof FormData !== 'undefined' && body instanceof FormData;
const headers = {};
if (!isFormData) {
headers['Content-Type'] = 'application/json';
}
if (isMutatingMethod(method)) {
Object.assign(headers, clientIdHeaders());
}
const url = new URL(path, window.location.origin).href;
logApi('request', {
method,
path,
url,
origin: window.location.origin,
credentials: 'include',
// HttpOnly cookies are invisible here; this only shows JS-readable cookies.
documentCookie: document.cookie || '(empty)',
hasBody: Boolean(body),
});
const res = await fetch(path, {
method,
headers,
@@ -47,18 +61,6 @@ async function http(path, { method = 'GET', body } = {}) {
body: isFormData ? body : (body ? JSON.stringify(body) : undefined),
});
const responseHeaders = readableHeaders(res.headers);
// Browsers hide Set-Cookie from JS; if it's missing here that proves nothing.
logApi('response', {
method,
path,
status: res.status,
statusText: res.statusText,
ok: res.ok,
responseHeaders,
documentCookieAfter: document.cookie || '(empty)',
});
if (!res.ok) {
let message = res.statusText;
let data;
@@ -78,12 +80,6 @@ async function http(path, { method = 'GET', body } = {}) {
}
if (res.status === 204) return null;
const json = await res.json();
logApi('ok-body', {
method,
path,
keys: json && typeof json === 'object' ? Object.keys(json) : typeof json,
hasUser: Boolean(json?.user),
});
return json;
}
@@ -102,7 +98,7 @@ async function httpBinary(path, { method = 'POST', body, contentType, headers: e
const res = await fetch(path, {
method,
headers: { 'Content-Type': contentType, ...(extraHeaders || {}) },
headers: { 'Content-Type': contentType, ...clientIdHeaders(), ...(extraHeaders || {}) },
credentials: 'include',
body,
});
@@ -851,13 +847,22 @@ export const brand = {
},
/**
* PUT /api/brand
* Body: { name, gsprpinfo? } — upsert by name for the selected company.
* Same name ⇒ update, otherwise create. Empty name ⇒ 400 Invalid brand.
* → 200 { brand: Brand }
* POST /api/brand
* Body: { name, gsprpinfo? } — insert for the selected company.
* Empty name ⇒ 400 Invalid brand.
* → 201 { brand: Brand } · 409 Brand already exists
*/
upsert(body) {
return http('/api/brand', { method: 'PUT', body });
create(body) {
return http('/api/brand', { method: 'POST', body });
},
/**
* PATCH /api/brand/:id
* Body: { name, gsprpinfo? } — rename / update info.
* → 200 { id, name, gsprpinfo, touchedArticleCount } · 404 Brand not found
*/
rename(id, body) {
return http(`/api/brand/${encodeURIComponent(id)}`, { method: 'PATCH', body });
},
/**
@@ -1170,6 +1175,7 @@ export const delivery = {
formData.append('file', file, file.name);
const res = await fetch('/api/delivery/parse', {
method: 'POST',
headers: { ...clientIdHeaders() },
credentials: 'include',
body: formData,
signal,
@@ -1264,6 +1270,7 @@ export const serper = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...clientIdHeaders(),
},
credentials: 'include',
body: JSON.stringify({ q, gl, hl, num, preferSource }),
@@ -1355,6 +1362,7 @@ export const serper = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...clientIdHeaders(),
},
credentials: 'include',
body: JSON.stringify({ url }),
@@ -2165,6 +2173,7 @@ export const vfs = {
}
const res = await fetch('/api/vfs/upload', {
method: 'POST',
headers: { ...clientIdHeaders() },
credentials: 'include',
body: formData,
});