166 lines
4.9 KiB
JavaScript
166 lines
4.9 KiB
JavaScript
function createHttpBackgroundJson({
|
|
previewDebugBody,
|
|
observeResponseHeaders = () => {},
|
|
environment
|
|
}) {
|
|
const { fetch } = environment;
|
|
async function fetchJson(url, options, requestName, debugRequests = null, { allowEmpty = false } = {}) {
|
|
const startedAt = new Date();
|
|
const debugEntry = createBackgroundDebugEntry(requestName, url, options, startedAt);
|
|
let response;
|
|
|
|
try {
|
|
response = await fetch(url, options);
|
|
} catch (error) {
|
|
completeBackgroundDebugEntry(debugEntry, null, "", error.message || "Network request failed.");
|
|
appendBackgroundDebugEntry(debugRequests, debugEntry);
|
|
throw error;
|
|
}
|
|
|
|
observeResponseHeaders(response.url || url, response.headers);
|
|
const responsePayload = await readResponsePayload(response);
|
|
const responseBody = createDebugBody(responsePayload.text);
|
|
completeBackgroundDebugEntry(debugEntry, response, responseBody, "");
|
|
debugEntry.metadata.responseContentType = response.headers.get("content-type") || "(not provided)";
|
|
debugEntry.metadata.responseLength = String(responsePayload.text.length);
|
|
appendBackgroundDebugEntry(debugRequests, debugEntry);
|
|
|
|
if (!response.ok) {
|
|
const error = new Error(`${requestName} failed with status ${response.status}.`);
|
|
error.httpStatus = response.status;
|
|
debugEntry.error = error.message;
|
|
throw error;
|
|
}
|
|
|
|
if (responsePayload.error) {
|
|
throw new Error(`${requestName} response body could not be read.`);
|
|
}
|
|
|
|
try {
|
|
if (allowEmpty && !responsePayload.text.trim()) return null;
|
|
return parseJsonResponse(responsePayload.text, requestName);
|
|
} catch (error) {
|
|
error.httpStatus = response.status;
|
|
debugEntry.status = "failed";
|
|
debugEntry.error = error.message;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
function createBackgroundDebugEntry(requestName, url, options, startedAt) {
|
|
return {
|
|
name: requestName,
|
|
method: options.method || "GET",
|
|
url,
|
|
status: "pending",
|
|
startedAt: startedAt.toISOString(),
|
|
completedAt: null,
|
|
durationMs: null,
|
|
httpStatus: null,
|
|
statusText: "",
|
|
requestHeaders: normalizeDebugHeaders(options.headers),
|
|
requestBody: formatDebugRequestBody(options.body),
|
|
metadata: {
|
|
executionContext: "background"
|
|
},
|
|
responseHeaders: {},
|
|
responseBody: "",
|
|
responseBodyTruncated: false,
|
|
error: ""
|
|
};
|
|
}
|
|
|
|
function completeBackgroundDebugEntry(entry, response, responseBody, errorMessage) {
|
|
const completedAt = new Date();
|
|
entry.completedAt = completedAt.toISOString();
|
|
entry.durationMs = completedAt.getTime() - new Date(entry.startedAt).getTime();
|
|
entry.error = errorMessage;
|
|
|
|
if (!response) {
|
|
entry.status = "failed";
|
|
return;
|
|
}
|
|
|
|
entry.status = response.ok ? "success" : "failed";
|
|
entry.httpStatus = response.status;
|
|
entry.statusText = response.statusText || "";
|
|
entry.responseHeaders = headersToObject(response.headers);
|
|
entry.responseBody = responseBody.value;
|
|
entry.responseBodyTruncated = responseBody.truncated;
|
|
}
|
|
|
|
function appendBackgroundDebugEntry(debugRequests, entry) {
|
|
if (Array.isArray(debugRequests)) {
|
|
debugRequests.push(entry);
|
|
}
|
|
}
|
|
|
|
async function readResponsePayload(response) {
|
|
try {
|
|
return {
|
|
text: await response.text(),
|
|
error: ""
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
text: `Unable to read response body: ${error.message || "unknown error"}`,
|
|
error: error.message || "unknown error"
|
|
};
|
|
}
|
|
}
|
|
|
|
function parseJsonResponse(text, requestName) {
|
|
const normalizedText = String(text)
|
|
.replace(/^\uFEFF/, "")
|
|
.trimStart()
|
|
.replace(/^\)\]\}',?\s*/, "")
|
|
.replace(/^while\s*\(1\);\s*/, "");
|
|
|
|
try {
|
|
return JSON.parse(normalizedText);
|
|
} catch (error) {
|
|
const invalidJsonError = new Error(`${requestName} returned an invalid JSON response.`);
|
|
invalidJsonError.code = "INVALID_JSON_RESPONSE";
|
|
throw invalidJsonError;
|
|
}
|
|
}
|
|
|
|
function createDebugBody(text) {
|
|
return previewDebugBody(text);
|
|
}
|
|
|
|
function normalizeDebugHeaders(headers) {
|
|
const normalized = {};
|
|
|
|
Object.entries(headers || {}).forEach(([key, value]) => {
|
|
normalized[key] = String(value);
|
|
});
|
|
return normalized;
|
|
}
|
|
|
|
function headersToObject(headers) {
|
|
const values = {};
|
|
|
|
if (headers && typeof headers.forEach === "function") {
|
|
headers.forEach((value, key) => {
|
|
values[key] = value;
|
|
});
|
|
}
|
|
return values;
|
|
}
|
|
|
|
function formatDebugRequestBody(body) {
|
|
if (!body) {
|
|
return "";
|
|
}
|
|
|
|
try {
|
|
return JSON.stringify(JSON.parse(body), null, 2);
|
|
} catch (error) {
|
|
return String(body);
|
|
}
|
|
}
|
|
|
|
return { fetchJson, createBackgroundDebugEntry, completeBackgroundDebugEntry, appendBackgroundDebugEntry, readResponsePayload, parseJsonResponse, createDebugBody, normalizeDebugHeaders, headersToObject, formatDebugRequestBody };
|
|
}
|