The sessions required for Internal SR API calls are now loaded via an iframe
This commit is contained in:
18
dist/chromium/background.js
vendored
18
dist/chromium/background.js
vendored
@@ -752,10 +752,14 @@ function createFusionSrDetails({ fetchJson }) {
|
||||
const fields = "Title,SrNumber,SrId,OpportunityName_c,QueueManagers_c,StatusCd,resourceMembers;ServiceRequest_SRToInternalSR_Tgt:ReportedHours_c,Title,OpportunityName_c,StatusCd,SrNumber,ServiceNew_c,AccountPartyName,LastUpdateDate,SvcHealth_c,AssigneePersonName,OpenDate,SrId";
|
||||
const url = "https://comcipapic-oalprod.integration.ocp.oraclecloud.com/ic/builder/rt/SeaaS_SR_management/live;profile=PROD/services/auth/1.1/proxy/oalsetCRMRestAPI/uri/https/eeho.fa.us2.oraclecloud.com/crmRestApi/resources/latest/serviceRequests"
|
||||
+ `?fields=${encodeURIComponent(fields)}&onlyData=true&q=${encodeURIComponent(`SrId=${id}`)}`;
|
||||
const result = await fetchJson(url, { method: "GET", credentials: "include",
|
||||
const query = async () => {
|
||||
const result = await fetchJson(url, { method: "GET", credentials: "include",
|
||||
headers: { Accept: "application/json", Authorization: "Session" }
|
||||
}, "requestInternalServiceRequest", debugRequests);
|
||||
if (!Array.isArray(result?.items)) throw new Error("Invalid SR details response.");
|
||||
}, "requestInternalServiceRequest", debugRequests);
|
||||
if (!Array.isArray(result?.items)) throw new Error("Invalid SR details response.");
|
||||
return result;
|
||||
};
|
||||
const result = await query();
|
||||
const items = result.items.flatMap(item => {
|
||||
const children = item.ServiceRequest_SRToInternalSR_Tgt;
|
||||
if (children == null) return [];
|
||||
@@ -1966,7 +1970,7 @@ runtimeApi.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
|
||||
if ([messageTypes.requestActionTeamMembers, messageTypes.requestActionFindLookup, messageTypes.requestMgmtAction, messageTypes.requestInternalServiceRequest].includes(message.type)) {
|
||||
const request = message.type === messageTypes.requestInternalServiceRequest ? requestInternalServiceRequest : message.type === messageTypes.requestMgmtAction ? requestMgmtAction : message.type === messageTypes.requestActionTeamMembers ? requestActionTeamMembers : requestActionFindLookup;
|
||||
request(message).then(sendResponse).catch(error => sendResponse({
|
||||
request(message, sender).then(sendResponse).catch(error => sendResponse({
|
||||
ok: false, httpStatus: error.httpStatus, error: error.message || "Unable to complete request.",
|
||||
debugRequests: Array.isArray(error.debugRequests) ? error.debugRequests : []
|
||||
}));
|
||||
@@ -2173,9 +2177,11 @@ function createBackgroundApplication(overrides = {}) {
|
||||
const requestRatecardByPlan = (...args) => spaConsumption.requestRatecardByPlan(...args);
|
||||
const requestOpptyProducts = (...args) => productsBackgroundJobs.requestOpptyProducts(...args);
|
||||
const runtimeApi = environment.browser || environment.chrome;
|
||||
const fusionSrDetails = createFusionSrDetails({ fetchJson: (...args) => httpBackgroundJson.fetchJson(...args) });
|
||||
const fusionSrDetails = createFusionSrDetails({
|
||||
fetchJson: (...args) => httpBackgroundJson.fetchJson(...args)
|
||||
});
|
||||
function start() {
|
||||
startBackgroundApplication({ messageTypes, requestInternalServiceRequest: message => fusionSrDetails.requestInternalServiceRequest(message), requestMgmtAction: message => spaActionOptions.requestMgmtAction(message), invalidateProductsCache: () => productsCache.invalidate(), requestCurrentUser, getXsrfTokenCookie, refreshXsrfCookie, requestAccountPlans, requestInstallBase, requestCustomerServiceRequest, requestWlActions, requestActionTeamMembers, requestActionFindLookup, requestConsumerTokenService, requestConsumptionTrend, requestConsumptionPlanDetail, requestRatecardByPlan, requestOpptyProducts, runtimeApi });
|
||||
startBackgroundApplication({ messageTypes, requestInternalServiceRequest: (message, sender) => fusionSrDetails.requestInternalServiceRequest(message, sender), requestMgmtAction: message => spaActionOptions.requestMgmtAction(message), invalidateProductsCache: () => productsCache.invalidate(), requestCurrentUser, getXsrfTokenCookie, refreshXsrfCookie, requestAccountPlans, requestInstallBase, requestCustomerServiceRequest, requestWlActions, requestActionTeamMembers, requestActionFindLookup, requestConsumerTokenService, requestConsumptionTrend, requestConsumptionPlanDetail, requestRatecardByPlan, requestOpptyProducts, runtimeApi });
|
||||
}
|
||||
return {
|
||||
start, contracts,
|
||||
|
||||
69
dist/chromium/content.js
vendored
69
dist/chromium/content.js
vendored
@@ -2620,6 +2620,62 @@ function createCrmSrPopover({ MODAL_ID, createCopyButton, createCloseIcon, reque
|
||||
return { bind, close };
|
||||
}
|
||||
|
||||
// Source: src/features/opportunities/internal-sr-session.js
|
||||
function createInternalSrSession({ query, environment }) {
|
||||
const { document, setTimeout } = environment;
|
||||
let recovery = null;
|
||||
const wait = duration => new Promise(resolve => setTimeout(resolve, duration));
|
||||
const now = () => environment.performance?.now() ?? Date.now();
|
||||
async function warmSession(id) {
|
||||
const frame = document.createElement("iframe");
|
||||
frame.src = "https://eeho.fa.us2.oraclecloud.com/fscmUI/redwood/service/ec/container/sr/edit?srNumber=SR0001421040&srId=302963804324543&view=showisr";
|
||||
frame.title = "Internal SR session initialization";
|
||||
frame.tabIndex = -1;
|
||||
frame.setAttribute("aria-hidden", "true");
|
||||
frame.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms");
|
||||
frame.style.cssText = "position:fixed;width:1px;height:1px;left:-10000px;top:0;border:0;opacity:0;pointer-events:none;";
|
||||
let blocked = false;
|
||||
let loaded = false;
|
||||
frame.addEventListener("error", () => { blocked = true; });
|
||||
frame.addEventListener("load", () => { loaded = true; });
|
||||
try {
|
||||
if (!document.body) throw new Error("The current page cannot initialize the Internal SR session.");
|
||||
document.body.append(frame);
|
||||
const deadline = now() + 60000;
|
||||
for (let attempt = 0; now() < deadline; attempt += 1) {
|
||||
// Check immediately, then briefly poll faster before backing off.
|
||||
if (attempt > 0) await wait(Math.min(attempt <= 4 ? 250 : 1000, deadline - now()));
|
||||
if (now() >= deadline) break;
|
||||
if (blocked) throw new Error("The Internal SR session iframe could not be loaded. Sign in manually and try again.");
|
||||
const result = await query(id);
|
||||
if (result?.ok) return { id, result };
|
||||
if (result?.httpStatus !== 401) throw new Error(result?.error || "Unable to load Internal SR details.");
|
||||
}
|
||||
throw new Error(`Internal SR session timed out after 60 seconds: the API still returned HTTP 401. ${loaded ? "The iframe emitted a load event, but authentication was not confirmed." : "The iframe did not emit a load event."} Sign in manually and try again.`);
|
||||
} finally {
|
||||
// Stop remaining page resources without stopping the caller's page.
|
||||
try { frame.contentWindow?.stop(); } catch { /* Redirects can make the frame cross-origin. */ }
|
||||
frame.src = "about:blank";
|
||||
frame.remove();
|
||||
}
|
||||
}
|
||||
async function request(id) {
|
||||
let result = await query(id);
|
||||
if (!result?.ok && result?.httpStatus === 401) {
|
||||
if (!recovery) {
|
||||
recovery = warmSession(id);
|
||||
const pending = recovery;
|
||||
pending.finally(() => { if (recovery === pending) recovery = null; }).catch(() => {});
|
||||
}
|
||||
const recovered = await recovery;
|
||||
result = recovered.id === id ? recovered.result : await query(id);
|
||||
}
|
||||
if (!result?.ok) throw new Error(result?.error || "Unable to load Internal SR details.");
|
||||
return result.items;
|
||||
}
|
||||
return { request };
|
||||
}
|
||||
|
||||
// Source: src/features/opportunities/workload-actions.js
|
||||
function createOpportunityWorkloadActions({ authSessionState, allowsCrmActivities, requestConsumerTokenServiceOnce, sendRuntimeMessage,
|
||||
appendBackgroundRequestLog, queueProductsTableRender, productsContentState, messageTypes, reportProgress, activityCache, createActionButton, closeActionForm, setBadgeContent, environment }) {
|
||||
@@ -8061,13 +8117,16 @@ function createContentApplication(overrides = {}) {
|
||||
readResponseBodyForDebug: (...args) => debugContentLog.readResponseBodyForDebug(...args),
|
||||
maskToken: (...args) => debugContentLog.maskToken(...args), environment
|
||||
});
|
||||
const crmSrPopover = createCrmSrPopover({
|
||||
requestDetails: async srId => {
|
||||
const internalSrSession = createInternalSrSession({
|
||||
environment,
|
||||
query: async srId => {
|
||||
const result = await platformContentMessages.sendRuntimeMessage({ type: messageTypes.requestInternalServiceRequest, srId });
|
||||
debugContentLog.appendBackgroundRequestLog(result?.debugRequests || []);
|
||||
if (!result?.ok) throw new Error(result?.error || "Unable to load SR details.");
|
||||
return result.items;
|
||||
},
|
||||
return result;
|
||||
}
|
||||
});
|
||||
const crmSrPopover = createCrmSrPopover({
|
||||
requestDetails: srId => internalSrSession.request(srId),
|
||||
MODAL_ID: contracts.MODAL_ID,
|
||||
createCopyButton: (...args) => opportunitiesLinks.createCopyOpportunityButton(...args),
|
||||
createCloseIcon: (...args) => contentIcons.createCloseIcon(...args), environment
|
||||
|
||||
18
dist/firefox/background.js
vendored
18
dist/firefox/background.js
vendored
@@ -752,10 +752,14 @@ function createFusionSrDetails({ fetchJson }) {
|
||||
const fields = "Title,SrNumber,SrId,OpportunityName_c,QueueManagers_c,StatusCd,resourceMembers;ServiceRequest_SRToInternalSR_Tgt:ReportedHours_c,Title,OpportunityName_c,StatusCd,SrNumber,ServiceNew_c,AccountPartyName,LastUpdateDate,SvcHealth_c,AssigneePersonName,OpenDate,SrId";
|
||||
const url = "https://comcipapic-oalprod.integration.ocp.oraclecloud.com/ic/builder/rt/SeaaS_SR_management/live;profile=PROD/services/auth/1.1/proxy/oalsetCRMRestAPI/uri/https/eeho.fa.us2.oraclecloud.com/crmRestApi/resources/latest/serviceRequests"
|
||||
+ `?fields=${encodeURIComponent(fields)}&onlyData=true&q=${encodeURIComponent(`SrId=${id}`)}`;
|
||||
const result = await fetchJson(url, { method: "GET", credentials: "include",
|
||||
const query = async () => {
|
||||
const result = await fetchJson(url, { method: "GET", credentials: "include",
|
||||
headers: { Accept: "application/json", Authorization: "Session" }
|
||||
}, "requestInternalServiceRequest", debugRequests);
|
||||
if (!Array.isArray(result?.items)) throw new Error("Invalid SR details response.");
|
||||
}, "requestInternalServiceRequest", debugRequests);
|
||||
if (!Array.isArray(result?.items)) throw new Error("Invalid SR details response.");
|
||||
return result;
|
||||
};
|
||||
const result = await query();
|
||||
const items = result.items.flatMap(item => {
|
||||
const children = item.ServiceRequest_SRToInternalSR_Tgt;
|
||||
if (children == null) return [];
|
||||
@@ -1966,7 +1970,7 @@ runtimeApi.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
|
||||
if ([messageTypes.requestActionTeamMembers, messageTypes.requestActionFindLookup, messageTypes.requestMgmtAction, messageTypes.requestInternalServiceRequest].includes(message.type)) {
|
||||
const request = message.type === messageTypes.requestInternalServiceRequest ? requestInternalServiceRequest : message.type === messageTypes.requestMgmtAction ? requestMgmtAction : message.type === messageTypes.requestActionTeamMembers ? requestActionTeamMembers : requestActionFindLookup;
|
||||
request(message).then(sendResponse).catch(error => sendResponse({
|
||||
request(message, sender).then(sendResponse).catch(error => sendResponse({
|
||||
ok: false, httpStatus: error.httpStatus, error: error.message || "Unable to complete request.",
|
||||
debugRequests: Array.isArray(error.debugRequests) ? error.debugRequests : []
|
||||
}));
|
||||
@@ -2173,9 +2177,11 @@ function createBackgroundApplication(overrides = {}) {
|
||||
const requestRatecardByPlan = (...args) => spaConsumption.requestRatecardByPlan(...args);
|
||||
const requestOpptyProducts = (...args) => productsBackgroundJobs.requestOpptyProducts(...args);
|
||||
const runtimeApi = environment.browser || environment.chrome;
|
||||
const fusionSrDetails = createFusionSrDetails({ fetchJson: (...args) => httpBackgroundJson.fetchJson(...args) });
|
||||
const fusionSrDetails = createFusionSrDetails({
|
||||
fetchJson: (...args) => httpBackgroundJson.fetchJson(...args)
|
||||
});
|
||||
function start() {
|
||||
startBackgroundApplication({ messageTypes, requestInternalServiceRequest: message => fusionSrDetails.requestInternalServiceRequest(message), requestMgmtAction: message => spaActionOptions.requestMgmtAction(message), invalidateProductsCache: () => productsCache.invalidate(), requestCurrentUser, getXsrfTokenCookie, refreshXsrfCookie, requestAccountPlans, requestInstallBase, requestCustomerServiceRequest, requestWlActions, requestActionTeamMembers, requestActionFindLookup, requestConsumerTokenService, requestConsumptionTrend, requestConsumptionPlanDetail, requestRatecardByPlan, requestOpptyProducts, runtimeApi });
|
||||
startBackgroundApplication({ messageTypes, requestInternalServiceRequest: (message, sender) => fusionSrDetails.requestInternalServiceRequest(message, sender), requestMgmtAction: message => spaActionOptions.requestMgmtAction(message), invalidateProductsCache: () => productsCache.invalidate(), requestCurrentUser, getXsrfTokenCookie, refreshXsrfCookie, requestAccountPlans, requestInstallBase, requestCustomerServiceRequest, requestWlActions, requestActionTeamMembers, requestActionFindLookup, requestConsumerTokenService, requestConsumptionTrend, requestConsumptionPlanDetail, requestRatecardByPlan, requestOpptyProducts, runtimeApi });
|
||||
}
|
||||
return {
|
||||
start, contracts,
|
||||
|
||||
69
dist/firefox/content.js
vendored
69
dist/firefox/content.js
vendored
@@ -2620,6 +2620,62 @@ function createCrmSrPopover({ MODAL_ID, createCopyButton, createCloseIcon, reque
|
||||
return { bind, close };
|
||||
}
|
||||
|
||||
// Source: src/features/opportunities/internal-sr-session.js
|
||||
function createInternalSrSession({ query, environment }) {
|
||||
const { document, setTimeout } = environment;
|
||||
let recovery = null;
|
||||
const wait = duration => new Promise(resolve => setTimeout(resolve, duration));
|
||||
const now = () => environment.performance?.now() ?? Date.now();
|
||||
async function warmSession(id) {
|
||||
const frame = document.createElement("iframe");
|
||||
frame.src = "https://eeho.fa.us2.oraclecloud.com/fscmUI/redwood/service/ec/container/sr/edit?srNumber=SR0001421040&srId=302963804324543&view=showisr";
|
||||
frame.title = "Internal SR session initialization";
|
||||
frame.tabIndex = -1;
|
||||
frame.setAttribute("aria-hidden", "true");
|
||||
frame.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms");
|
||||
frame.style.cssText = "position:fixed;width:1px;height:1px;left:-10000px;top:0;border:0;opacity:0;pointer-events:none;";
|
||||
let blocked = false;
|
||||
let loaded = false;
|
||||
frame.addEventListener("error", () => { blocked = true; });
|
||||
frame.addEventListener("load", () => { loaded = true; });
|
||||
try {
|
||||
if (!document.body) throw new Error("The current page cannot initialize the Internal SR session.");
|
||||
document.body.append(frame);
|
||||
const deadline = now() + 60000;
|
||||
for (let attempt = 0; now() < deadline; attempt += 1) {
|
||||
// Check immediately, then briefly poll faster before backing off.
|
||||
if (attempt > 0) await wait(Math.min(attempt <= 4 ? 250 : 1000, deadline - now()));
|
||||
if (now() >= deadline) break;
|
||||
if (blocked) throw new Error("The Internal SR session iframe could not be loaded. Sign in manually and try again.");
|
||||
const result = await query(id);
|
||||
if (result?.ok) return { id, result };
|
||||
if (result?.httpStatus !== 401) throw new Error(result?.error || "Unable to load Internal SR details.");
|
||||
}
|
||||
throw new Error(`Internal SR session timed out after 60 seconds: the API still returned HTTP 401. ${loaded ? "The iframe emitted a load event, but authentication was not confirmed." : "The iframe did not emit a load event."} Sign in manually and try again.`);
|
||||
} finally {
|
||||
// Stop remaining page resources without stopping the caller's page.
|
||||
try { frame.contentWindow?.stop(); } catch { /* Redirects can make the frame cross-origin. */ }
|
||||
frame.src = "about:blank";
|
||||
frame.remove();
|
||||
}
|
||||
}
|
||||
async function request(id) {
|
||||
let result = await query(id);
|
||||
if (!result?.ok && result?.httpStatus === 401) {
|
||||
if (!recovery) {
|
||||
recovery = warmSession(id);
|
||||
const pending = recovery;
|
||||
pending.finally(() => { if (recovery === pending) recovery = null; }).catch(() => {});
|
||||
}
|
||||
const recovered = await recovery;
|
||||
result = recovered.id === id ? recovered.result : await query(id);
|
||||
}
|
||||
if (!result?.ok) throw new Error(result?.error || "Unable to load Internal SR details.");
|
||||
return result.items;
|
||||
}
|
||||
return { request };
|
||||
}
|
||||
|
||||
// Source: src/features/opportunities/workload-actions.js
|
||||
function createOpportunityWorkloadActions({ authSessionState, allowsCrmActivities, requestConsumerTokenServiceOnce, sendRuntimeMessage,
|
||||
appendBackgroundRequestLog, queueProductsTableRender, productsContentState, messageTypes, reportProgress, activityCache, createActionButton, closeActionForm, setBadgeContent, environment }) {
|
||||
@@ -8061,13 +8117,16 @@ function createContentApplication(overrides = {}) {
|
||||
readResponseBodyForDebug: (...args) => debugContentLog.readResponseBodyForDebug(...args),
|
||||
maskToken: (...args) => debugContentLog.maskToken(...args), environment
|
||||
});
|
||||
const crmSrPopover = createCrmSrPopover({
|
||||
requestDetails: async srId => {
|
||||
const internalSrSession = createInternalSrSession({
|
||||
environment,
|
||||
query: async srId => {
|
||||
const result = await platformContentMessages.sendRuntimeMessage({ type: messageTypes.requestInternalServiceRequest, srId });
|
||||
debugContentLog.appendBackgroundRequestLog(result?.debugRequests || []);
|
||||
if (!result?.ok) throw new Error(result?.error || "Unable to load SR details.");
|
||||
return result.items;
|
||||
},
|
||||
return result;
|
||||
}
|
||||
});
|
||||
const crmSrPopover = createCrmSrPopover({
|
||||
requestDetails: srId => internalSrSession.request(srId),
|
||||
MODAL_ID: contracts.MODAL_ID,
|
||||
createCopyButton: (...args) => opportunitiesLinks.createCopyOpportunityButton(...args),
|
||||
createCloseIcon: (...args) => contentIcons.createCloseIcon(...args), environment
|
||||
|
||||
@@ -39,6 +39,7 @@ module.exports = {
|
||||
"src/features/opportunities/activity-progress.js",
|
||||
"src/features/opportunities/activity-filters.js",
|
||||
"src/features/opportunities/crm-sr-popover.js",
|
||||
"src/features/opportunities/internal-sr-session.js",
|
||||
"src/features/opportunities/workload-actions.js",
|
||||
"src/features/opportunities/workload-action-form.js",
|
||||
"src/features/opportunities/workload-action-options.js",
|
||||
|
||||
@@ -125,9 +125,11 @@ function createBackgroundApplication(overrides = {}) {
|
||||
const requestRatecardByPlan = (...args) => spaConsumption.requestRatecardByPlan(...args);
|
||||
const requestOpptyProducts = (...args) => productsBackgroundJobs.requestOpptyProducts(...args);
|
||||
const runtimeApi = environment.browser || environment.chrome;
|
||||
const fusionSrDetails = createFusionSrDetails({ fetchJson: (...args) => httpBackgroundJson.fetchJson(...args) });
|
||||
const fusionSrDetails = createFusionSrDetails({
|
||||
fetchJson: (...args) => httpBackgroundJson.fetchJson(...args)
|
||||
});
|
||||
function start() {
|
||||
startBackgroundApplication({ messageTypes, requestInternalServiceRequest: message => fusionSrDetails.requestInternalServiceRequest(message), requestMgmtAction: message => spaActionOptions.requestMgmtAction(message), invalidateProductsCache: () => productsCache.invalidate(), requestCurrentUser, getXsrfTokenCookie, refreshXsrfCookie, requestAccountPlans, requestInstallBase, requestCustomerServiceRequest, requestWlActions, requestActionTeamMembers, requestActionFindLookup, requestConsumerTokenService, requestConsumptionTrend, requestConsumptionPlanDetail, requestRatecardByPlan, requestOpptyProducts, runtimeApi });
|
||||
startBackgroundApplication({ messageTypes, requestInternalServiceRequest: (message, sender) => fusionSrDetails.requestInternalServiceRequest(message, sender), requestMgmtAction: message => spaActionOptions.requestMgmtAction(message), invalidateProductsCache: () => productsCache.invalidate(), requestCurrentUser, getXsrfTokenCookie, refreshXsrfCookie, requestAccountPlans, requestInstallBase, requestCustomerServiceRequest, requestWlActions, requestActionTeamMembers, requestActionFindLookup, requestConsumerTokenService, requestConsumptionTrend, requestConsumptionPlanDetail, requestRatecardByPlan, requestOpptyProducts, runtimeApi });
|
||||
}
|
||||
return {
|
||||
start, contracts,
|
||||
|
||||
@@ -108,7 +108,7 @@ runtimeApi.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
|
||||
if ([messageTypes.requestActionTeamMembers, messageTypes.requestActionFindLookup, messageTypes.requestMgmtAction, messageTypes.requestInternalServiceRequest].includes(message.type)) {
|
||||
const request = message.type === messageTypes.requestInternalServiceRequest ? requestInternalServiceRequest : message.type === messageTypes.requestMgmtAction ? requestMgmtAction : message.type === messageTypes.requestActionTeamMembers ? requestActionTeamMembers : requestActionFindLookup;
|
||||
request(message).then(sendResponse).catch(error => sendResponse({
|
||||
request(message, sender).then(sendResponse).catch(error => sendResponse({
|
||||
ok: false, httpStatus: error.httpStatus, error: error.message || "Unable to complete request.",
|
||||
debugRequests: Array.isArray(error.debugRequests) ? error.debugRequests : []
|
||||
}));
|
||||
|
||||
@@ -216,13 +216,16 @@ function createContentApplication(overrides = {}) {
|
||||
readResponseBodyForDebug: (...args) => debugContentLog.readResponseBodyForDebug(...args),
|
||||
maskToken: (...args) => debugContentLog.maskToken(...args), environment
|
||||
});
|
||||
const crmSrPopover = createCrmSrPopover({
|
||||
requestDetails: async srId => {
|
||||
const internalSrSession = createInternalSrSession({
|
||||
environment,
|
||||
query: async srId => {
|
||||
const result = await platformContentMessages.sendRuntimeMessage({ type: messageTypes.requestInternalServiceRequest, srId });
|
||||
debugContentLog.appendBackgroundRequestLog(result?.debugRequests || []);
|
||||
if (!result?.ok) throw new Error(result?.error || "Unable to load SR details.");
|
||||
return result.items;
|
||||
},
|
||||
return result;
|
||||
}
|
||||
});
|
||||
const crmSrPopover = createCrmSrPopover({
|
||||
requestDetails: srId => internalSrSession.request(srId),
|
||||
MODAL_ID: contracts.MODAL_ID,
|
||||
createCopyButton: (...args) => opportunitiesLinks.createCopyOpportunityButton(...args),
|
||||
createCloseIcon: (...args) => contentIcons.createCloseIcon(...args), environment
|
||||
|
||||
54
src/features/opportunities/internal-sr-session.js
Normal file
54
src/features/opportunities/internal-sr-session.js
Normal file
@@ -0,0 +1,54 @@
|
||||
function createInternalSrSession({ query, environment }) {
|
||||
const { document, setTimeout } = environment;
|
||||
let recovery = null;
|
||||
const wait = duration => new Promise(resolve => setTimeout(resolve, duration));
|
||||
const now = () => environment.performance?.now() ?? Date.now();
|
||||
async function warmSession(id) {
|
||||
const frame = document.createElement("iframe");
|
||||
frame.src = "https://eeho.fa.us2.oraclecloud.com/fscmUI/redwood/service/ec/container/sr/edit?srNumber=SR0001421040&srId=302963804324543&view=showisr";
|
||||
frame.title = "Internal SR session initialization";
|
||||
frame.tabIndex = -1;
|
||||
frame.setAttribute("aria-hidden", "true");
|
||||
frame.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms");
|
||||
frame.style.cssText = "position:fixed;width:1px;height:1px;left:-10000px;top:0;border:0;opacity:0;pointer-events:none;";
|
||||
let blocked = false;
|
||||
let loaded = false;
|
||||
frame.addEventListener("error", () => { blocked = true; });
|
||||
frame.addEventListener("load", () => { loaded = true; });
|
||||
try {
|
||||
if (!document.body) throw new Error("The current page cannot initialize the Internal SR session.");
|
||||
document.body.append(frame);
|
||||
const deadline = now() + 60000;
|
||||
for (let attempt = 0; now() < deadline; attempt += 1) {
|
||||
// Check immediately, then briefly poll faster before backing off.
|
||||
if (attempt > 0) await wait(Math.min(attempt <= 4 ? 250 : 1000, deadline - now()));
|
||||
if (now() >= deadline) break;
|
||||
if (blocked) throw new Error("The Internal SR session iframe could not be loaded. Sign in manually and try again.");
|
||||
const result = await query(id);
|
||||
if (result?.ok) return { id, result };
|
||||
if (result?.httpStatus !== 401) throw new Error(result?.error || "Unable to load Internal SR details.");
|
||||
}
|
||||
throw new Error(`Internal SR session timed out after 60 seconds: the API still returned HTTP 401. ${loaded ? "The iframe emitted a load event, but authentication was not confirmed." : "The iframe did not emit a load event."} Sign in manually and try again.`);
|
||||
} finally {
|
||||
// Stop remaining page resources without stopping the caller's page.
|
||||
try { frame.contentWindow?.stop(); } catch { /* Redirects can make the frame cross-origin. */ }
|
||||
frame.src = "about:blank";
|
||||
frame.remove();
|
||||
}
|
||||
}
|
||||
async function request(id) {
|
||||
let result = await query(id);
|
||||
if (!result?.ok && result?.httpStatus === 401) {
|
||||
if (!recovery) {
|
||||
recovery = warmSession(id);
|
||||
const pending = recovery;
|
||||
pending.finally(() => { if (recovery === pending) recovery = null; }).catch(() => {});
|
||||
}
|
||||
const recovered = await recovery;
|
||||
result = recovered.id === id ? recovered.result : await query(id);
|
||||
}
|
||||
if (!result?.ok) throw new Error(result?.error || "Unable to load Internal SR details.");
|
||||
return result.items;
|
||||
}
|
||||
return { request };
|
||||
}
|
||||
@@ -7,10 +7,14 @@ function createFusionSrDetails({ fetchJson }) {
|
||||
const fields = "Title,SrNumber,SrId,OpportunityName_c,QueueManagers_c,StatusCd,resourceMembers;ServiceRequest_SRToInternalSR_Tgt:ReportedHours_c,Title,OpportunityName_c,StatusCd,SrNumber,ServiceNew_c,AccountPartyName,LastUpdateDate,SvcHealth_c,AssigneePersonName,OpenDate,SrId";
|
||||
const url = "https://comcipapic-oalprod.integration.ocp.oraclecloud.com/ic/builder/rt/SeaaS_SR_management/live;profile=PROD/services/auth/1.1/proxy/oalsetCRMRestAPI/uri/https/eeho.fa.us2.oraclecloud.com/crmRestApi/resources/latest/serviceRequests"
|
||||
+ `?fields=${encodeURIComponent(fields)}&onlyData=true&q=${encodeURIComponent(`SrId=${id}`)}`;
|
||||
const result = await fetchJson(url, { method: "GET", credentials: "include",
|
||||
const query = async () => {
|
||||
const result = await fetchJson(url, { method: "GET", credentials: "include",
|
||||
headers: { Accept: "application/json", Authorization: "Session" }
|
||||
}, "requestInternalServiceRequest", debugRequests);
|
||||
if (!Array.isArray(result?.items)) throw new Error("Invalid SR details response.");
|
||||
}, "requestInternalServiceRequest", debugRequests);
|
||||
if (!Array.isArray(result?.items)) throw new Error("Invalid SR details response.");
|
||||
return result;
|
||||
};
|
||||
const result = await query();
|
||||
const items = result.items.flatMap(item => {
|
||||
const children = item.ServiceRequest_SRToInternalSR_Tgt;
|
||||
if (children == null) return [];
|
||||
|
||||
@@ -2,6 +2,44 @@ const assert = require("node:assert/strict");
|
||||
const { test } = require("node:test");
|
||||
const { loadBackground, response } = require("./support/characterization.cjs");
|
||||
const type = "opportunitiesExtension.requestInternalServiceRequest";
|
||||
for (const family of ["chrome", "firefox"]) {
|
||||
test(`${family}: 401 is returned to the caller without opening tabs`, async () => {
|
||||
let count = 0;
|
||||
const h = loadBackground({ family, fetch: async () => ++count < 3 ? response({}, 401) : response({ items: [] }) });
|
||||
const result = await h.dispatch({ type, srId: "123" });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.httpStatus, 401);
|
||||
assert.equal(count, 1);
|
||||
assert.equal(h.tabs.length, 0);
|
||||
assert.deepEqual(h.removedTabs, []);
|
||||
assert.deepEqual(result.debugRequests.map(entry => entry.httpStatus), [401]);
|
||||
});
|
||||
}
|
||||
test("concurrent 401 responses do not cause background tab recovery", async () => {
|
||||
let calls = 0;
|
||||
const h = loadBackground({ fetch: async url => {
|
||||
if (++calls <= 2) return response({}, 401);
|
||||
const id = new URL(url).searchParams.get("q");
|
||||
return response({ items: [{ ServiceRequest_SRToInternalSR_Tgt: [{ Title: id }] }] });
|
||||
} });
|
||||
const results = await Promise.all([h.dispatch({ type, srId: "1" }), h.dispatch({ type, srId: "2" })]);
|
||||
assert.equal(h.tabs.length, 0);
|
||||
assert.deepEqual(h.removedTabs, []);
|
||||
assert.ok(results.every(result => result.httpStatus === 401));
|
||||
});
|
||||
test("background never creates recovery tabs after an error", async () => {
|
||||
for (const status of [401, 500]) {
|
||||
let count = 0;
|
||||
const h = loadBackground({ fetch: async () => response({}, ++count === 1 ? 401 : status) });
|
||||
const result = await h.dispatch({ type, srId: "1" });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(count, 1);
|
||||
assert.deepEqual(h.removedTabs, []);
|
||||
}
|
||||
const h = loadBackground({ fetch: async () => response({}, 403) });
|
||||
assert.equal((await h.dispatch({ type, srId: "1" })).ok, false);
|
||||
assert.equal(h.tabs.length, 0);
|
||||
});
|
||||
for (const family of ["chrome", "firefox"]) {
|
||||
test(`${family}: SR detail GET uses exact scope and Session authentication`, async () => {
|
||||
const calls = [];
|
||||
|
||||
@@ -3,7 +3,7 @@ const { test } = require("node:test");
|
||||
const { loadApplication } = require("./support/application.cjs");
|
||||
const flush = () => new Promise(resolve => setImmediate(resolve));
|
||||
|
||||
test("internal SR uses Session without TokenRelay or retries on 401", async () => {
|
||||
test("internal SR uses Session without TokenRelay and reports unavailable iframe host", async () => {
|
||||
const h = setup();
|
||||
let renewals = 0;
|
||||
const sent = [];
|
||||
@@ -24,7 +24,7 @@ test("internal SR uses Session without TokenRelay or retries on 401", async () =
|
||||
assert.equal(sent.length, 1);
|
||||
assert.equal(sent[0].token, undefined);
|
||||
assert.ok(sent.every(item => item.type === "opportunitiesExtension.requestInternalServiceRequest"));
|
||||
assert.ok(h.walk(h.panel()).some(node => node.textContent === "Unauthorized"));
|
||||
assert.ok(h.walk(h.panel()).some(node => node.attributes.role === "alert"));
|
||||
});
|
||||
|
||||
function setup() {
|
||||
|
||||
99
tests/internalSrSession.test.cjs
Normal file
99
tests/internalSrSession.test.cjs
Normal file
@@ -0,0 +1,99 @@
|
||||
const assert = require("node:assert/strict");
|
||||
const { test } = require("node:test");
|
||||
const fs = require("node:fs");
|
||||
const vm = require("node:vm");
|
||||
const path = require("node:path");
|
||||
const factory = vm.runInNewContext(`${fs.readFileSync(path.join(__dirname, "../src/features/opportunities/internal-sr-session.js"), "utf8")}\ncreateInternalSrSession`);
|
||||
function setup(query) {
|
||||
let elapsed = 0;
|
||||
const frames = [], removed = [], delays = [], stopped = [];
|
||||
const body = { append(frame) { frame.initialSrc = frame.src; frames.push(frame); } };
|
||||
const document = { body, createElement(tag) {
|
||||
assert.equal(tag, "iframe");
|
||||
return { contentWindow: { stop() { stopped.push(true); } }, style: {}, attributes: {}, listeners: {}, setAttribute(k, v) { this.attributes[k] = v; },
|
||||
addEventListener(k, fn) { this.listeners[k] = fn; }, remove() { removed.push(this); } };
|
||||
} };
|
||||
const api = factory({ query, environment: { document, performance: { now: () => elapsed }, setTimeout: (fn, ms) => { delays.push(ms); elapsed += ms; return setImmediate(fn); } } });
|
||||
return { api, frames, removed, delays, stopped };
|
||||
}
|
||||
test("401 initializes a hidden iframe in the calling document and removes it on success", async () => {
|
||||
let calls = 0;
|
||||
const h = setup(async () => ++calls < 3 ? { ok: false, httpStatus: 401 } : { ok: true, items: [{ SrId: 1 }] });
|
||||
const items = await h.api.request("1");
|
||||
assert.equal(items[0].SrId, 1);
|
||||
assert.equal(h.frames.length, 1);
|
||||
const frame = h.frames[0];
|
||||
assert.equal(frame.tabIndex, -1);
|
||||
assert.equal(frame.attributes["aria-hidden"], "true");
|
||||
assert.match(frame.style.cssText, /pointer-events:none/);
|
||||
assert.ok(!frame.attributes.sandbox.includes("allow-top-navigation"));
|
||||
assert.ok(frame.initialSrc.endsWith("&view=showisr"));
|
||||
assert.equal(frame.src, "about:blank");
|
||||
assert.equal(h.stopped.length, 1);
|
||||
assert.deepEqual(h.delays, [250]);
|
||||
assert.equal(h.removed[0], frame);
|
||||
});
|
||||
|
||||
test("an immediately available session stops the iframe without waiting for load or a timer", async () => {
|
||||
let calls = 0;
|
||||
const h = setup(async () => ++calls === 1 ? { ok: false, httpStatus: 401 } : { ok: true, items: [] });
|
||||
await h.api.request("1");
|
||||
assert.equal(calls, 2);
|
||||
assert.equal(h.delays.length, 0);
|
||||
assert.equal(h.stopped.length, 1);
|
||||
assert.equal(h.removed.length, 1);
|
||||
});
|
||||
|
||||
test("cross-origin redirects still allow iframe removal when stop is blocked", async () => {
|
||||
let calls = 0;
|
||||
const h = setup(async () => {
|
||||
if (++calls === 1) return { ok: false, httpStatus: 401 };
|
||||
h.frames[0].contentWindow.stop = () => { throw new Error("Cross-origin"); };
|
||||
return { ok: true, items: [] };
|
||||
});
|
||||
await h.api.request("1");
|
||||
assert.equal(h.frames[0].src, "about:blank");
|
||||
assert.equal(h.removed.length, 1);
|
||||
});
|
||||
test("concurrent queries share one iframe without mixing SR results", async () => {
|
||||
let calls = 0;
|
||||
const h = setup(async id => ++calls <= 2 ? { ok: false, httpStatus: 401 } : { ok: true, items: [id] });
|
||||
const results = await Promise.all([h.api.request("1"), h.api.request("2")]);
|
||||
assert.deepEqual(results, [["1"], ["2"]]);
|
||||
assert.equal(h.frames.length, 1);
|
||||
assert.equal(h.removed.length, 1);
|
||||
});
|
||||
test("normal success does not mount an iframe; errors and timeout always clean it up", async () => {
|
||||
const success = setup(async () => ({ ok: true, items: [] }));
|
||||
await success.api.request("1");
|
||||
assert.equal(success.frames.length, 0);
|
||||
for (const status of [401, 500]) {
|
||||
let calls = 0;
|
||||
const h = setup(async () => ({ ok: false, httpStatus: ++calls === 1 ? 401 : status }));
|
||||
await assert.rejects(h.api.request("1"));
|
||||
if (status === 401) {
|
||||
assert.ok(calls > 31);
|
||||
assert.equal(h.delays.reduce((sum, ms) => sum + ms, 0), 60000);
|
||||
} else assert.equal(calls, 2);
|
||||
assert.equal(h.removed.length, 1);
|
||||
}
|
||||
});
|
||||
|
||||
test("slow session initialization can succeed after the old thirty-attempt limit", async () => {
|
||||
let calls = 0;
|
||||
const h = setup(async () => ++calls < 45 ? { ok: false, httpStatus: 401 } : { ok: true, items: ["ready"] });
|
||||
assert.deepEqual(await h.api.request("1"), ["ready"]);
|
||||
assert.equal(calls, 45);
|
||||
assert.equal(h.removed.length, 1);
|
||||
assert.ok(h.delays.reduce((sum, ms) => sum + ms, 0) < 60000);
|
||||
});
|
||||
|
||||
test("iframe load alone never confirms authentication", async () => {
|
||||
let calls = 0;
|
||||
const h = setup(async () => {
|
||||
if (++calls > 1) h.frames[0].listeners.load();
|
||||
return { ok: false, httpStatus: 401 };
|
||||
});
|
||||
await assert.rejects(h.api.request("1"), /HTTP 401.*load event, but authentication was not confirmed/);
|
||||
assert.equal(h.removed.length, 1);
|
||||
});
|
||||
Reference in New Issue
Block a user