Fixed the SPA authentication flow

This commit is contained in:
2026-10-21 15:55:29 -03:00
parent 5ef4c22c59
commit ba61f91c53
33 changed files with 1924 additions and 117 deletions

View File

@@ -57,30 +57,53 @@ function loadContent({ fetch = unexpectedFetch, storage = new Map(), xsrf = null
};
}
function loadBackground({ family = "chrome", fetch = unexpectedFetch, storage = {}, sessionStorage = {}, cookies = () => [], storageError = false, ruleError = false, messageTimeout = 2000 } = {}) {
function loadBackground({ family = "chrome", fetch = unexpectedFetch, storage = {}, sessionStorage = {}, cookies = () => [], storageError = false, ruleError = false, updateTabError = false, messageTimeout = 2000 } = {}) {
const time = clock();
const listeners = [];
const progress = [];
const requestDebug = [];
const tabs = [];
const removedTabs = [];
const updatedTabs = [];
const cookieQueries = [];
const headerListeners = [];
const responseHeaderListeners = [];
const headerRules = [];
let spaDocument = 0;
let identityRequest;
const api = { runtime: { onMessage: { addListener: fn => listeners.push(fn) },
getURL: path => `${family === "firefox" ? "moz" : "chrome"}-extension://synthetic-extension${path}` },
webRequest: {
onBeforeSendHeaders: { addListener: (listener, filter, options) => headerListeners.push({ listener, filter: plain(filter), options: plain(options) }) },
onHeadersReceived: { addListener: (listener, filter, options) => responseHeaderListeners.push({ listener, filter: plain(filter), options: plain(options) }) }
onHeadersReceived: {
addListener: (listener, filter, options) => responseHeaderListeners.push({ listener, filter: plain(filter), options: plain(options) }),
removeListener: listener => {
const index = responseHeaderListeners.findIndex(item => item.listener === listener);
if (index >= 0) responseHeaderListeners.splice(index, 1);
}
}
},
declarativeNetRequest: { updateSessionRules: async rules => { if (ruleError) throw new Error("Rule permission denied"); headerRules.push(plain(rules)); } }
};
const readCookies = details => { cookieQueries.push(plain(details)); return cookies(details); };
const getStored = key => ({ [key]: storage[key] === undefined ? undefined : plain(storage[key]) });
const store = value => Object.assign(storage, plain(value));
const openTab = details => { tabs.push(plain(details)); return { id: 91 }; };
const newSpaDocument = () => { spaDocument++; identityRequest = null; };
const openTab = details => { tabs.push(plain(details)); newSpaDocument(); return { id: 91 }; };
const send = (tabId, message, options) => {
if (message.type === "opportunitiesExtension.inspectSpaPage") return { status: "ready", documentId: String(spaDocument) };
if (message.type === "opportunitiesExtension.readSpaIdentity") {
assert.equal(message.documentId, String(spaDocument));
if (!identityRequest) identityRequest = (async () => {
try {
const result = await fetch("https://spa.oracle.com/oalcrm/web/api/identity-management/users/current", {
method: "GET", credentials: "include", cache: "no-store", headers: { Accept: "application/json" }
});
return { status: result.status, statusText: result.statusText, contentType: result.headers.get("content-type"), body: await result.text() };
} catch (error) { return { error: error.message }; }
})();
return identityRequest;
}
const events = message.type === "opportunitiesExtension.opptyProductsRequestDebug" ? requestDebug : progress;
events.push({ tabId, message: plain(message), options: plain(options || {}) });
};
@@ -91,6 +114,12 @@ function loadBackground({ family = "chrome", fetch = unexpectedFetch, storage =
set: async value => { if (storageError) throw new Error("Storage unavailable"); store(value); }
} };
api.tabs = { create: async details => openTab(details), remove: async id => removedTabs.push(id),
update: async (id, details) => {
updatedTabs.push({ id, details: plain(details) });
if (updateTabError) throw new Error("Synthetic navigation failure");
newSpaDocument();
return { id };
},
sendMessage: async (tabId, message, options) => send(tabId, message, options) };
api.cookies = {
get: async details => readCookies(details).find(cookie => cookie.name === details.name) || null,
@@ -107,11 +136,17 @@ function loadBackground({ family = "chrome", fetch = unexpectedFetch, storage =
set(value, callback) { withStorageError(() => { if (!storageError) store(value); callback(); }); }
} };
api.tabs = {
update(id, details, callback) {
updatedTabs.push({ id, details: plain(details) });
api.runtime.lastError = updateTabError ? { message: "Synthetic navigation failure" } : undefined;
if (!updateTabError) newSpaDocument();
try { callback({ id }); } finally { delete api.runtime.lastError; }
},
create(details, callback) { callback(openTab(details)); },
remove(id, callback) { removedTabs.push(id); callback(); },
sendMessage(tabId, message, options, callback) {
if (typeof options === "function") { callback = options; options = undefined; }
send(tabId, message, options); callback();
Promise.resolve(send(tabId, message, options)).then(callback);
}
};
api.cookies = {
@@ -126,7 +161,7 @@ function loadBackground({ family = "chrome", fetch = unexpectedFetch, storage =
};
const context = {
[family === "firefox" ? "browser" : "chrome"]: api,
fetch, URL, Date: time.Date,
fetch, URL, Date: time.Date, clearTimeout,
setTimeout: (callback, ms) => { time.advance(ms); return setTimeout(callback, 0); }
};
new vm.Script(composeScript("background"), { filename: "background.js" })
@@ -146,7 +181,7 @@ function loadBackground({ family = "chrome", fetch = unexpectedFetch, storage =
if (pending !== true) { clearTimeout(timer); reject(new Error("Expected asynchronous response contract")); }
});
}
return { dispatch, listener: listeners[0], progress, requestDebug, tabs, removedTabs, cookieQueries, storage, sessionStorage, headerRules, headerListeners, responseHeaderListeners, advance: time.advance };
return { dispatch, listener: listeners[0], progress, requestDebug, tabs, removedTabs, updatedTabs, cookieQueries, storage, sessionStorage, headerRules, headerListeners, responseHeaderListeners, advance: time.advance };
}
module.exports = { root, plain, response, loadContent, loadBackground };