(function () { "use strict"; const ORACLE_DOMAIN = "eeho.fa.us2.oraclecloud.com"; const ORACLE_COOKIE_URLS = [ "https://eeho.fa.us2.oraclecloud.com/", "https://eeho.fa.us2.oraclecloud.com/hcmUI/faces/FuseWelcome", "https://eeho.fa.us2.oraclecloud.com/fscmUI/faces/FuseWelcome", "https://eeho.fa.us2.oraclecloud.com/fscmRestApi/tokenrelay" ]; const XSRF_COOKIE_NAME = "XSRF-TOKEN-US2DZ2V_F"; const XSRF_COOKIE_PREFIX = "XSRF-TOKEN-"; const OPPORTUNITIES_LIST_URL = "https://eeho.fa.us2.oraclecloud.com/fscmUI/redwood/cx-sales/application/container/opportunities/opportunities-list"; const COOKIE_REFRESH_TIMEOUT_MS = 15000; const COOKIE_REFRESH_POLL_MS = 250; const OPPTY_PRODUCTS_CACHE_KEY = "opportunitiesExtension.opptyProductsCache.v1"; const OPPTY_PRODUCTS_PAGE_LIMIT = 15; const OPPTY_PRODUCTS_MAX_PAGES = 250; const OPPTY_PRODUCTS_CONCURRENCY = 4; const CUSTOMER_TOKEN_SERVICE_URL = "https://spa.oracle.com/oalcrm/web/api/g2m-consumer-application/consumerTokenService"; const ACCOUNT_SUMMARY_URL = "https://spa.oracle.com/oalcrm/web/api/provider-proxy/g2m-account/service/accountsummary"; const ACCOUNT_PLANS_URL = "https://spa.oracle.com/oalcrm/web/api/provider-proxy/g2m-consumption/service/v1/account/accountPlans"; const ACCOUNT_PLANS_PAYLOAD = { hierarchy: "single", products: "all", language: "english", planStatus: [ "active", "awaiting_provisioning", "awaiting_provisioning_error", "draft", "expired", "final_billing", "onhold", "provisioned", "signed", "superseded", "superseded_pending_final_billing", "suspended", "terminated", "terminated_pending_final_billing" ] }; const DEBUG_BODY_LIMIT = 100000; const OPPTY_PRODUCTS_FIELDS = [ "ProductType", "Description", "InventoryItemId", "ProdGroupName", "ProdGroupId", "OwnerLockAsgnFlag", "Quantity", "RecurTypeCode", "RevnAmountCurcyCode", "UnitPrice", "RevnAmount", "PriceTypeCode", "EffectiveDate", "Name1", "NonRecurringRevenue", "OpportunityOwnerPartyName", "OpportunityOwnerResourcePartyId", "OptyId", "OptyNumber", "PartyName2", "PrTerritoryVersionId", "PrTerritoryVersionIdForManual", "RecurEndDate", "RecurFrequencyCode", "RecurNumberPeriods", "RecurRevenue", "ResourcePartyId", "RevnId", "RevnNumber", "SplitPercent", "SalesCreditTypeCode", "SplitTypeCode", "TerrOwnerPartyName", "UpsideAmount", "UsageRevenue", "StatusCode", "BUOrgId", "TypeCode", "WinProb", "ForecastType_c", "ARRLocalCurrency_c", "CPQLastSyncDate_c", "CPQOperationType_c", "ProdGLID_c", "ProposalNumber_c", "QuoteNumber_c", "ServicesPeriod_c", "CPQIntLastUpdatedBy_c", "CPQUpdatedAmount_c", "CPQUpdatedQuantity_c", "CPQUpdatedRevnType_c", "CPQUpdatedServicePeriod_c", "CPQUpdatedStatus_c", "OrderNumber_c", "PrevAmount_c", "PrevQuantity_c", "PrevRevenueType_c", "PrevServicePeriod_c", "PrevStatus_c", "SubscriptionID_c", "WorkloadName_c", "ConsumptionStartDate_c", "RampMonths_c" ]; const runtimeApi = typeof browser !== "undefined" ? browser : chrome; let productsCachePromise = null; let productsCacheWritePromise = Promise.resolve(); const inFlightProducts = new Map(); runtimeApi.runtime.onMessage.addListener((message, sender, sendResponse) => { if (!message || !message.type) { return false; } if (message.type === "opportunitiesExtension.getXsrfToken") { getXsrfTokenCookie() .then((result) => { sendResponse({ ok: true, cookieName: result.cookie ? result.cookie.name : "", token: result.cookie ? result.cookie.value : "", matchedCookieNames: result.matchedCookieNames, lookupDetails: result.lookupDetails }); }) .catch((error) => { sendResponse({ ok: false, cookieName: "", token: "", matchedCookieNames: [], lookupDetails: [], error: error.message || "Unable to read cookies." }); }); return true; } if (message.type === "opportunitiesExtension.refreshXsrfCookie") { refreshXsrfCookie(sender) .then(sendResponse) .catch((error) => { sendResponse({ ok: false, refreshed: false, cookieName: "", durationMs: 0, error: error.message || "Unable to refresh XSRF cookie." }); }); return true; } if (message.type === "opportunitiesExtension.requestOpptyProducts") { requestOpptyProducts(message, sender) .then(sendResponse) .catch((error) => { sendResponse({ ok: false, requestId: message.requestId || "", error: error.message || "Unable to load opportunity products." }); }); return true; } if (message.type === "opportunitiesExtension.requestConsumerTokenService") { requestConsumerTokenService() .then(sendResponse) .catch((error) => { sendResponse({ ok: false, error: error.message || "Unable to load consumer token service.", debugRequests: Array.isArray(error.debugRequests) ? error.debugRequests : [] }); }); return true; } if (message.type === "opportunitiesExtension.requestAccountPlans") { requestAccountPlans(message) .then(sendResponse) .catch((error) => { sendResponse({ ok: false, error: error.message || "Unable to load account plans.", debugRequests: Array.isArray(error.debugRequests) ? error.debugRequests : [] }); }); return true; } return false; }); async function requestConsumerTokenService() { const debugRequests = []; try { const tokenResponse = await fetchJson(CUSTOMER_TOKEN_SERVICE_URL, { method: "GET", credentials: "include", cache: "no-store", headers: { Accept: "application/json", "Cache-Control": "no-cache", Pragma: "no-cache" } }, "requestConsumerTokenService", debugRequests); const accountSummaryAuthorization = extractTokenServiceAuthorization( tokenResponse, "ACCP-TS", "account-ti-authorization" ); const accountPlansAuthorization = extractTokenServiceAuthorization( tokenResponse, "OCICONS-TS", "g2m-authorization" ); const tokenRequestDebugEntry = debugRequests[debugRequests.length - 1]; if (tokenRequestDebugEntry) { tokenRequestDebugEntry.metadata = { ...tokenRequestDebugEntry.metadata, accountSummaryTokenService: "ACCP-TS", accountSummaryAuthorizationHeader: accountSummaryAuthorization.name || "(missing)", accountSummaryAuthorizationValue: accountSummaryAuthorization.value ? `(present, ${accountSummaryAuthorization.value.length} characters)` : "(missing)", accountSummaryAuthorizationExpires: accountSummaryAuthorization.expires || "(not provided)", accountPlansTokenService: "OCICONS-TS", accountPlansAuthorizationHeader: accountPlansAuthorization.name || "(missing)", accountPlansAuthorizationValue: accountPlansAuthorization.value ? `(present, ${accountPlansAuthorization.value.length} characters)` : "(missing)", accountPlansAuthorizationExpires: accountPlansAuthorization.expires || "(not provided)" }; } if (!accountSummaryAuthorization.value) { throw new Error("ACCP-TS uiaasHeader.value was not found in requestConsumerTokenService."); } if (!accountPlansAuthorization.value) { throw new Error("OCICONS-TS uiaasHeader.value was not found in requestConsumerTokenService."); } return { ok: true, authorizations: { accountSummary: accountSummaryAuthorization, accountPlans: accountPlansAuthorization }, debugRequests }; } catch (error) { error.debugRequests = debugRequests; throw error; } } async function requestAccountPlans(message) { const partyNumber = typeof message.partyNumber === "string" ? message.partyNumber.trim() : ""; const accountSummaryAuthorization = normalizeMessageAuthorization( message.authorizations && message.authorizations.accountSummary, "account-ti-authorization" ); const accountPlansAuthorization = normalizeMessageAuthorization( message.authorizations && message.authorizations.accountPlans, "g2m-authorization" ); const debugRequests = []; if (!partyNumber) { throw new Error("Customer Party Number is required."); } if (!accountSummaryAuthorization.value) { throw new Error("ACCP-TS authorization is unavailable. requestConsumerTokenService must complete first."); } if (!accountPlansAuthorization.value) { throw new Error("OCICONS-TS authorization is unavailable. requestConsumerTokenService must complete first."); } try { const accountSummary = await fetchJson(ACCOUNT_SUMMARY_URL, { method: "POST", credentials: "include", headers: { Accept: "application/json", [accountSummaryAuthorization.name]: accountSummaryAuthorization.value, "Content-Type": "application/json" }, body: JSON.stringify([{ key: "regId", value: partyNumber }]) }, "requestAccountSummary", debugRequests); const accountPlanId = findAccountPlanId(accountSummary); if (!accountPlanId) { throw new Error("Account plan id was not found in requestAccountSummary."); } const accountPlansResponse = await fetchJson(`${ACCOUNT_PLANS_URL}/${encodeURIComponent(String(accountPlanId))}`, { method: "POST", credentials: "include", headers: { Accept: "application/json", [accountPlansAuthorization.name]: accountPlansAuthorization.value, "Content-Type": "application/json" }, body: JSON.stringify(ACCOUNT_PLANS_PAYLOAD) }, "requestAccountPlans", debugRequests); return { ok: true, accountPlanId: String(accountPlanId), plans: extractAccountPlans(accountPlansResponse), debugRequests }; } catch (error) { error.debugRequests = debugRequests; throw error; } } function normalizeMessageAuthorization(authorization, fallbackHeaderName) { const headerName = authorization && typeof authorization.name === "string" ? authorization.name.trim() : ""; const headerValue = authorization && typeof authorization.value === "string" ? authorization.value.trim() : ""; return { name: headerName || fallbackHeaderName, value: headerValue }; } function extractTokenServiceAuthorization(response, tokenServiceName, fallbackHeaderName) { const tokenService = Array.isArray(response) ? response.find((entry) => entry && entry.tokenService === tokenServiceName) : findNestedObject(response, (entry) => entry.tokenService === tokenServiceName); const authorizationHeader = tokenService && tokenService.uiaasHeader; const headerName = authorizationHeader && typeof authorizationHeader.label === "string" ? authorizationHeader.label.trim() : ""; const headerValue = authorizationHeader && typeof authorizationHeader.value === "string" ? authorizationHeader.value.trim() : ""; return { name: headerName || fallbackHeaderName, value: headerValue, expires: authorizationHeader && authorizationHeader.expires ? String(authorizationHeader.expires) : "" }; } async function fetchJson(url, options, requestName, debugRequests = null) { 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; } 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) { throw new Error(`${requestName} failed with status ${response.status}.`); } if (responsePayload.error) { throw new Error(`${requestName} response body could not be read.`); } return parseJsonResponse(responsePayload.text, requestName); } 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) { throw new Error(`${requestName} returned an invalid JSON response.`); } } function createDebugBody(text) { if (text.length > DEBUG_BODY_LIMIT) { return { value: `${text.slice(0, DEBUG_BODY_LIMIT)}\n... truncated ${text.length - DEBUG_BODY_LIMIT} characters`, truncated: true }; } return { value: text || "(empty response body)", truncated: false }; } 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); } } function findNestedObject(value, predicate, visited = new Set()) { if (!value || typeof value !== "object" || visited.has(value)) { return null; } visited.add(value); if (!Array.isArray(value) && predicate(value)) { return value; } for (const child of Object.values(value)) { const match = findNestedObject(child, predicate, visited); if (match) { return match; } } return null; } function findAccountPlanId(response) { const account = findNestedObject(response, (value) => ( Object.prototype.hasOwnProperty.call(value, "id") && value.id !== null && value.id !== undefined && value.id !== "" )); return account ? account.id : ""; } function extractAccountPlans(response) { const plans = []; const visited = new Set(); const visit = (value) => { if (!value || typeof value !== "object" || visited.has(value)) { return; } visited.add(value); if (!Array.isArray(value) && ( Object.prototype.hasOwnProperty.call(value, "planType") || Object.prototype.hasOwnProperty.call(value, "subPlanNum") )) { plans.push(value); return; } Object.values(value).forEach(visit); }; visit(response); return plans; } async function requestOpptyProducts(message, sender) { const requestId = typeof message.requestId === "string" ? message.requestId : ""; const accessToken = typeof message.accessToken === "string" ? message.accessToken : ""; const cacheMaxAgeMs = normalizeCacheMaxAge(message.cacheMaxAgeMs); const optyNumbers = Array.from(new Set( (Array.isArray(message.optyNumbers) ? message.optyNumbers : []) .filter(Boolean) .map(String) )); if (!requestId || !accessToken) { throw new Error("A request id and access token are required for requestOpptyProducts."); } let nextIndex = 0; let completed = 0; let failed = 0; let cacheHits = 0; const runWorker = async () => { while (nextIndex < optyNumbers.length) { const index = nextIndex; nextIndex += 1; const optyNumber = optyNumbers[index]; try { const result = await getOpptyProducts(optyNumber, accessToken, cacheMaxAgeMs); completed += 1; if (result.fromCache) { cacheHits += 1; } await sendProductsProgress(sender, { type: "opportunitiesExtension.opptyProductsProgress", requestId, state: "running", optyNumber, items: result.items, fromCache: result.fromCache, cachedAt: result.cachedAt, completed, total: optyNumbers.length, failed, cacheHits }); } catch (error) { completed += 1; failed += 1; await sendProductsProgress(sender, { type: "opportunitiesExtension.opptyProductsProgress", requestId, state: "running", optyNumber, items: [], error: error.message || "Unable to load products.", completed, total: optyNumbers.length, failed, cacheHits }); } } }; const workerCount = Math.min(OPPTY_PRODUCTS_CONCURRENCY, Math.max(optyNumbers.length, 1)); await Promise.all(Array.from({ length: workerCount }, runWorker)); const state = failed > 0 ? "complete-with-errors" : "complete"; await sendProductsProgress(sender, { type: "opportunitiesExtension.opptyProductsProgress", requestId, state, completed, total: optyNumbers.length, failed, cacheHits }); return { ok: failed === 0, requestId, completed, total: optyNumbers.length, failed, cacheHits }; } async function getOpptyProducts(optyNumber, accessToken, cacheMaxAgeMs) { const cache = await getProductsCache(); const cachedEntry = cache[optyNumber]; if (cachedEntry && Array.isArray(cachedEntry.items) && Date.now() - Number(cachedEntry.fetchedAt) < cacheMaxAgeMs) { return { items: cachedEntry.items, fromCache: true, cachedAt: Number(cachedEntry.fetchedAt) }; } if (!inFlightProducts.has(optyNumber)) { const request = fetchAllOpptyProducts(optyNumber, accessToken) .then(async (items) => { const fetchedAt = Date.now(); cache[optyNumber] = { fetchedAt, items }; await saveProductsCache(cache).catch(() => {}); return { items, fetchedAt }; }) .finally(() => { inFlightProducts.delete(optyNumber); }); inFlightProducts.set(optyNumber, request); } const fetchedResult = await inFlightProducts.get(optyNumber); return { items: fetchedResult.items, cachedAt: fetchedResult.fetchedAt, fromCache: false }; } async function fetchAllOpptyProducts(optyNumber, accessToken) { let offset = 0; let page = 0; const items = []; while (page < OPPTY_PRODUCTS_MAX_PAGES) { const response = await fetch(createOpptyProductsUrl(optyNumber, offset), { method: "GET", credentials: "include", headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}` } }); if (!response.ok) { throw new Error(`requestOpptyProducts failed with status ${response.status}.`); } const responseData = await response.json(); const pageItems = Array.isArray(responseData.items) ? responseData.items : []; items.push(...pageItems); page += 1; if (!responseData.hasMore) { return items; } const responseOffset = Number(responseData.offset); const responseCount = Number(responseData.count); const receivedCount = Number.isFinite(responseCount) ? responseCount : pageItems.length; const currentOffset = Number.isFinite(responseOffset) ? responseOffset : offset; if (receivedCount <= 0) { throw new Error("requestOpptyProducts returned hasMore without additional results."); } offset = currentOffset + receivedCount; } throw new Error("requestOpptyProducts exceeded the pagination safety limit."); } function createOpptyProductsUrl(optyNumber, offset) { const url = new URL(`https://${ORACLE_DOMAIN}/crmRestApi/rest/rv:be91c002-2e5d-4ed2-a37e-e0c837bf141f/en/11.13.18.05:9/opportunities/${encodeURIComponent(optyNumber)}/child/ChildRevenue`); url.searchParams.set("onlyData", "true"); url.searchParams.set("q", "(SplitTypeCode!='DETAILCHILDSPLIT') AND (RecurTypeCode!='CHILDRECUR')"); url.searchParams.set("totalResults", "false"); url.searchParams.set("fields", OPPTY_PRODUCTS_FIELDS.join(",")); url.searchParams.set("orderBy", "CreationDate:desc"); url.searchParams.set("limit", String(OPPTY_PRODUCTS_PAGE_LIMIT)); url.searchParams.set("offset", String(offset)); return url.toString(); } function normalizeCacheMaxAge(value) { const maxAge = Number(value); const defaultMaxAge = 24 * 60 * 60 * 1000; const maximumMaxAge = 30 * 24 * 60 * 60 * 1000; return Number.isFinite(maxAge) && maxAge > 0 ? Math.min(maxAge, maximumMaxAge) : defaultMaxAge; } async function getProductsCache() { if (!productsCachePromise) { productsCachePromise = storageLocalGet(OPPTY_PRODUCTS_CACHE_KEY) .then((result) => result && typeof result[OPPTY_PRODUCTS_CACHE_KEY] === "object" ? result[OPPTY_PRODUCTS_CACHE_KEY] : {}) .catch(() => ({})); } return productsCachePromise; } function saveProductsCache(cache) { productsCacheWritePromise = productsCacheWritePromise .catch(() => {}) .then(() => storageLocalSet({ [OPPTY_PRODUCTS_CACHE_KEY]: cache })); return productsCacheWritePromise; } function sendProductsProgress(sender, message) { if (!sender || !sender.tab || !Number.isInteger(sender.tab.id)) { return Promise.resolve(); } const options = Number.isInteger(sender.frameId) ? { frameId: sender.frameId } : undefined; return tabsSendMessage(sender.tab.id, message, options).catch(() => {}); } async function refreshXsrfCookie(sender) { const startedAt = Date.now(); const previous = (await getXsrfTokenCookie()).cookie; const createProperties = { url: OPPORTUNITIES_LIST_URL, active: false }; if (sender && sender.tab && Number.isInteger(sender.tab.windowId)) { createProperties.windowId = sender.tab.windowId; } if (supportsFirefoxCookieStoreTabs() && sender && sender.tab && sender.tab.cookieStoreId) { createProperties.cookieStoreId = sender.tab.cookieStoreId; } let refreshTab; try { refreshTab = await tabsCreate(createProperties); const current = await waitForXsrfCookieChange(previous); const refreshed = hasCookieChanged(previous, current); return { ok: Boolean(current), refreshed, cookieName: current ? current.name : "", durationMs: Date.now() - startedAt, error: refreshed ? "" : "XSRF cookie was not created or changed before timeout." }; } finally { if (refreshTab && Number.isInteger(refreshTab.id)) { try { await tabsRemove(refreshTab.id); } catch (error) { // The temporary tab may already have been closed. } } } } async function waitForXsrfCookieChange(previousCookie) { const deadline = Date.now() + COOKIE_REFRESH_TIMEOUT_MS; let currentCookie = null; while (Date.now() < deadline) { currentCookie = (await getXsrfTokenCookie()).cookie; if (hasCookieChanged(previousCookie, currentCookie)) { return currentCookie; } await wait(COOKIE_REFRESH_POLL_MS); } return currentCookie; } function hasCookieChanged(previousCookie, currentCookie) { if (!currentCookie || !currentCookie.value) { return false; } if (!previousCookie) { return true; } return previousCookie.name !== currentCookie.name || previousCookie.value !== currentCookie.value || previousCookie.expirationDate !== currentCookie.expirationDate; } function supportsFirefoxCookieStoreTabs() { return Boolean( runtimeApi.runtime && typeof runtimeApi.runtime.getBrowserInfo === "function" ); } function tabsCreate(details) { if (runtimeApi.tabs.create.length <= 1) { return runtimeApi.tabs.create(details); } return new Promise((resolve, reject) => { runtimeApi.tabs.create(details, (tab) => { const lastError = runtimeApi.runtime.lastError; if (lastError) { reject(new Error(lastError.message)); return; } resolve(tab); }); }); } function tabsRemove(tabId) { if (runtimeApi.tabs.remove.length <= 1) { return runtimeApi.tabs.remove(tabId); } return new Promise((resolve, reject) => { runtimeApi.tabs.remove(tabId, () => { const lastError = runtimeApi.runtime.lastError; if (lastError) { reject(new Error(lastError.message)); return; } resolve(); }); }); } function tabsSendMessage(tabId, message, options) { if (typeof browser !== "undefined") { return options ? runtimeApi.tabs.sendMessage(tabId, message, options) : runtimeApi.tabs.sendMessage(tabId, message); } return new Promise((resolve, reject) => { const callback = (response) => { const lastError = runtimeApi.runtime.lastError; if (lastError) { reject(new Error(lastError.message)); return; } resolve(response); }; if (options) { runtimeApi.tabs.sendMessage(tabId, message, options, callback); } else { runtimeApi.tabs.sendMessage(tabId, message, callback); } }); } function storageLocalGet(key) { if (typeof browser !== "undefined") { return runtimeApi.storage.local.get(key); } return new Promise((resolve, reject) => { runtimeApi.storage.local.get(key, (result) => { const lastError = runtimeApi.runtime.lastError; if (lastError) { reject(new Error(lastError.message)); return; } resolve(result); }); }); } function storageLocalSet(value) { if (typeof browser !== "undefined") { return runtimeApi.storage.local.set(value); } return new Promise((resolve, reject) => { runtimeApi.storage.local.set(value, () => { const lastError = runtimeApi.runtime.lastError; if (lastError) { reject(new Error(lastError.message)); return; } resolve(); }); }); } function wait(durationMs) { return new Promise((resolve) => setTimeout(resolve, durationMs)); } async function getXsrfTokenCookie() { const lookupDetails = []; const allCookies = []; const stores = await getCookieStores(lookupDetails); for (const store of stores) { const exactCookie = await getExactCookieFromUrls(store.id, lookupDetails); if (exactCookie) { allCookies.push(exactCookie); } } for (const store of stores) { await collectCookies({ name: XSRF_COOKIE_NAME, storeId: store.id }, allCookies, lookupDetails); } for (const store of stores) { await collectCookies({ domain: ORACLE_DOMAIN, storeId: store.id }, allCookies, lookupDetails); await collectCookies({ domain: `.${ORACLE_DOMAIN}`, storeId: store.id }, allCookies, lookupDetails); } for (const store of stores) { for (const url of ORACLE_COOKIE_URLS) { await collectCookies({ url, storeId: store.id }, allCookies, lookupDetails); } } const uniqueCookies = dedupeCookies(allCookies); const xsrfCookies = uniqueCookies.filter((cookie) => cookie.name.startsWith(XSRF_COOKIE_PREFIX)); const exactCookie = xsrfCookies.find((cookie) => cookie.name === XSRF_COOKIE_NAME); const hostCookie = xsrfCookies.find((cookie) => cookie.domain === ORACLE_DOMAIN || cookie.domain === `.${ORACLE_DOMAIN}`); return { cookie: exactCookie || hostCookie || xsrfCookies[0] || null, matchedCookieNames: xsrfCookies.map((cookie) => `${cookie.name} (${cookie.domain}${cookie.path})`), lookupDetails }; } async function getExactCookieFromUrls(storeId, lookupDetails) { for (const url of ORACLE_COOKIE_URLS) { const cookie = await cookiesGet({ url, name: XSRF_COOKIE_NAME, storeId }); lookupDetails.push(`${JSON.stringify({ url, name: XSRF_COOKIE_NAME, storeId })} => ${cookie ? "found" : "not found"}`); if (cookie) { return cookie; } } return null; } async function getCookieStores(lookupDetails) { try { const stores = await cookiesGetAllCookieStores(); lookupDetails.push(`getAllCookieStores => ${stores.length} store(s)`); return stores.length ? stores : [{ id: undefined }]; } catch (error) { lookupDetails.push(`getAllCookieStores => unavailable (${error.message || "unknown error"})`); return [{ id: undefined }]; } } async function collectCookies(details, target, lookupDetails) { const cleanDetails = removeUndefinedValues(details); const cookies = await cookiesGetAll(cleanDetails); target.push(...cookies); lookupDetails.push(`${JSON.stringify(cleanDetails)} => ${cookies.length} cookie(s)`); } function dedupeCookies(cookies) { const seen = new Set(); return cookies.filter((cookie) => { const key = `${cookie.name}|${cookie.domain}|${cookie.path}|${cookie.storeId || ""}`; if (seen.has(key)) { return false; } seen.add(key); return true; }); } function cookiesGetAll(details) { if (runtimeApi.cookies.getAll.length <= 1) { return runtimeApi.cookies.getAll(details); } return new Promise((resolve, reject) => { runtimeApi.cookies.getAll(details, (cookies) => { const lastError = runtimeApi.runtime.lastError; if (lastError) { reject(new Error(lastError.message)); return; } resolve(cookies); }); }); } function cookiesGet(details) { const cleanDetails = removeUndefinedValues(details); if (runtimeApi.cookies.get.length <= 1) { return runtimeApi.cookies.get(cleanDetails); } return new Promise((resolve, reject) => { runtimeApi.cookies.get(cleanDetails, (cookie) => { const lastError = runtimeApi.runtime.lastError; if (lastError) { reject(new Error(lastError.message)); return; } resolve(cookie); }); }); } function cookiesGetAllCookieStores() { if (!runtimeApi.cookies.getAllCookieStores) { return Promise.resolve([{ id: undefined }]); } if (runtimeApi.cookies.getAllCookieStores.length === 0) { return runtimeApi.cookies.getAllCookieStores(); } return new Promise((resolve, reject) => { runtimeApi.cookies.getAllCookieStores((stores) => { const lastError = runtimeApi.runtime.lastError; if (lastError) { reject(new Error(lastError.message)); return; } resolve(stores); }); }); } function removeUndefinedValues(details) { return Object.fromEntries( Object.entries(details).filter((entry) => entry[1] !== undefined) ); } })();