(() => { if (window.__archPanelPageBridgeInstalled) { return; } window.__archPanelPageBridgeInstalled = true; const MESSAGE_SOURCE = "arch-panel-extension"; const API_PATH_FRAGMENT = "/oalcrm/web/api/"; const EXTRA_CAPTURE_ORIGINS = new Set([ "https://comcipapic-oalprod.integration.ocp.oraclecloud.com", ]); const ALLOWED_TOP_MESSAGE_ORIGINS = new Set([ "https://eeho.fa.us2.oraclecloud.com", "https://spa.oracle.com", ]); const AUTH_STORAGE_HINT_PATTERN = /(authorization|token|wwb|wlwb|provider|spa-ts|spats)/i; const SAFE_HEADER_BLOCKLIST = new Set([ "accept-encoding", "accept-language", "connection", "content-length", "cookie", "host", "origin", "referer", "sec-ch-ua", "sec-ch-ua-mobile", "sec-ch-ua-platform", "sec-fetch-dest", "sec-fetch-mode", "sec-fetch-site", "user-agent", ]); const bridgeState = { globalHeaders: new Map(), scopedHeaders: new Map(), }; function getSafeTargetOrigin() { const origin = String(window.location?.origin || ""); return origin && origin !== "null" ? origin : "*"; } function safePostMessage(payload, targetWindow = window, targetOrigin = getSafeTargetOrigin()) { try { targetWindow.postMessage(payload, targetOrigin); return true; } catch { return false; } } function getAllowedTopMessageOrigin() { const candidates = []; try { const referrerOrigin = document.referrer ? new URL(document.referrer).origin : ""; if (referrerOrigin) { candidates.push(referrerOrigin); } } catch { // Referrer parsing is best-effort; fall back to ancestorOrigins/current origin. } try { const ancestorOrigins = Array.from(window.location?.ancestorOrigins || []); candidates.push(...ancestorOrigins); } catch { // ancestorOrigins is browser-specific and may be unavailable. } if (window.top === window) { candidates.push(getSafeTargetOrigin()); } for (const origin of candidates) { if (ALLOWED_TOP_MESSAGE_ORIGINS.has(origin)) { return origin; } } return ""; } function safePostToTop(payload) { const targetOrigin = getAllowedTopMessageOrigin(); if (!targetOrigin) { return false; } return safePostMessage(payload, window.top, targetOrigin); } const nativeFetch = window.fetch.bind(window); const originalOpen = window.XMLHttpRequest.prototype.open; const originalSetRequestHeader = window.XMLHttpRequest.prototype.setRequestHeader; const originalSend = window.XMLHttpRequest.prototype.send; function isIntegrationOrigin(url) { try { return ( new URL(url, window.location.origin).origin === "https://comcipapic-oalprod.integration.ocp.oraclecloud.com" ); } catch { return false; } } function normalizeUrl(input) { try { return new URL(input, window.location.origin).toString(); } catch { return ""; } } function shouldCapture(url) { if (!url) { return false; } if (url.includes(API_PATH_FRAGMENT)) { return true; } try { return EXTRA_CAPTURE_ORIGINS.has(new URL(url).origin); } catch { return false; } } function normalizeHeaders(input) { const normalized = new Map(); if (!input) { return normalized; } const headers = new Headers(input); headers.forEach((value, key) => { const lowerKey = key.toLowerCase(); if (SAFE_HEADER_BLOCKLIST.has(lowerKey)) { return; } if (lowerKey.startsWith("sec-")) { return; } normalized.set(lowerKey, value); }); return normalized; } function getScopeKeys(url) { const normalizedUrl = normalizeUrl(url); if (!normalizedUrl) { return []; } try { const parsedUrl = new URL(normalizedUrl); const path = parsedUrl.pathname; const keys = [`path:${path}`]; const providerProxyMatch = path.match( /\/provider-proxy\/([^/]+)\// ); const serviceMatch = path.match( /\/provider-proxy\/[^/]+\/service\/([^/]+)\// ); if (path.includes("/provider-proxy/")) { keys.push("scope:provider-proxy"); } if (path.includes("/identity-management/")) { keys.push("scope:identity-management"); } if (path.includes("/workbench-proxy/")) { keys.push("scope:workbench-proxy"); } if (path.includes("/elastic/")) { keys.push("scope:elastic"); } if (providerProxyMatch?.[1]) { keys.push(`provider:${providerProxyMatch[1]}`); } if (serviceMatch?.[1]) { keys.push(`service:${serviceMatch[1]}`); } keys.push("scope:api"); return keys; } catch { return []; } } function mergeIntoBucket(bucket, headers) { headers.forEach((value, key) => { bucket.set(key, value); }); } function isAuthorizationLikeHeader(key) { const normalizedKey = String(key || "").toLowerCase(); return ( normalizedKey === "authorization" || normalizedKey.endsWith("-authorization") || normalizedKey.includes("token") ); } function rememberHeaders(url, input) { const headers = normalizeHeaders(input); if (headers.size === 0) { return; } headers.forEach((value, key) => { if (!isAuthorizationLikeHeader(key)) { bridgeState.globalHeaders.set(key, value); } }); for (const scopeKey of getScopeKeys(url)) { const bucket = bridgeState.scopedHeaders.get(scopeKey) || new Map(); mergeIntoBucket(bucket, headers); bridgeState.scopedHeaders.set(scopeKey, bucket); } publishCapturedAuthHeaders(url, headers); } function publishCapturedAuthHeaders(url, headers) { const authHeaders = {}; headers.forEach((value, key) => { if (isAuthorizationLikeHeader(key)) { authHeaders[key] = value; } }); if (!Object.keys(authHeaders).length) { return; } safePostToTop( { source: MESSAGE_SOURCE, type: "ARCH_PANEL_WORKBENCH_AUTH_HEADERS", url: normalizeUrl(url), headers: authHeaders, } ); } function buildHeaders(url, input, body) { const merged = new Headers(); const activeScopeKeys = getScopeKeys(url); bridgeState.globalHeaders.forEach((value, key) => { merged.set(key, value); }); for (const scopeKey of activeScopeKeys) { const bucket = bridgeState.scopedHeaders.get(scopeKey); if (!bucket) { continue; } bucket.forEach((value, key) => { merged.set(key, value); }); } normalizeHeaders(input).forEach((value, key) => { merged.set(key, value); }); if (activeScopeKeys.includes("scope:provider-proxy")) { collectStoredAuthHeaders().forEach((value, key) => { if (!merged.has(key)) { merged.set(key, value); } }); } if (!merged.has("accept")) { merged.set("accept", "application/json, text/plain, */*"); } if (!merged.has("x-requested-with")) { merged.set("x-requested-with", "XMLHttpRequest"); } if (activeScopeKeys.includes("scope:provider-proxy")) { const allowedProviderHeaders = new Set([ "accept", "authorization", "content-type", "spa-ts-authorization", "wwb-provider-authorization", "x-id-token", "x-requested-with", ]); for (const key of Array.from(merged.keys())) { if (key.endsWith("-authorization") && !allowedProviderHeaders.has(key)) { merged.delete(key); } } if (merged.has("wwb-provider-authorization")) { merged.delete("authorization"); } } if (isIntegrationOrigin(url)) { const allowedIntegrationHeaders = new Set([ "accept", "authorization", "content-type", "spa-ts-authorization", "wwb-provider-authorization", "x-id-token", "x-requested-with", ]); for (const key of Array.from(merged.keys())) { if (!allowedIntegrationHeaders.has(key)) { merged.delete(key); } } if (merged.has("wwb-provider-authorization")) { merged.delete("authorization"); } } if (!body) { merged.delete("content-type"); } return merged; } function buildHeaderSnapshot(options = {}) { const snapshot = new Headers(); const scopeUrl = options.url || window.location.href; const merged = buildHeaders(scopeUrl, options.headers, options.body); merged.forEach((value, key) => { snapshot.set(key, value); }); if (options.includeAllAuth) { bridgeState.scopedHeaders.forEach((bucket) => { bucket.forEach((value, key) => { if (isAuthorizationLikeHeader(key)) { snapshot.set(key, value); } }); }); collectStoredAuthHeaders().forEach((value, key) => { if (!snapshot.has(key)) { snapshot.set(key, value); } }); } return Object.fromEntries(snapshot.entries()); } function collectStoredAuthHeaders() { const headers = new Map(); const candidates = []; for (const storage of [window.localStorage, window.sessionStorage]) { try { for (let index = 0; index < storage.length; index += 1) { const key = storage.key(index) || ""; const value = storage.getItem(key) || ""; if (isAuthStorageCandidate(key, value)) { candidates.push([key, value]); } } } catch { // Ignore storage access restrictions. } } for (const [key, value] of candidates) { collectAuthFromStorageEntry(headers, key, value); } return headers; } function isAuthStorageCandidate(key, value) { const normalizedKey = String(key || ""); const normalizedValue = String(value || "").trim(); if (!normalizedKey && !normalizedValue) { return false; } if (AUTH_STORAGE_HINT_PATTERN.test(normalizedKey)) { return true; } if (looksLikeJwt(normalizedValue)) { return true; } return AUTH_STORAGE_HINT_PATTERN.test(normalizedValue); } function collectAuthFromStorageEntry(headers, key, value) { const normalizedKey = String(key || "").toLowerCase(); const normalizedValue = String(value || "").trim(); if (!normalizedValue) { return; } if ( normalizedKey === "wwb-provider-authorization" || normalizedKey.includes("wwb-provider-authorization") ) { headers.set("wwb-provider-authorization", normalizedValue); } if ( normalizedKey === "spa-ts-authorization" || normalizedKey.includes("spa-ts-authorization") ) { headers.set("spa-ts-authorization", normalizedValue); } if (normalizedKey === "authorization") { headers.set("authorization", normalizedValue); } collectAuthFromTokenString(headers, normalizedValue); if (!(normalizedValue.startsWith("{") || normalizedValue.startsWith("["))) { return; } try { collectAuthFromObject(headers, JSON.parse(normalizedValue)); } catch { // Ignore non-JSON values. } } function collectAuthFromObject(headers, value, depth = 0) { if (!value || typeof value !== "object" || depth > 5) { return; } if (Array.isArray(value)) { value.forEach((item) => collectAuthFromObject(headers, item, depth + 1)); return; } for (const [key, entryValue] of Object.entries(value)) { const normalizedKey = String(key || "").toLowerCase(); if (typeof entryValue === "string" && entryValue.trim()) { collectAuthFromTokenString(headers, entryValue.trim()); if ( normalizedKey === "wwb-provider-authorization" || normalizedKey.includes("wwbprovider") || normalizedKey.includes("wwb-provider") ) { headers.set("wwb-provider-authorization", entryValue.trim()); } if ( normalizedKey === "spa-ts-authorization" || normalizedKey.includes("spats") || normalizedKey.includes("spa-ts") ) { headers.set("spa-ts-authorization", entryValue.trim()); } if (normalizedKey === "authorization") { headers.set("authorization", entryValue.trim()); } } collectAuthFromObject(headers, entryValue, depth + 1); } } function collectAuthFromTokenString(headers, value) { const token = String(value || "").trim(); if (!looksLikeJwt(token)) { return; } const payload = decodeJwtPayload(token); if (!payload) { return; } const issuedFor = String(payload.issuedFor || payload.aud || "").toUpperCase(); const authorities = Array.isArray(payload.authorities) ? payload.authorities.map((authority) => String(authority || "").toUpperCase()) : []; const scopes = Array.isArray(payload.scope) ? payload.scope.map((scope) => String(scope || "").toUpperCase()) : String(payload.scope || "") .split(/\s+/) .map((scope) => scope.toUpperCase()) .filter(Boolean); const isWwbToken = issuedFor.includes("WWB") || authorities.some((authority) => authority.startsWith("WLWB.")) || scopes.some((scope) => scope.includes("WWB") || scope.includes("WLWB")); if (isWwbToken && !headers.has("wwb-provider-authorization")) { headers.set("wwb-provider-authorization", token); } } function looksLikeJwt(value) { return /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test( String(value || "").trim() ); } function decodeJwtPayload(token) { try { const payloadPart = String(token).split(".")[1]; const base64 = payloadPart.replace(/-/g, "+").replace(/_/g, "/"); const paddedBase64 = base64.padEnd( base64.length + (4 - (base64.length % 4 || 4)), "=" ); const json = decodeURIComponent( Array.from(atob(paddedBase64), (char) => { return `%${char.charCodeAt(0).toString(16).padStart(2, "0")}`; }).join("") ); return JSON.parse(json); } catch { return null; } } async function fetchWithCapturedHeaders(url, options = {}) { const mergedHeaders = buildHeaders(url, options.headers, options.body); const response = await nativeFetch(url, { method: options.method || "GET", credentials: "include", cache: "no-store", headers: mergedHeaders, body: options.body, }); const rawText = await response.text(); let payload; try { payload = rawText ? JSON.parse(rawText) : null; } catch { payload = rawText; } return { ok: response.ok, status: response.status, statusText: response.statusText, payload, debug: { url, headerKeys: Array.from(mergedHeaders.keys()), scopeKeys: getScopeKeys(url), spaTabFetch: true, }, }; } window.__archPanelExtensionBridge = { buildHeaderSnapshot, fetchWithCapturedHeaders, }; const trackedFetch = async function archPanelTrackedFetch(input, init) { const url = normalizeUrl(typeof input === "string" ? input : input?.url); if (shouldCapture(url)) { rememberHeaders(url, init?.headers || input?.headers); } return nativeFetch(input, init); }; window.fetch = trackedFetch; window.XMLHttpRequest.prototype.open = function archPanelTrackedOpen(method, url, ...rest) { this.__archPanelTrackedUrl = normalizeUrl(url); this.__archPanelTrackedHeaders = new Headers(); return originalOpen.call(this, method, url, ...rest); }; window.XMLHttpRequest.prototype.setRequestHeader = function archPanelTrackedSetRequestHeader(name, value) { if (this.__archPanelTrackedHeaders) { this.__archPanelTrackedHeaders.set(name, value); } return originalSetRequestHeader.call(this, name, value); }; window.XMLHttpRequest.prototype.send = function archPanelTrackedSend(body) { if (shouldCapture(this.__archPanelTrackedUrl)) { rememberHeaders(this.__archPanelTrackedUrl, this.__archPanelTrackedHeaders); } return originalSend.call(this, body); }; function requestViaOriginalXhr(url, options, headers) { return new Promise((resolve, reject) => { const xhr = new window.XMLHttpRequest(); originalOpen.call(xhr, options?.method || "GET", url, true); xhr.withCredentials = true; xhr.responseType = "text"; xhr.timeout = 60000; headers.forEach((value, key) => { originalSetRequestHeader.call(xhr, key, value); }); xhr.onload = () => { resolve({ ok: xhr.status >= 200 && xhr.status < 300, status: xhr.status, text: xhr.responseText || "", }); }; xhr.onerror = () => { reject(new Error("Failed to fetch")); }; xhr.onabort = () => { reject(new Error("Request aborted")); }; xhr.ontimeout = () => { reject(new Error(`Timed out while requesting ${url}`)); }; originalSend.call(xhr, options?.body ?? null); }); } window.addEventListener("message", async (event) => { if (event.source !== window) { return; } const message = event.data; if (!message || message.source !== MESSAGE_SOURCE) { return; } if (message.type === "ARCH_PANEL_HEADERS") { safePostMessage( { source: MESSAGE_SOURCE, type: "ARCH_PANEL_HEADERS_RESULT", requestId: message.requestId, headers: buildHeaderSnapshot(message.options), } ); return; } if (message.type !== "ARCH_PANEL_FETCH") { return; } try { const mergedHeaders = buildHeaders( message.url, message.options?.headers, message.options?.body ); const useXhr = message.options?.transport === "xhr"; const response = useXhr ? await requestViaOriginalXhr(message.url, message.options, mergedHeaders) : await (async () => { const activeFetch = typeof window.fetch === "function" ? window.fetch.bind(window) : nativeFetch; const fetchResponse = await activeFetch(message.url, { method: message.options?.method || "GET", credentials: "include", headers: mergedHeaders, body: message.options?.body, }); return { ok: fetchResponse.ok, status: fetchResponse.status, text: await fetchResponse.text(), }; })(); const rawText = response.text; let payload; try { payload = rawText ? JSON.parse(rawText) : null; } catch { payload = rawText; } safePostMessage( { source: MESSAGE_SOURCE, type: "ARCH_PANEL_FETCH_RESULT", requestId: message.requestId, ok: response.ok, status: response.status, debug: { headerKeys: Array.from(mergedHeaders.keys()), scopeKeys: getScopeKeys(message.url), transport: useXhr ? "xhr" : "fetch", }, payload, } ); } catch (error) { safePostMessage( { source: MESSAGE_SOURCE, type: "ARCH_PANEL_FETCH_RESULT", requestId: message.requestId, ok: false, status: 0, debug: { scopeKeys: getScopeKeys(message.url), transport: message.options?.transport || "fetch", }, error: error instanceof Error ? error.message : String(error), } ); } }); safePostMessage( { source: MESSAGE_SOURCE, type: "ARCH_PANEL_BRIDGE_READY", } ); })();