frontend fixed and good
This commit is contained in:
1
static/css/main.css
Normal file
1
static/css/main.css
Normal file
File diff suppressed because one or more lines are too long
131
static/js/fetch-wrapper.js
Normal file
131
static/js/fetch-wrapper.js
Normal 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;
|
||||
})();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user