Files
opportunities-extension/dist/firefox/background.js

361 lines
9.8 KiB
JavaScript

(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 runtimeApi = typeof browser !== "undefined" ? browser : chrome;
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;
}
return false;
});
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 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)
);
}
})();