/** * Fetch API Detection and Utilities * * Provides comprehensive Fetch API support detection and utilities: * - Native fetch detection * - Feature capability testing * - Cross-platform compatibility checks * - Edge runtime detection */ /** * Test if ErrorEvent constructor is available * @returns {boolean} True if ErrorEvent is supported */ function supportsErrorEvent() { try { new ErrorEvent(""); return true; } catch (error) { return false; } } /** * Test if DOMError constructor is available * @returns {boolean} True if DOMError is supported */ function supportsDOMError() { try { new DOMError(""); return true; } catch (error) { return false; } } /** * Test if DOMException constructor is available * @returns {boolean} True if DOMException is supported */ function supportsDOMException() { try { new DOMException(""); return true; } catch (error) { return false; } } /** * Test if fetch API is available * @returns {boolean} True if fetch is supported */ function supportsFetch() { const globalObj = getGlobalObject(); if (!("fetch" in globalObj)) { return false; } try { new Request("http://www.example.com"); return true; } catch (error) { return false; } } /** * Test if function is native fetch implementation * @param {Function} fetchFn - Function to test * @returns {boolean} True if it's native fetch */ function isNativeFetch(fetchFn) { return ( fetchFn && /^function fetch\(\)\s+\{\s+\[native code\]\s+\}$/.test(fetchFn.toString()) ); } /** * Detect if environment supports native fetch (not polyfilled) * @returns {boolean} True if native fetch is available */ function supportsNativeFetch() { const globalObj = getGlobalObject(); // Edge Runtime always has native fetch if (typeof EdgeRuntime === "string") { return true; } if (!supportsFetch()) { return false; } // Check if window.fetch is native if (isNativeFetch(globalObj.fetch)) { return true; } // Test using iframe sandbox for pure fetch detection let hasNativeFetch = false; const document = globalObj.document; if (document && typeof document.createElement === "function") { try { const iframe = document.createElement("iframe"); iframe.hidden = true; document.head.appendChild(iframe); if (iframe.contentWindow && iframe.contentWindow.fetch) { hasNativeFetch = isNativeFetch(iframe.contentWindow.fetch); } document.head.removeChild(iframe); } catch (error) { // Fallback to window.fetch check if iframe test fails console.warn( "Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ", error, ); } } return hasNativeFetch; } /** * Test if ReportingObserver is available * @returns {boolean} True if ReportingObserver is supported */ function supportsReportingObserver() { const globalObj = getGlobalObject(); return "ReportingObserver" in globalObj; } /** * Test if referrerPolicy is supported in Request constructor * @returns {boolean} True if referrerPolicy is supported */ function supportsReferrerPolicy() { if (!supportsFetch()) { return false; } try { new Request("_", { referrerPolicy: "origin" }); return true; } catch (error) { return false; } } /** * Parse fetch arguments into method and URL * @param {Array} args - Fetch function arguments * @returns {Object} Parsed arguments with method and url */ function parseFetchArgs(args) { if (args.length === 0) { return { method: "GET", url: "" }; } if (args.length === 2) { const [input, init] = args; return { url: extractUrl(input), method: hasProperty(init, "method") ? String(init.method).toUpperCase() : "GET", }; } const input = args[0]; return { url: extractUrl(input), method: hasProperty(input, "method") ? String(input.method).toUpperCase() : "GET", }; } /** * Extract URL from fetch input parameter * @param {string|Request|URL} input - Fetch input parameter * @returns {string} Extracted URL string */ function extractUrl(input) { if (typeof input === "string") { return input; } if (!input) { return ""; } if (hasProperty(input, "url")) { return input.url; } if (input.toString) { return input.toString(); } return ""; } /** * Check if object has a specific property * @param {any} obj - Object to check * @param {string} prop - Property name * @returns {boolean} True if object has property */ function hasProperty(obj, prop) { return !!(obj && typeof obj === "object" && obj[prop]); } /** * Get global object (window, global, self, etc.) * @returns {Object} Global object */ function getGlobalObject() { return ( (typeof globalThis === "object" && globalThis) || (typeof window === "object" && window) || (typeof self === "object" && self) || (typeof global === "object" && global) || (function () { return this; })() || {} ); } /** * Create fetch capability report * @returns {Object} Comprehensive capability report */ function getFetchCapabilities() { const globalObj = getGlobalObject(); return { // Basic availability hasFetch: supportsFetch(), hasNativeFetch: supportsNativeFetch(), // Related API support hasErrorEvent: supportsErrorEvent(), hasDOMError: supportsDOMError(), hasDOMException: supportsDOMException(), hasReportingObserver: supportsReportingObserver(), hasReferrerPolicy: supportsReferrerPolicy(), // Environment detection isEdgeRuntime: typeof EdgeRuntime === "string", isBrowser: typeof window !== "undefined", isNode: typeof global !== "undefined" && !globalObj.window, isWorker: typeof self !== "undefined" && typeof window === "undefined", // Implementation details fetchFunction: globalObj.fetch ? globalObj.fetch.toString().slice(0, 100) : null, nativeFetchDetected: globalObj.fetch ? isNativeFetch(globalObj.fetch) : false, }; } /** * Create a fetch wrapper that adds instrumentation * @param {Function} originalFetch - Original fetch function * @param {Function} onRequest - Request interceptor * @param {Function} onResponse - Response interceptor * @param {Function} onError - Error interceptor * @returns {Function} Wrapped fetch function */ function createInstrumentedFetch( originalFetch, onRequest, onResponse, onError, ) { return function instrumentedFetch(...args) { const { method, url } = parseFetchArgs(args); const requestData = { args, fetchData: { method, url }, startTimestamp: Date.now(), }; // Call request interceptor if (onRequest) { onRequest(requestData); } return originalFetch .apply(this, args) .then((response) => { const responseData = { ...requestData, endTimestamp: Date.now(), response, }; if (onResponse) { onResponse(responseData); } return response; }) .catch((error) => { const errorData = { ...requestData, endTimestamp: Date.now(), error, }; if (onError) { onError(errorData); } throw error; }); }; } module.exports = { supportsErrorEvent, supportsDOMError, supportsDOMException, supportsFetch, supportsNativeFetch, supportsReportingObserver, supportsReferrerPolicy, isNativeFetch, parseFetchArgs, extractUrl, getFetchCapabilities, createInstrumentedFetch, getGlobalObject, };