From 577d183e48178f16a5eb5015e2069194175ed7f1 Mon Sep 17 00:00:00 2001 From: Paulo Porto Date: Mon, 4 May 2026 13:56:27 -0300 Subject: [PATCH] =?UTF-8?q?Implementa=C3=A7=C3=A3o=20do=20Arch=20Panel=20n?= =?UTF-8?q?o=20FuseWelcome?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- background.js | 473 +++++++++++++++++++++++++++++ content-script.js | 752 ++++++++++++++++++++++++++++++++++++++++++---- manifest.json | 52 +++- page-bridge.js | 278 ++++++++++++++++- 4 files changed, 1489 insertions(+), 66 deletions(-) diff --git a/background.js b/background.js index 05100c4..a571f9c 100644 --- a/background.js +++ b/background.js @@ -14,8 +14,94 @@ const COMCIP_TAB_URL_PATTERN = `${COMCIP_ORIGIN}/ic/builder/rt/oalset_semc/live*`; const COMCIP_TIME_ENTRY_TAB_URL_PATTERN = `${COMCIP_ORIGIN}/ic/builder/rt/oalset_timeentrymobile/live*`; +const SPA_FRAME_DNR_RULE_ID = 92001; + +void installSpaFrameHeaderRules(); + +chrome.runtime.onInstalled.addListener(() => { + void installSpaFrameHeaderRules(); +}); + +chrome.runtime.onStartup.addListener(() => { + void installSpaFrameHeaderRules(); +}); + +async function installSpaFrameHeaderRules() { + if (!chrome.declarativeNetRequest?.updateDynamicRules) { + return; + } + + await chrome.declarativeNetRequest.updateDynamicRules({ + removeRuleIds: [SPA_FRAME_DNR_RULE_ID], + addRules: [ + { + id: SPA_FRAME_DNR_RULE_ID, + priority: 1, + action: { + type: "modifyHeaders", + responseHeaders: [ + { + header: "x-frame-options", + operation: "remove", + }, + { + header: "frame-options", + operation: "remove", + }, + { + header: "content-security-policy", + operation: "remove", + }, + { + header: "content-security-policy-report-only", + operation: "remove", + }, + ], + }, + condition: { + initiatorDomains: ["eeho.fa.us2.oraclecloud.com"], + requestDomains: ["spa.oracle.com"], + resourceTypes: ["sub_frame"], + urlFilter: "||spa.oracle.com/oalcrm/web/api/g2m-consumer-application/ui/index.html", + }, + }, + ], + }).catch(() => {}); +} chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message?.type === "ARCH_PANEL_SPA_FRAME_FETCH") { + executeSpaFrameFetch({ + senderTabId: sender?.tab?.id, + url: message.url, + options: message.options || {}, + }) + .then(sendResponse) + .catch((error) => { + sendResponse({ + ok: false, + status: 0, + error: error instanceof Error ? error.message : String(error), + }); + }); + + return true; + } + + if (message?.type === "ARCH_PANEL_EXTENSION_FETCH") { + executeExtensionFetch(message.url, message.options || {}) + .then(sendResponse) + .catch((error) => { + sendResponse({ + ok: false, + status: 0, + error: error instanceof Error ? error.message : String(error), + }); + }); + + return true; + } + if ( !message || (message.type !== "ARCH_PANEL_COMCIP_POST" && @@ -44,6 +130,393 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { return true; }); +async function executeExtensionFetch(requestUrl, options = {}) { + const method = options.method || "GET"; + const headers = { + ...(options.headers || {}), + }; + const fetchOptions = { + method, + credentials: "include", + cache: "no-store", + headers, + }; + + if (options.body !== undefined && options.body !== null) { + fetchOptions.body = options.body; + } + + const response = await fetch(requestUrl, fetchOptions); + const text = await response.text(); + + return { + ok: response.ok, + status: response.status, + statusText: response.statusText, + headers: pickExtensionResponseHeaders(response.headers), + payload: parseExtensionResponseText(text), + debug: { + url: requestUrl, + headerKeys: Object.keys(headers), + extensionFetch: true, + }, + }; +} + +async function executeSpaFrameFetch({ senderTabId, url, options = {} }) { + if (!senderTabId) { + throw new Error("Unable to locate the current tab for the SPA frame request."); + } + + await installSpaFrameHeaderRules(); + await waitForSpaFramePresent(senderTabId); + await ensureSpaBridgeInFrames(senderTabId); + await waitForSpaFrameCapturedAuth(senderTabId, url); + + return executeSpaFrameScriptFetch(senderTabId, url, options); +} + +async function waitForSpaFramePresent(tabId) { + const startedAt = Date.now(); + let lastFrames = ""; + + while (Date.now() - startedAt < 45000) { + const frames = await getFrameHints(tabId).catch(() => []); + const spaFrame = frames.find((frame) => isSpaFrameHint(frame)); + + lastFrames = frames + .map((frame) => `${frame.frameId}:${frame.url || frame.name || "blank"}`) + .slice(0, 8) + .join(" | "); + + if (spaFrame) { + return spaFrame; + } + + await delay(500); + } + + throw new Error( + `SPA Workbench iframe was not found.${lastFrames ? ` Frames: ${lastFrames}` : ""}` + ); +} + +async function getFrameHints(tabId) { + const injectionResults = await chrome.scripting.executeScript({ + target: { tabId, allFrames: true }, + world: "MAIN", + func: () => ({ + href: String(window.location.href || ""), + name: String(window.name || ""), + }), + }); + + return (injectionResults || []).map((item) => ({ + frameId: item.frameId, + url: item.result?.href || "", + name: item.result?.name || "", + })); +} + +function isSpaFrameHint(frame) { + const url = String(frame?.url || ""); + const name = String(frame?.name || ""); + + return ( + url.startsWith("https://spa.oracle.com/") || + name === "arch-panel-extension-spa-frame" + ); +} + +async function ensureSpaBridgeInFrames(tabId) { + await chrome.scripting.executeScript({ + target: { tabId, allFrames: true }, + world: "MAIN", + files: ["page-bridge.js"], + }).catch(() => {}); +} + +async function waitForSpaFrameCapturedAuth(tabId, requestUrl) { + const startedAt = Date.now(); + let lastHeaderKeys = ""; + let lastSnapshot = null; + const requiresWwbToken = String(requestUrl || "").includes("/provider-proxy/wwb-provider/"); + + while (Date.now() - startedAt < 90000) { + await ensureSpaBridgeInFrames(tabId); + const snapshot = await readSpaFrameCapturedHeaders(tabId, requestUrl).catch(() => null); + const headerKeys = Object.keys(snapshot || {}); + + lastSnapshot = snapshot; + lastHeaderKeys = headerKeys.join(", "); + + if (requiresWwbToken && snapshot?.["wwb-provider-authorization"]) { + return snapshot; + } + + if ( + !requiresWwbToken && + (snapshot?.["wwb-provider-authorization"] || + snapshot?.["spa-ts-authorization"] || + snapshot?.["authorization"]) + ) { + return snapshot; + } + + await delay(1000); + } + + return lastSnapshot || {}; +} + +async function readSpaFrameCapturedHeaders(tabId, requestUrl) { + const injectionResults = await chrome.scripting.executeScript({ + target: { tabId, allFrames: true }, + world: "MAIN", + args: [requestUrl], + func: (url) => { + const href = String(window.location.href || ""); + const frameName = String(window.name || ""); + const isSpaFrame = + href.startsWith("https://spa.oracle.com/") || + frameName === "arch-panel-extension-spa-frame"; + + if (!isSpaFrame) { + return { + matched: false, + }; + } + + return { + matched: true, + headers: + window.__archPanelExtensionBridge?.buildHeaderSnapshot?.({ + url, + includeAllAuth: true, + }) || {}, + }; + }, + }); + + const matchedResult = (injectionResults || []) + .map((item) => item.result) + .find((result) => result?.matched); + + return matchedResult?.headers || null; +} + +async function executeSpaFrameScriptFetch(tabId, requestUrl, options = {}) { + const startedAt = Date.now(); + let lastError = ""; + + while (Date.now() - startedAt < 45000) { + await ensureSpaBridgeInFrames(tabId); + const response = await executeSpaFrameScriptFetchOnce(tabId, requestUrl, options).catch( + (error) => { + lastError = error instanceof Error ? error.message : String(error); + return null; + } + ); + + if (response?.matched) { + return response; + } + + await delay(700); + } + + throw new Error( + `SPA Workbench frame did not return a result.${lastError ? ` ${lastError}` : ""}` + ); +} + +async function executeSpaFrameScriptFetchOnce(tabId, requestUrl, options = {}) { + const injectionResults = await chrome.scripting.executeScript({ + target: { tabId, allFrames: true }, + world: "MAIN", + args: [ + requestUrl, + options?.method || "GET", + options?.body ?? null, + options?.headers || {}, + ], + func: async (requestUrl, requestMethod, requestBody, sourceHeaders) => { + function isSpaFrame() { + const href = String(window.location.href || ""); + const frameName = String(window.name || ""); + + return ( + href.startsWith("https://spa.oracle.com/") || + frameName === "arch-panel-extension-spa-frame" + ); + } + + if (!isSpaFrame()) { + return { + matched: false, + frameUrl: window.location.href, + frameName: window.name || "", + }; + } + + const bridge = window.__archPanelExtensionBridge; + + if (bridge?.fetchWithCapturedHeaders) { + const response = await bridge.fetchWithCapturedHeaders(requestUrl, { + method: requestMethod || "GET", + headers: sourceHeaders || {}, + body: requestBody, + }); + + return { + matched: true, + ...response, + debug: { + ...(response.debug || {}), + frameUrl: window.location.href, + frameName: window.name || "", + spaFrameFetch: true, + }, + }; + } + + function parseResponseText(text) { + if (!text) { + return null; + } + + try { + return JSON.parse(text); + } catch { + return text; + } + } + + function pickResponseHeaders(responseHeaders) { + const safeHeaders = {}; + const exposedHeaders = ["content-type", "x-request-id", "x-oracle-dms-ecid"]; + + for (const key of exposedHeaders) { + const value = responseHeaders.get(key); + + if (value) { + safeHeaders[key] = value; + } + } + + return safeHeaders; + } + + function normalizeHeaders(headers) { + const normalized = {}; + + for (const [key, value] of Object.entries(headers || {})) { + const normalizedKey = String(key || "").toLowerCase(); + + if (!normalizedKey || value === undefined || value === null || value === "") { + continue; + } + + normalized[normalizedKey] = String(value); + } + + normalized.accept = normalized.accept || "application/json, text/plain, */*"; + normalized["x-requested-with"] = + normalized["x-requested-with"] || "XMLHttpRequest"; + + return normalized; + } + + const headers = normalizeHeaders(sourceHeaders); + const normalizedMethod = String(requestMethod || "GET").toUpperCase(); + const fetchOptions = { + method: normalizedMethod, + credentials: "include", + cache: "no-store", + headers, + }; + + if (normalizedMethod !== "GET" && normalizedMethod !== "HEAD") { + if (!headers["content-type"]) { + headers["content-type"] = "application/json"; + } + + fetchOptions.body = requestBody; + } + + const response = await fetch(requestUrl, fetchOptions); + const text = await response.text(); + + return { + matched: true, + ok: response.ok, + status: response.status, + statusText: response.statusText, + headers: pickResponseHeaders(response.headers), + payload: parseResponseText(text), + debug: { + url: requestUrl, + frameUrl: window.location.href, + frameName: window.name || "", + headerKeys: Object.keys(headers), + spaFrameFetch: true, + }, + }; + }, + }); + + return (injectionResults || []) + .map((item) => item.result) + .find((result) => result?.matched) || null; +} + +function stripGenericAuthHeaders(headers = {}) { + const nextHeaders = {}; + + for (const [key, value] of Object.entries(headers || {})) { + const normalizedKey = String(key || "").toLowerCase(); + + if ( + normalizedKey === "authorization" || + normalizedKey === "x-id-token" || + normalizedKey === "spa-ts-authorization" + ) { + continue; + } + + nextHeaders[key] = value; + } + + return nextHeaders; +} + +function parseExtensionResponseText(text) { + if (!text) { + return null; + } + + try { + return JSON.parse(text); + } catch { + return text; + } +} + +function pickExtensionResponseHeaders(responseHeaders) { + const safeHeaders = {}; + const exposedHeaders = ["content-type", "x-request-id", "x-oracle-dms-ecid"]; + + for (const key of exposedHeaders) { + const value = responseHeaders.get(key); + + if (value) { + safeHeaders[key] = value; + } + } + + return safeHeaders; +} + async function requestComcipFromPage({ url, method = "POST", diff --git a/content-script.js b/content-script.js index 94025a1..2398a8c 100644 --- a/content-script.js +++ b/content-script.js @@ -4,6 +4,13 @@ "/oalcrm/web/api/g2m-consumer-application/ui/index.html"; const TARGET_PARAM = "ojr"; const TARGET_PARAM_VALUE = "workload_workbench"; + const TARGET_APP_URL = + "https://spa.oracle.com/oalcrm/web/api/g2m-consumer-application/ui/index.html?ojr=workload_workbench"; + const HCM_ORIGIN = "https://eeho.fa.us2.oraclecloud.com"; + const HCM_WELCOME_PATHNAME = "/hcmUI/faces/FuseWelcome"; + const HCM_MY_INFORMATION_APPS_GROUP_ID = "yourapps_groupNode_my_information"; + const WORKLIST_ORIGIN = "https://worklist.oracle.com"; + const WORKLIST_SAASUI_PATHNAME = "/oalapp/pub/worklist/saasui/index.html"; const USER_BUTTON_LABEL = "[[displayUser().fullName]]"; const ALLOWED_STAGE_TYPES = ["SQL", "Pipeline", "Upside", "Forecast"]; @@ -18,6 +25,9 @@ style: "arch-panel-extension-style", overlay: "arch-panel-extension-overlay", dialog: "arch-panel-extension-dialog", + hcmTile: "arch-panel-extension-hcm-tile", + spaFrame: "arch-panel-extension-spa-frame", + spaFrameHost: "arch-panel-extension-spa-frame-host", }; const CLASSES = { @@ -37,6 +47,7 @@ storeName: "datasets", datasetKey: "workload-dashboard", taskTypeKey: "time-management-task-types", + worklistTokenKey: "worklist-saasui-token", }; const DETAIL_SORT_KEYS = { @@ -56,6 +67,7 @@ resultType: "ARCH_PANEL_FETCH_RESULT", headersType: "ARCH_PANEL_HEADERS", headersResultType: "ARCH_PANEL_HEADERS_RESULT", + workbenchAuthHeadersType: "ARCH_PANEL_WORKBENCH_AUTH_HEADERS", readyType: "ARCH_PANEL_BRIDGE_READY", scriptId: "arch-panel-extension-page-bridge", }; @@ -63,6 +75,8 @@ const API_ENDPOINTS = { currentUser: "/oalcrm/web/api/identity-management/users/current", + worklistCurrentUser: + "https://worklist.oracle.com/oalapp/pub/wlp/svc/user-service/user", customerSummary: "/oalcrm/web/api/provider-proxy/wwb-provider/service/elastic/customers/listSummary", customerWorkloads: @@ -222,6 +236,7 @@ let requestCounter = 0; let bridgeReadyPromise = null; let cacheHydrationPromise = null; + let lastPersistedWorklistIframeUrl = ""; const pendingBridgeRequests = new Map(); function isTargetPage() { @@ -234,6 +249,15 @@ ); } + function isHcmWelcomePage() { + const currentUrl = new URL(window.location.href); + + return ( + currentUrl.origin === HCM_ORIGIN && + currentUrl.pathname === HCM_WELCOME_PATHNAME + ); + } + function scheduleSync() { if (syncFrame) { return; @@ -247,14 +271,28 @@ function syncButton() { const mount = document.getElementById(IDS.mount); + const hcmTile = document.getElementById(IDS.hcmTile); + + if (isHcmWelcomePage()) { + mount?.remove(); + ensureStyles(); + ensureModal(); + syncHcmTile(); + ensureSpaFrame(); + syncWorklistIframeToken(); + return; + } if (!isTargetPage()) { mount?.remove(); + hcmTile?.remove(); removeModal(); unlockScroll(); return; } + hcmTile?.remove(); + const header = document.querySelector(SELECTORS.header); const functionsContainer = header?.querySelector(SELECTORS.functions); const anchor = functionsContainer ? findUserAnchor(functionsContainer) : null; @@ -293,6 +331,107 @@ } } + function syncHcmTile() { + const appsGroup = document.getElementById(HCM_MY_INFORMATION_APPS_GROUP_ID); + const addTile = appsGroup?.querySelector(".flat-grid-cell.flat-grid-cell-addicon"); + + if (!addTile?.parentElement) { + document.getElementById(IDS.hcmTile)?.remove(); + return; + } + + const tile = document.getElementById(IDS.hcmTile) || createHcmTile(); + + if (tile.parentElement !== addTile.parentElement || tile.nextElementSibling !== addTile) { + addTile.parentElement.insertBefore(tile, addTile); + } + } + + function syncWorklistIframeToken(options = {}) { + if (!isHcmWelcomePage()) { + return null; + } + + for (const frame of document.querySelectorAll("iframe")) { + const tokenPayload = extractWorklistIframeToken(frame); + + if (tokenPayload?.token) { + persistWorklistIframeToken(tokenPayload, options); + return tokenPayload; + } + } + + return null; + } + + function extractWorklistIframeToken(frame) { + const rawSrc = cleanString(frame?.getAttribute?.("src") || frame?.src); + + if (!rawSrc) { + return null; + } + + try { + const url = new URL(rawSrc, window.location.href); + const token = cleanString(url.searchParams.get("token")); + + if ( + url.origin !== WORKLIST_ORIGIN || + url.pathname !== WORKLIST_SAASUI_PATHNAME || + !token + ) { + return null; + } + + return { + token, + url: url.toString(), + }; + } catch { + return null; + } + } + + function persistWorklistIframeToken(tokenPayload, options = {}) { + if (!options.force && tokenPayload.url === lastPersistedWorklistIframeUrl) { + return; + } + + lastPersistedWorklistIframeUrl = tokenPayload.url; + void writeCachedWorklistToken(tokenPayload); + } + + function createHcmTile() { + const template = document.createElement("template"); + template.innerHTML = ` +
+ +
+ `; + const tile = template.content.firstElementChild; + + tile.addEventListener("click", openHcmArchPanelTile); + tile.addEventListener("keydown", (event) => { + if (event.key === "Enter" || event.key === " ") { + openHcmArchPanelTile(event); + } + }); + tile.tabIndex = 0; + tile.setAttribute("role", "button"); + tile.setAttribute("aria-label", "Open Arch Panel"); + + return tile; + } + + function openHcmArchPanelTile(event) { + event.preventDefault(); + event.stopPropagation(); + openModal(); + } + function createMount() { const mount = document.createElement("div"); mount.id = IDS.mount; @@ -1160,11 +1299,7 @@