Files
arch-panel-extension-browser/background.js
2026-05-04 13:56:27 -03:00

1327 lines
35 KiB
JavaScript

const COMCIP_ORIGIN = "https://comcipapic-oalprod.integration.ocp.oraclecloud.com";
const COMCIP_APP_URL =
`${COMCIP_ORIGIN}/ic/builder/rt/oalset_semc/live/webApps/Dashboard/?page=shell&shell=main&main=service-requests-detailed-view`;
const COMCIP_QUERY_URL =
`${COMCIP_ORIGIN}/ic/builder/rt/oalset_semc/live;profile=PROD/services/auth/1.1/proxy/oalsetCRMRestAPIElastic/uri/https/eeho.fa.us2.oraclecloud.com/crmRestApi/searchResources/latest/custom-actions/queries`;
const COMCIP_CLIENT_ID_PROBE_URL =
`${COMCIP_ORIGIN}/ic/builder/rt/oalset_semc/live;profile=PROD/services/auth/1.1/proxy/oalsetSeaaSOKECustomRestAPI/uri/https/gxpap.oracle.com/oalcrm/service/set/seaas/crm/countries`;
const COMCIP_TIME_ENTRY_APP_URL =
`${COMCIP_ORIGIN}/ic/builder/rt/oalset_timeentrymobile/live/webApps/timeentry/`;
const COMCIP_TIME_ENTRY_CLIENT_ID_PROBE_URL =
`${COMCIP_ORIGIN}/ic/builder/rt/oalset_timeentrymobile/live;profile=PROD/services/auth/1.1/proxy/oke_seaas/uri/https/gxpap.oracle.com/oalcrm/service/set/seaas/crm/lookups?lookupType=SCTA_TASK_TYPE&type=CUSTOM`;
const COMCIP_TIME_ENTRY_APP_VERSION = "version_1774016034953";
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" &&
message.type !== "ARCH_PANEL_COMCIP_REQUEST")
) {
return false;
}
requestComcipFromPage({
url: message.url || COMCIP_QUERY_URL,
method: message.method || "POST",
payload: message.payload,
headers: message.headers,
senderTabId: sender?.tab?.id,
useTimeEntryFrame: Boolean(message.useTimeEntryFrame),
})
.then(sendResponse)
.catch((error) => {
sendResponse({
ok: false,
status: 0,
error: error instanceof Error ? error.message : String(error),
});
});
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",
payload,
headers = {},
senderTabId,
useTimeEntryFrame = false,
}) {
const route = getComcipRoute(url);
if (route.useSenderFrame || useTimeEntryFrame) {
if (!senderTabId) {
throw new Error("Unable to locate the current dashboard tab for COMCIP Time Entry.");
}
await waitForComcipFrameSessionReady(senderTabId, headers, route);
let response = await executeComcipFetchFromFrame(
senderTabId,
url,
method,
payload,
headers,
route
);
if (isAuthorizationFailure(response) || isOutdatedApplicationFailure(response)) {
await delay(1500);
await waitForComcipFrameSessionReady(senderTabId, headers, route);
response = await executeComcipFetchFromFrame(
senderTabId,
url,
method,
payload,
headers,
route
);
}
return response;
}
const activeTab = await captureActiveTab();
const target = await getComcipTab(route, activeTab);
try {
await restoreActiveTab(activeTab);
await waitForComcipTabReady(target.tab.id);
await waitForComcipSessionReady(target.tab.id, headers, route);
let response = await executeComcipFetch(
target.tab.id,
url,
method,
payload,
headers,
route
);
if (isAuthorizationFailure(response)) {
await delay(1500);
await waitForComcipSessionReady(target.tab.id, headers, route);
response = await executeComcipFetch(
target.tab.id,
url,
method,
payload,
headers,
route
);
}
return response;
} finally {
if (target.created && target.tab.id) {
await chrome.tabs.remove(target.tab.id).catch(() => {});
}
await restoreActiveTab(activeTab);
}
}
function getComcipRoute(requestUrl) {
const normalizedUrl = String(requestUrl || "");
if (normalizedUrl.includes("/ic/builder/rt/oalset_timeentrymobile/live")) {
return {
appUrl: COMCIP_TIME_ENTRY_APP_URL,
clientIdProbeUrl: COMCIP_TIME_ENTRY_CLIENT_ID_PROBE_URL,
tabPattern: COMCIP_TIME_ENTRY_TAB_URL_PATTERN,
useSenderFrame: true,
appVersion: COMCIP_TIME_ENTRY_APP_VERSION,
};
}
return {
appUrl: COMCIP_APP_URL,
clientIdProbeUrl: COMCIP_CLIENT_ID_PROBE_URL,
tabPattern: COMCIP_TAB_URL_PATTERN,
};
}
async function getComcipTab(route, activeTab) {
const tabs = await chrome.tabs.query({ url: route.tabPattern });
const existing = tabs.find((tab) => {
const tabUrl = String(tab.url || "");
return tab.id && !tab.discarded && !tabUrl.includes("/services/");
});
if (existing) {
return {
tab: existing,
created: false,
};
}
if (route.requireExistingTab) {
throw new Error(
"A sessao COMCIP Time Entry nao esta pronta. Abra/autentique o Time Entry uma vez e tente novamente."
);
}
const created = await chrome.tabs.create({
url: route.appUrl,
active: false,
windowId: activeTab?.windowId,
});
await restoreActiveTab(activeTab);
return {
tab: created,
created: true,
};
}
async function captureActiveTab() {
const [activeTab] = await chrome.tabs.query({
active: true,
currentWindow: true,
});
if (!activeTab?.id) {
return null;
}
return {
id: activeTab.id,
windowId: activeTab.windowId,
};
}
async function restoreActiveTab(activeTab) {
if (!activeTab?.id) {
return;
}
await chrome.windows.update(activeTab.windowId, { focused: true }).catch(() => {});
await chrome.tabs.update(activeTab.id, { active: true }).catch(() => {});
}
async function waitForComcipTabReady(tabId) {
const startedAt = Date.now();
while (Date.now() - startedAt < 45000) {
const tab = await chrome.tabs.get(tabId);
const url = String(tab.url || "");
if (tab.status === "complete" && url.startsWith(COMCIP_ORIGIN)) {
await delay(1000);
return;
}
await delay(500);
}
throw new Error("Timed out while loading the COMCIP origin page.");
}
async function waitForComcipSessionReady(tabId, headers = {}, route) {
const startedAt = Date.now();
let lastStatus = "";
while (Date.now() - startedAt < 60000) {
const probe = await executeComcipProbe(tabId, headers, route).catch((error) => ({
ok: false,
status: 0,
error: error instanceof Error ? error.message : String(error),
}));
if (probe?.ok && probe?.appBuilderClientId) {
return probe;
}
lastStatus = probe?.status
? `${probe.status} ${probe.statusText || ""}`.trim()
: probe?.error || "not ready";
await delay(1000);
}
throw new Error(`Timed out while waiting for COMCIP authenticated session (${lastStatus}).`);
}
async function waitForComcipFrameSessionReady(tabId, headers = {}, route) {
const startedAt = Date.now();
let lastStatus = "";
while (Date.now() - startedAt < 60000) {
const probe = await executeComcipFrameProbe(tabId, headers, route).catch((error) => ({
ok: false,
status: 0,
error: error instanceof Error ? error.message : String(error),
}));
if (probe?.ok && probe?.matched) {
return probe;
}
lastStatus = probe?.status
? `${probe.status} ${probe.statusText || ""}`.trim()
: probe?.error || "not ready";
await delay(1000);
}
throw new Error(
`Timed out while waiting for COMCIP Time Entry frame session (${lastStatus}).`
);
}
async function executeComcipFrameProbe(tabId, headers = {}, route) {
const injectionResults = await chrome.scripting.executeScript({
target: { tabId, allFrames: true },
world: "MAIN",
args: [
route?.clientIdProbeUrl || COMCIP_CLIENT_ID_PROBE_URL,
headers || {},
route?.appVersion || "",
],
func: async (probeUrl, sourceHeaders, routeAppVersion) => {
function isTimeEntryFrame() {
const href = String(window.location.href || "");
const frameName = String(window.name || "");
const isComcipOrigin = href.startsWith(
"https://comcipapic-oalprod.integration.ocp.oraclecloud.com/"
);
return (
(frameName === "arch-panel-extension-comcip-timeentry-frame" &&
isComcipOrigin) ||
href.includes("oalset_timeentrymobile") ||
href.includes("/webApps/timeentry")
);
}
if (!isTimeEntryFrame()) {
return {
matched: false,
href: window.location.href,
frameName: window.name || "",
};
}
const response = await fetch(probeUrl, {
method: "GET",
credentials: "include",
cache: "no-store",
headers: {
accept: "*/*",
authorization: "Session",
"accept-language":
sourceHeaders["accept-language"] || navigator.language || "pt-BR",
"vb-proxy-header-rest-framework-version": "3",
"x-vb-application-version":
routeAppVersion || sourceHeaders["x-vb-application-version"] || "",
},
});
return {
matched: true,
ok: response.ok,
status: response.status,
statusText: response.statusText,
appBuilderClientId: response.headers.get("x-appbuilder-client-id") || "",
href: window.location.href,
frameName: window.name || "",
};
},
});
const matchedResult = (injectionResults || [])
.map((item) => item.result)
.find((result) => result?.matched);
if (!matchedResult) {
const frameHints = (injectionResults || [])
.map((item) => item.result)
.filter(Boolean)
.map((result) => result.href || result.frameName || "unknown")
.slice(0, 5)
.join(" | ");
throw new Error(
`COMCIP Time Entry frame was not found in the dashboard tab.${
frameHints ? ` Frames: ${frameHints}` : ""
}`
);
}
return matchedResult;
}
async function executeComcipProbe(tabId, headers = {}, route) {
const [injectionResult] = await chrome.scripting.executeScript({
target: { tabId },
world: "MAIN",
args: [route?.clientIdProbeUrl || COMCIP_CLIENT_ID_PROBE_URL, headers || {}],
func: async (probeUrl, sourceHeaders) => {
const APP_VERSION = "version_1754044416761";
const response = await fetch(probeUrl, {
method: "GET",
credentials: "include",
cache: "no-store",
headers: {
accept: "*/*",
authorization: "Session",
"accept-language":
sourceHeaders["accept-language"] || navigator.language || "pt-BR",
"x-vb-application-version":
sourceHeaders["x-vb-application-version"] || APP_VERSION,
},
});
return {
ok: response.ok,
status: response.status,
statusText: response.statusText,
appBuilderClientId: response.headers.get("x-appbuilder-client-id") || "",
href: window.location.href,
};
},
});
if (!injectionResult) {
throw new Error("COMCIP probe did not return a result.");
}
return injectionResult.result;
}
async function executeComcipFetch(tabId, requestUrl, method, payload, headers, route) {
const [injectionResult] = await chrome.scripting.executeScript({
target: { tabId },
world: "MAIN",
args: [
requestUrl,
method || "POST",
payload || null,
headers || {},
route?.clientIdProbeUrl || COMCIP_CLIENT_ID_PROBE_URL,
route?.appVersion || "",
],
func: async (
requestUrl,
requestMethod,
requestPayload,
sourceHeaders,
clientIdProbeUrl,
routeAppVersion
) => {
const APP_VERSION = "version_1754044416761";
function parseResponseText(text) {
if (!text) {
return null;
}
try {
return JSON.parse(text);
} catch {
return text;
}
}
function pickResponseHeaders(headers) {
const safeHeaders = {};
const exposedHeaders = [
"content-type",
"x-appbuilder-client-id",
"x-invalid-appbuilder-client-id",
"x-appbuilder-repeat-request",
"vb-proxy-status-actual",
"vb-proxy-version",
];
for (const key of exposedHeaders) {
const value = headers.get(key);
if (value) {
safeHeaders[key] = value;
}
}
return safeHeaders;
}
async function resolveAppBuilderClientId() {
try {
const response = await fetch(clientIdProbeUrl, {
method: "GET",
credentials: "include",
cache: "no-store",
headers: {
accept: "*/*",
authorization: "Session",
"accept-language":
sourceHeaders["accept-language"] || navigator.language || "pt-BR",
"x-vb-application-version":
sourceHeaders["x-vb-application-version"] || APP_VERSION,
},
});
return response.headers.get("x-appbuilder-client-id") || "";
} catch {
return "";
}
}
const appBuilderClientId =
sourceHeaders["x-appbuilder-client-id"] || (await resolveAppBuilderClientId());
const requestHeaders = {
accept: "*/*",
authorization: "Session",
"vb-proxy-header-preference": "transient",
"x-vb-application-version":
sourceHeaders["x-vb-application-version"] || APP_VERSION,
"accept-language":
sourceHeaders["accept-language"] || navigator.language || "pt-BR",
};
if (appBuilderClientId) {
requestHeaders["x-appbuilder-client-id"] = appBuilderClientId;
}
const normalizedMethod = String(requestMethod || "POST").toUpperCase();
const fetchOptions = {
method: normalizedMethod,
credentials: "include",
cache: "no-store",
headers: requestHeaders,
};
if (normalizedMethod !== "GET" && normalizedMethod !== "HEAD") {
requestHeaders["content-type"] = "application/json";
fetchOptions.body = JSON.stringify(requestPayload || {});
}
const response = await fetch(requestUrl, {
...fetchOptions,
});
const text = await response.text();
return {
ok: response.ok,
status: response.status,
statusText: response.statusText,
headers: pickResponseHeaders(response.headers),
payload: parseResponseText(text),
debug: {
href: window.location.href,
origin: window.location.origin,
headerKeys: Object.keys(requestHeaders),
},
};
},
});
if (!injectionResult) {
throw new Error("COMCIP page script did not return a result.");
}
return injectionResult.result;
}
async function executeComcipFetchFromFrame(
tabId,
requestUrl,
method,
payload,
headers,
route
) {
const injectionResults = await chrome.scripting.executeScript({
target: { tabId, allFrames: true },
world: "MAIN",
args: [
requestUrl,
method || "POST",
payload || null,
headers || {},
route?.clientIdProbeUrl || COMCIP_CLIENT_ID_PROBE_URL,
route?.appVersion || "",
],
func: async (
requestUrl,
requestMethod,
requestPayload,
sourceHeaders,
clientIdProbeUrl,
routeAppVersion
) => {
function isTimeEntryFrame() {
const href = String(window.location.href || "");
const frameName = String(window.name || "");
const isComcipOrigin = href.startsWith(
"https://comcipapic-oalprod.integration.ocp.oraclecloud.com/"
);
return (
(frameName === "arch-panel-extension-comcip-timeentry-frame" &&
isComcipOrigin) ||
href.includes("oalset_timeentrymobile") ||
href.includes("/webApps/timeentry")
);
}
if (!isTimeEntryFrame()) {
return {
matched: false,
href: window.location.href,
frameName: window.name || "",
};
}
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-appbuilder-client-id",
"x-invalid-appbuilder-client-id",
"x-appbuilder-repeat-request",
"vb-proxy-status-actual",
"vb-proxy-version",
];
for (const key of exposedHeaders) {
const value = responseHeaders.get(key);
if (value) {
safeHeaders[key] = value;
}
}
return safeHeaders;
}
async function resolveAppBuilderClientId() {
try {
const response = await fetch(clientIdProbeUrl, {
method: "GET",
credentials: "include",
cache: "no-store",
headers: {
accept: "*/*",
authorization: "Session",
"accept-language":
sourceHeaders["accept-language"] || navigator.language || "pt-BR",
"vb-proxy-header-rest-framework-version": "3",
"x-vb-application-version":
routeAppVersion || sourceHeaders["x-vb-application-version"] || "",
},
});
return response.headers.get("x-appbuilder-client-id") || "";
} catch {
return "";
}
}
const appBuilderClientId =
sourceHeaders["x-appbuilder-client-id"] || (await resolveAppBuilderClientId());
const requestHeaders = {
accept: "*/*",
authorization: "Session",
"vb-proxy-header-preference": "transient",
"vb-proxy-header-rest-framework-version": "3",
"x-vb-application-version":
routeAppVersion || sourceHeaders["x-vb-application-version"] || "",
"accept-language":
sourceHeaders["accept-language"] || navigator.language || "pt-BR",
};
if (appBuilderClientId) {
requestHeaders["x-appbuilder-client-id"] = appBuilderClientId;
}
const normalizedMethod = String(requestMethod || "POST").toUpperCase();
const fetchOptions = {
method: normalizedMethod,
credentials: "include",
cache: "no-store",
headers: requestHeaders,
};
if (normalizedMethod !== "GET" && normalizedMethod !== "HEAD") {
requestHeaders["content-type"] = "application/json";
fetchOptions.body = JSON.stringify(requestPayload || {});
}
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: {
href: window.location.href,
origin: window.location.origin,
frameName: window.name || "",
headerKeys: Object.keys(requestHeaders),
frameFetch: true,
},
};
},
});
const matchedResult = (injectionResults || [])
.map((item) => item.result)
.find((result) => result?.matched);
if (!matchedResult) {
const frameHints = (injectionResults || [])
.map((item) => item.result)
.filter(Boolean)
.map((result) => result.href || result.frameName || "unknown")
.slice(0, 5)
.join(" | ");
throw new Error(
`COMCIP Time Entry frame did not return a result.${
frameHints ? ` Frames: ${frameHints}` : ""
}`
);
}
return matchedResult;
}
async function executeComcipDirectFetch(
requestUrl,
method = "GET",
payload = null,
headers = {},
route
) {
const APP_VERSION = "version_1754044416761";
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-appbuilder-client-id",
"x-invalid-appbuilder-client-id",
"x-appbuilder-repeat-request",
"vb-proxy-status-actual",
"vb-proxy-version",
];
for (const key of exposedHeaders) {
const value = responseHeaders.get(key);
if (value) {
safeHeaders[key] = value;
}
}
return safeHeaders;
}
async function resolveAppBuilderClientId() {
try {
const response = await fetch(route?.clientIdProbeUrl || COMCIP_CLIENT_ID_PROBE_URL, {
method: "GET",
credentials: "include",
cache: "no-store",
headers: {
accept: "*/*",
authorization: "Session",
"accept-language": headers["accept-language"] || "pt-BR",
},
});
return response.headers.get("x-appbuilder-client-id") || "";
} catch {
return "";
}
}
const appBuilderClientId =
headers["x-appbuilder-client-id"] || (await resolveAppBuilderClientId());
const requestHeaders = {
accept: "*/*",
authorization: "Session",
"vb-proxy-header-preference": "transient",
"accept-language": headers["accept-language"] || "pt-BR",
};
if (!route?.omitAppVersion && headers["x-vb-application-version"]) {
requestHeaders["x-vb-application-version"] = headers["x-vb-application-version"];
}
if (appBuilderClientId) {
requestHeaders["x-appbuilder-client-id"] = appBuilderClientId;
}
const normalizedMethod = String(method || "GET").toUpperCase();
const fetchOptions = {
method: normalizedMethod,
credentials: "include",
cache: "no-store",
headers: requestHeaders,
};
if (normalizedMethod !== "GET" && normalizedMethod !== "HEAD") {
requestHeaders["content-type"] = "application/json";
fetchOptions.body = JSON.stringify(payload || {});
}
const response = await fetch(requestUrl, fetchOptions);
const text = await response.text();
return {
ok: response.ok,
status: response.status,
statusText: response.statusText,
headers: pickResponseHeaders(response.headers),
payload: parseResponseText(text),
debug: {
directFetch: true,
origin: chrome.runtime.getURL(""),
headerKeys: Object.keys(requestHeaders),
},
};
}
function isAuthorizationFailure(response) {
if (!response) {
return false;
}
const payloadText =
typeof response.payload === "string"
? response.payload
: JSON.stringify(response.payload || "");
return (
response.status === 401 ||
response.status === 500 && /vb:\/\/trap\/model\/no_auth/i.test(payloadText) ||
/No user authentication for the service/i.test(payloadText) ||
/missingAnonymous/i.test(payloadText) ||
/401 Authorization Required/i.test(payloadText) ||
/Authorization Required/i.test(payloadText)
);
}
function isOutdatedApplicationFailure(response) {
if (!response) {
return false;
}
const payloadText =
typeof response.payload === "string"
? response.payload
: JSON.stringify(response.payload || "");
return /out-dated version of the application/i.test(payloadText);
}
function delay(milliseconds) {
return new Promise((resolve) => {
setTimeout(resolve, milliseconds);
});
}