PageSourceSearch

https://moto-ankauf.de/assets/utils/diagnostics.js

js moto-ankauf.de collected 2026-10-07 21:53:22 UTC 8,310 bytes, 287 lines download raw bytes

1import { getCookie } from "./cookies.js";
2
3const DIAGNOSTICS_ENDPOINT = '/diagnostics/errors';
4const MAX_TEXT_LENGTH = 10000;
5const MAX_PATH_LENGTH = 255;
6
7function isAbortError(error) {
8    return error?.name === 'AbortError';
9}
10
11function toAbsoluteUrl(input) {
12    if (typeof window === 'undefined') {
13        return null;
14    }
15
16    const base = window.location.origin;
17
18    if (typeof input === 'string') {
19        return new URL(input, base);
20    }
21
22    if (input instanceof URL) {
23        return new URL(input.toString(), base);
24    }
25
26    if (typeof Request !== 'undefined' && input instanceof Request) {
27        return new URL(input.url, base);
28    }
29
30    return null;
31}
32
33function isDiagnosticsRequest(url) {
34    if (!url) return false;
35    return (
36        url.pathname === DIAGNOSTICS_ENDPOINT
37        || url.pathname === '/v2/diagnostics/errors'
38    );
39}
40
41function getDiagnosticsUrl() {
42    if (typeof window === 'undefined') {
43        return null;
44    }
45
46    const configBase = (window.__MV_DIAGNOSTICS_API_BASE || '').trim();
47    if (configBase) {
48        return `${configBase}${DIAGNOSTICS_ENDPOINT}`;
49    }
50
51    const metaBase = document.querySelector('meta[name="mv-api-base"]')?.getAttribute('content')?.trim() || '';
52    if (metaBase) {
53        return `${metaBase.replace(/\/+$/, '')}${DIAGNOSTICS_ENDPOINT}`;
54    }
55
56    const apiDomain = (getCookie('api_domain') || '').trim();
57    if (apiDomain) {
58        return `${window.location.protocol}//${apiDomain}${DIAGNOSTICS_ENDPOINT}`;
59    }
60
61    return null;
62}
63
64function sanitizeText(value, maxLength) {
65    if (!value) return undefined;
66    const normalized = String(value).trim();
67    if (!normalized) return undefined;
68    return normalized.slice(0, maxLength);
69}
70
71function buildMethod(input, init) {
72    if (init?.method) {
73        return String(init.method).toUpperCase();
74    }
75
76    if (typeof Request !== 'undefined' && input instanceof Request && input.method) {
77        return String(input.method).toUpperCase();
78    }
79
80    return 'GET';
81}
82
83function buildMessage(error, response, method, path) {
84    const explicitMessage = sanitizeText(error?.message, MAX_TEXT_LENGTH);
85    if (explicitMessage && !/^HTTP\s+\d{3}$/i.test(explicitMessage)) {
86        return explicitMessage;
87    }
88
89    if (response) {
90        return `${method} ${path} failed with status ${response.status}`;
91    }
92
93    const fallback = explicitMessage || 'Request failed';
94    return sanitizeText(fallback, MAX_TEXT_LENGTH) || 'Request failed';
95}
96
97function buildClientContext() {
98    if (typeof window === 'undefined' || typeof navigator === 'undefined') {
99        return {};
100    }
101
102    return {
103        app_version: window.APP_VERSION || undefined,
104        environment: window.APP_ENV || undefined,
105        locale: navigator.language || undefined,
106        user_agent: navigator.userAgent || undefined,
107        page_url: window.location.href,
108        referrer: document?.referrer || undefined,
109    };
110}
111
112function buildFingerprint(method, path, response) {
113    const status = response?.status || 'network';
114    const shortPath = sanitizeText(path, 120) || 'unknown';
115    return sanitizeText(`frontend-request-${method}-${status}-${shortPath}`, 191);
116}
117
118function getValidStatusCode(response) {
119    const status = response?.status;
120    return Number.isInteger(status) && status >= 100 && status <= 599 ? status : undefined;
121}
122
123function isIgnorableResponse(response) {
124    if (!response) return false;
125    return (
126        response.status === 0
127        || response.type === 'opaque'
128        || response.type === 'opaqueredirect'
129    );
130}
131
132function buildOccurredAtRfc3339() {
133    return new Date().toISOString().replace(/\.\d{3}Z$/, '+00:00');
134}
135
136function parseProblemIds(bodyText) {
137    if (!bodyText) {
138        return { requestId: undefined, correlationId: undefined };
139    }
140
141    try {
142        const parsed = JSON.parse(bodyText);
143        if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
144            return { requestId: undefined, correlationId: undefined };
145        }
146
147        const requestId = sanitizeText(parsed.request_id || parsed.correlation_id, 191);
148        const correlationId = sanitizeText(parsed.correlation_id || parsed.request_id, 191);
149
150        return { requestId, correlationId };
151    } catch (_parseError) {
152        return { requestId: undefined, correlationId: undefined };
153    }
154}
155
156function sendViaBeacon(url, payload) {
157    if (typeof navigator === 'undefined' || typeof navigator.sendBeacon !== 'function') {
158        return false;
159    }
160
161    try {
162        const body = new Blob([JSON.stringify(payload)], { type: 'application/json' });
163        return navigator.sendBeacon(url, body);
164    } catch (_error) {
165        return false;
166    }
167}
168
169async function sendPublicDiagnostics(url, payload) {
170    if (!url) {
171        return false;
172    }
173
174    if (sendViaBeacon(url, payload)) {
175        return true;
176    }
177
178    await window.fetch(url, {
179        method: 'POST',
180        headers: {
181            'Content-Type': 'application/json',
182        },
183        body: JSON.stringify(payload),
184        keepalive: true,
185        __mvSkipDiagnosticsPatch: true,
186    });
187
188    return true;
189}
190
191export async function sendDiagnosticsError({
192    error,
193    input,
194    init,
195    response,
196}) {
197    if (typeof window === 'undefined') {
198        return;
199    }
200
201    if (isAbortError(error)) {
202        return;
203    }
204
205    const url = toAbsoluteUrl(input);
206    if (isDiagnosticsRequest(url) || isIgnorableResponse(response)) {
207        return;
208    }
209
210    const method = buildMethod(input, init);
211    const pathWithQuery = url ? `${url.pathname}${url.search}` : undefined;
212    const httpPath = sanitizeText(pathWithQuery, MAX_PATH_LENGTH);
213    let effectiveError = error;
214    let backendErrorBody;
215
216    if (response) {
217        try {
218            backendErrorBody = sanitizeText(await response.clone().text(), MAX_TEXT_LENGTH);
219            if (backendErrorBody) {
220                effectiveError = new Error(backendErrorBody);
221            }
222        } catch (_bodyReadError) {
223            // Ignore body parsing issues, keep fallback message.
224        }
225    }
226
227    const message = buildMessage(effectiveError, response, method, httpPath || 'unknown');
228    const problemIds = parseProblemIds(backendErrorBody);
229    const requestId = sanitizeText(response?.headers?.get?.('x-request-id') || problemIds.requestId, 191);
230    const correlationId = sanitizeText(problemIds.correlationId || requestId, 191);
231
232    const payload = {
233        message,
234        severity: 'error',
235        type: sanitizeText(effectiveError?.name || (response ? 'HttpRequestError' : undefined), 191),
236        stack_trace: sanitizeText(effectiveError?.stack, 65000),
237        occurred_at: buildOccurredAtRfc3339(),
238        http_path: httpPath,
239        http_method: sanitizeText(method, 16),
240        status_code: getValidStatusCode(response),
241        request_id: sanitizeText(requestId, 191),
242        correlation_id: sanitizeText(correlationId, 191),
243        fingerprint: buildFingerprint(method, url?.pathname || 'unknown', response),
244        client_context: buildClientContext(),
245        extra: {
246            request_url: sanitizeText(url?.toString(), 1000),
247            request_origin: sanitizeText(url?.origin, 255),
248            status_text: sanitizeText(response?.statusText, 255),
249            backend_error_body: backendErrorBody,
250        },
251    };
252
253    try {
254        const diagnosticsUrl = getDiagnosticsUrl();
255        await sendPublicDiagnostics(diagnosticsUrl, payload);
256    } catch (_reportingError) {
257        // Silent fallback: diagnostics reporting must never break UI flows.
258    }
259}
260
261export async function fetchWithDiagnostics(input, init = {}, options = {}) {
262    const { reportHttpErrors = true } = options;
263
264    const patchedInit = {
265        ...init,
266        __mvSkipDiagnosticsPatch: true,
267    };
268
269    let response;
270    try {
271        response = await fetch(input, patchedInit);
272    } catch (error) {
273        await sendDiagnosticsError({ error, input, init: patchedInit });
274        throw error;
275    }
276
277    if (reportHttpErrors && !response.ok && !isIgnorableResponse(response)) {
278        await sendDiagnosticsError({
279            error: new Error(`HTTP ${response.status}`),
280            input,
281            init: patchedInit,
282            response,
283        });
284    }
285
286    return response;
287}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.