edit login & otp
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { logApiServerTiming } from './server-timing';
|
||||
|
||||
const baseUrl = import.meta.env.VITE_API_URL ?? 'http://localhost:3000';
|
||||
|
||||
export class ApiError extends Error {
|
||||
@@ -36,6 +38,8 @@ export async function api<T>(path: string, options: ApiOptions = {}): Promise<T>
|
||||
headers,
|
||||
});
|
||||
|
||||
logApiServerTiming(path, res);
|
||||
|
||||
if (!res.ok) {
|
||||
const body = (await res.json().catch(() => null)) as
|
||||
| { message?: string | string[] }
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { reactive } from 'vue';
|
||||
|
||||
export const apiMetrics = reactive({
|
||||
inFlight: 0,
|
||||
lastDurationMs: null as number | null,
|
||||
lastServerMs: null as number | null,
|
||||
lastMethod: 'GET',
|
||||
lastPath: '',
|
||||
lastOk: true,
|
||||
lastRequestId: null as string | null,
|
||||
});
|
||||
|
||||
export function parseTimingFromResponse(res: Response): {
|
||||
serverMs: number | null;
|
||||
requestId: string | null;
|
||||
} {
|
||||
let serverMs: number | null = null;
|
||||
const headerMs = res.headers.get('X-Response-Time-Ms');
|
||||
if (headerMs) {
|
||||
const n = Number(headerMs);
|
||||
if (Number.isFinite(n)) {
|
||||
serverMs = n;
|
||||
}
|
||||
}
|
||||
if (serverMs == null) {
|
||||
const serverTiming = res.headers.get('Server-Timing');
|
||||
const match = serverTiming?.match(/dur=(\d+(?:\.\d+)?)/);
|
||||
if (match) {
|
||||
serverMs = Math.round(Number(match[1]));
|
||||
}
|
||||
}
|
||||
return {
|
||||
serverMs,
|
||||
requestId: res.headers.get('X-Request-Id'),
|
||||
};
|
||||
}
|
||||
|
||||
export function trackApiRequest(
|
||||
method: string,
|
||||
path: string,
|
||||
): (ok: boolean, res?: Response) => void {
|
||||
apiMetrics.inFlight += 1;
|
||||
const start = performance.now();
|
||||
return (ok: boolean, res?: Response) => {
|
||||
apiMetrics.inFlight = Math.max(0, apiMetrics.inFlight - 1);
|
||||
apiMetrics.lastDurationMs = Math.round(performance.now() - start);
|
||||
apiMetrics.lastMethod = method.toUpperCase();
|
||||
apiMetrics.lastPath = path.split('?')[0] ?? path;
|
||||
apiMetrics.lastOk = ok;
|
||||
|
||||
if (res) {
|
||||
const timing = parseTimingFromResponse(res);
|
||||
apiMetrics.lastServerMs = timing.serverMs;
|
||||
apiMetrics.lastRequestId = timing.requestId;
|
||||
} else {
|
||||
apiMetrics.lastServerMs = null;
|
||||
apiMetrics.lastRequestId = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/** Log Server-Timing from API responses (DevTools Network tab also shows the header). */
|
||||
export function logApiServerTiming(path: string, res: Response): void {
|
||||
const timing = res.headers.get('server-timing');
|
||||
if (!timing) {
|
||||
return;
|
||||
}
|
||||
const enabled =
|
||||
import.meta.env.DEV || import.meta.env.VITE_LOG_API_TIMING === 'true';
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
const match = /total;dur=(\d+)/i.exec(timing);
|
||||
const ms = match?.[1] ?? timing;
|
||||
console.info(`[API] ${path} — server-timing: total;dur=${ms}`);
|
||||
}
|
||||
Reference in New Issue
Block a user