frontend fixed and good

This commit is contained in:
HotSwapp
2025-08-11 10:26:41 -05:00
parent 1512b2d12a
commit 88501a8891
10 changed files with 616 additions and 223 deletions

131
static/js/fetch-wrapper.js Normal file
View File

@@ -0,0 +1,131 @@
/**
* Lightweight fetch wrapper that:
* - Adds an X-Correlation-ID header to every request
* - Exposes helpers to parse the standard error envelope
* - Provides helpers to build user-facing error messages with correlation ID
*/
(function () {
// Ensure global app object exists
window.app = window.app || {};
const CORRELATION_HEADER = 'X-Correlation-ID';
function generateCorrelationId() {
try {
if (window.crypto && typeof window.crypto.randomUUID === 'function') {
return window.crypto.randomUUID();
}
} catch (_) {}
// Fallback RFC4122-ish UUID
const s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).slice(1);
return (
s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4()
);
}
function normalizeHeaders(input) {
if (!input) return new Headers();
return input instanceof Headers ? input : new Headers(input);
}
const originalFetch = window.fetch.bind(window);
async function wrappedFetch(resource, options = {}) {
const url = typeof resource === 'string' ? resource : (resource && resource.url) || '';
const headers = normalizeHeaders(options.headers);
// Inject correlation id if not present
let outgoingCid = headers.get(CORRELATION_HEADER);
if (!outgoingCid) {
outgoingCid = generateCorrelationId();
headers.set(CORRELATION_HEADER, outgoingCid);
}
const requestInit = { ...options, headers };
const response = await originalFetch(resource, requestInit);
try {
const incomingCid = (response && response.headers && response.headers.get(CORRELATION_HEADER)) || null;
// Track last seen correlation id for diagnostics/UI messaging
window.app.lastCorrelationId = incomingCid || outgoingCid || null;
} catch (_) {}
return response;
}
/**
* Parse the standardized error envelope from a failed response.
* Returns a normalized object with message, correlationId, code, details.
*/
async function parseErrorEnvelope(response) {
let body = null;
try {
body = await response.clone().json();
} catch (_) {
// not JSON; try text as last resort
try {
const txt = await response.clone().text();
body = { detail: txt };
} catch (_) {
body = null;
}
}
const headerCid = response && response.headers ? response.headers.get(CORRELATION_HEADER) : null;
const envelopeCid = body && (body.correlation_id || (body.error && body.error.correlation_id));
const correlationId = headerCid || envelopeCid || window.app.lastCorrelationId || null;
const message = body && body.error && body.error.message
? String(body.error.message)
: body && typeof body.detail === 'string' && body.detail.trim()
? String(body.detail)
: `HTTP ${response.status}`;
const code = body && body.error ? (body.error.code || null) : null;
const details = body && body.error ? (body.error.details || null) : null;
return { message, correlationId, code, details, raw: body };
}
/**
* Create an Error populated from a failed Response and optional fallback message.
*/
async function toError(response, fallbackMessage = null) {
const parsed = await parseErrorEnvelope(response);
const msg = parsed && parsed.message ? parsed.message : (fallbackMessage || `HTTP ${response.status}`);
const err = new Error(msg);
err.status = response.status;
err.correlationId = parsed.correlationId || null;
if (parsed && parsed.code) err.code = parsed.code;
if (parsed && parsed.details) err.details = parsed.details;
return err;
}
/**
* Format a message suitable for UI alerts with correlation id reference.
* Example: formatAlert(err, 'Error saving customer')
*/
function formatAlert(errorOrMessage, prefix = null) {
const isError = errorOrMessage instanceof Error;
const message = isError ? errorOrMessage.message : String(errorOrMessage || '');
const correlationId = isError && errorOrMessage.correlationId
? errorOrMessage.correlationId
: (window.app && window.app.lastCorrelationId) || null;
const base = prefix ? `${prefix}: ${message}` : message;
return correlationId ? `${base} (Ref: ${correlationId})` : base;
}
// Expose helpers
window.http = {
parseErrorEnvelope,
toError,
formatAlert,
};
// Install wrapper
window.fetch = wrappedFetch;
})();