Adjustment to the request debug view functionality

This commit is contained in:
2026-09-18 16:46:47 -03:00
parent ac817a69d4
commit eed4189a07
20 changed files with 945 additions and 87 deletions

View File

@@ -8,6 +8,7 @@ function createMessageTypes() {
refreshXsrfCookie: "opportunitiesExtension.refreshXsrfCookie",
requestOpptyProducts: "opportunitiesExtension.requestOpptyProducts",
opptyProductsProgress: "opportunitiesExtension.opptyProductsProgress",
opptyProductsRequestDebug: "opportunitiesExtension.opptyProductsRequestDebug",
requestConsumerTokenService: "opportunitiesExtension.requestConsumerTokenService",
requestAccountPlans: "opportunitiesExtension.requestAccountPlans",
requestConsumptionTrend: "opportunitiesExtension.requestConsumptionTrend"
@@ -757,6 +758,15 @@ function createProductsBackgroundJobs({
let completed = 0;
let failed = 0;
let cacheHits = 0;
let debugDelivery = Promise.resolve();
const reportRequest = entry => {
const message = {
type: messageTypes.opptyProductsRequestDebug,
requestId,
entry: { ...entry, debugKey: JSON.stringify([requestId, entry.metadata.optyNumber, entry.metadata.offset]) }
};
debugDelivery = debugDelivery.catch(() => {}).then(() => sendProductsProgress(sender, message));
};
const runWorker = async () => {
while (nextIndex < optyNumbers.length) {
@@ -765,7 +775,7 @@ function createProductsBackgroundJobs({
const optyNumber = optyNumbers[index];
try {
const result = await getOpptyProducts(optyNumber, accessToken, cacheMaxAgeMs);
const result = await getOpptyProducts(optyNumber, accessToken, cacheMaxAgeMs, reportRequest);
completed += 1;
if (result.fromCache) {
@@ -806,6 +816,7 @@ function createProductsBackgroundJobs({
const workerCount = Math.min(OPPTY_PRODUCTS_CONCURRENCY, Math.max(optyNumbers.length, 1));
await Promise.all(Array.from({ length: workerCount }, runWorker));
await debugDelivery.catch(() => {});
const state = failed > 0 ? "complete-with-errors" : "complete";
await sendProductsProgress(sender, {
@@ -848,7 +859,7 @@ function createProductsCache({
storageLocalGet,
storageLocalSet
}) {
async function getOpptyProducts(optyNumber, accessToken, cacheMaxAgeMs) {
async function getOpptyProducts(optyNumber, accessToken, cacheMaxAgeMs, onDebug = () => {}) {
const cache = await getProductsCache();
const cachedEntry = cache[optyNumber];
@@ -857,7 +868,14 @@ function createProductsCache({
}
if (!productsCacheState.inFlightProducts.has(optyNumber)) {
const request = fetchAllOpptyProducts(optyNumber, accessToken)
const flight = { listeners: new Set(), entries: new Map(), promise: null };
productsCacheState.inFlightProducts.set(optyNumber, flight);
flight.promise = fetchAllOpptyProducts(optyNumber, accessToken, entry => {
flight.entries.set(entry.metadata.offset, entry);
for (const listener of flight.listeners) {
try { listener(entry); } catch (_) {}
}
})
.then(async (items) => {
const fetchedAt = Date.now();
cache[optyNumber] = {
@@ -870,15 +888,20 @@ function createProductsCache({
.finally(() => {
productsCacheState.inFlightProducts.delete(optyNumber);
});
productsCacheState.inFlightProducts.set(optyNumber, request);
}
const fetchedResult = await productsCacheState.inFlightProducts.get(optyNumber);
return {
items: fetchedResult.items,
cachedAt: fetchedResult.fetchedAt,
fromCache: false
};
const flight = productsCacheState.inFlightProducts.get(optyNumber);
// A second tab/job shares the fetch but still receives its current diagnostics.
flight.listeners.add(onDebug);
for (const entry of flight.entries.values()) {
try { onDebug(entry); } catch (_) {}
}
try {
const fetchedResult = await flight.promise;
return { items: fetchedResult.items, cachedAt: fetchedResult.fetchedAt, fromCache: false };
} finally {
flight.listeners.delete(onDebug);
}
}
function normalizeCacheMaxAge(value) {
@@ -918,29 +941,67 @@ function createFusionProducts({
OPPTY_PRODUCTS_MAX_PAGES,
OPPTY_PRODUCTS_PAGE_LIMIT,
ORACLE_DOMAIN,
createBackgroundDebugEntry,
completeBackgroundDebugEntry,
createDebugBody,
headersToObject,
environment
}) {
const { fetch } = environment;
async function fetchAllOpptyProducts(optyNumber, accessToken) {
async function fetchAllOpptyProducts(optyNumber, accessToken, onDebug = () => {}) {
let offset = 0;
let page = 0;
const items = [];
while (page < OPPTY_PRODUCTS_MAX_PAGES) {
const response = await fetch(createOpptyProductsUrl(optyNumber, offset), {
const url = createOpptyProductsUrl(optyNumber, offset);
const options = {
method: "GET",
credentials: "include",
headers: {
Accept: "application/json",
Authorization: `Bearer ${accessToken}`
}
});
if (!response.ok) {
throw new Error(`requestOpptyProducts failed with status ${response.status}.`);
};
const entry = createBackgroundDebugEntry("requestOpptyProducts", url, {
...options, headers: { Accept: "application/json", Authorization: "(present)" }
}, new Date());
entry.metadata = { executionContext: "background", optyNumber, offset, page: page + 1 };
const report = () => {
// Diagnostics must not block requests or transmit the Bearer credential.
try { onDebug({ ...entry, metadata: { ...entry.metadata } }); } catch (_) {}
};
let response;
let responseBody = { value: "", truncated: false };
let responseData;
report();
try {
response = await fetch(url, options);
entry.httpStatus = response.status;
entry.statusText = response.statusText || "";
entry.responseHeaders = headersToObject(response.headers);
Object.keys(entry.responseHeaders).forEach(key => {
if (/authorization|cookie|token/i.test(key)) entry.responseHeaders[key] = "(present)";
});
entry.metadata.phase = "reading-response";
report();
const text = await response.text();
responseBody = createDebugBody(text);
if (!response.ok) throw new Error(`requestOpptyProducts failed with status ${response.status}.`);
responseData = JSON.parse(text);
if (!responseData || typeof responseData !== "object") throw new Error("requestOpptyProducts returned an invalid response.");
completeBackgroundDebugEntry(entry, response, responseBody, "");
} catch (error) {
completeBackgroundDebugEntry(entry, response, responseBody, error.message || "Unable to load products.");
entry.status = "failed";
throw error;
} finally {
Object.keys(entry.responseHeaders).forEach(key => {
if (/authorization|cookie|token/i.test(key)) entry.responseHeaders[key] = "(present)";
});
entry.metadata.phase = "complete";
report();
}
const responseData = await response.json();
const pageItems = Array.isArray(responseData.items) ? responseData.items : [];
items.push(...pageItems);
page += 1;
@@ -1561,6 +1622,10 @@ function createBackgroundApplication(overrides = {}) {
storageLocalSet: (...args) => platformBackgroundApi.storageLocalSet(...args)
});
const fusionProducts = createFusionProducts({
createBackgroundDebugEntry: (...args) => httpBackgroundJson.createBackgroundDebugEntry(...args),
completeBackgroundDebugEntry: (...args) => httpBackgroundJson.completeBackgroundDebugEntry(...args),
createDebugBody: (...args) => httpBackgroundJson.createDebugBody(...args),
headersToObject: (...args) => httpBackgroundJson.headersToObject(...args),
OPPTY_PRODUCTS_FIELDS: contracts.OPPTY_PRODUCTS_FIELDS,
OPPTY_PRODUCTS_MAX_PAGES: contracts.OPPTY_PRODUCTS_MAX_PAGES,
OPPTY_PRODUCTS_PAGE_LIMIT: contracts.OPPTY_PRODUCTS_PAGE_LIMIT,