Implementação do Arch Panel no FuseWelcome
This commit is contained in:
473
background.js
473
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",
|
||||
|
||||
Reference in New Issue
Block a user