Implementação do Arch Panel no FuseWelcome
This commit is contained in:
278
page-bridge.js
278
page-bridge.js
@@ -33,6 +33,21 @@
|
||||
scopedHeaders: new Map(),
|
||||
};
|
||||
|
||||
function getSafeTargetOrigin() {
|
||||
const origin = String(window.location?.origin || "");
|
||||
|
||||
return origin && origin !== "null" ? origin : "*";
|
||||
}
|
||||
|
||||
function safePostMessage(payload, targetWindow = window, targetOrigin = getSafeTargetOrigin()) {
|
||||
try {
|
||||
targetWindow.postMessage(payload, targetOrigin);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const nativeFetch = window.fetch.bind(window);
|
||||
const originalOpen = window.XMLHttpRequest.prototype.open;
|
||||
const originalSetRequestHeader = window.XMLHttpRequest.prototype.setRequestHeader;
|
||||
@@ -183,6 +198,33 @@
|
||||
mergeIntoBucket(bucket, headers);
|
||||
bridgeState.scopedHeaders.set(scopeKey, bucket);
|
||||
}
|
||||
|
||||
publishCapturedAuthHeaders(url, headers);
|
||||
}
|
||||
|
||||
function publishCapturedAuthHeaders(url, headers) {
|
||||
const authHeaders = {};
|
||||
|
||||
headers.forEach((value, key) => {
|
||||
if (isAuthorizationLikeHeader(key)) {
|
||||
authHeaders[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
if (!Object.keys(authHeaders).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
safePostMessage(
|
||||
{
|
||||
source: MESSAGE_SOURCE,
|
||||
type: "ARCH_PANEL_WORKBENCH_AUTH_HEADERS",
|
||||
url: normalizeUrl(url),
|
||||
headers: authHeaders,
|
||||
},
|
||||
window.top,
|
||||
"*"
|
||||
);
|
||||
}
|
||||
|
||||
function buildHeaders(url, input, body) {
|
||||
@@ -209,6 +251,14 @@
|
||||
merged.set(key, value);
|
||||
});
|
||||
|
||||
if (activeScopeKeys.includes("scope:provider-proxy")) {
|
||||
collectStoredAuthHeaders().forEach((value, key) => {
|
||||
if (!merged.has(key)) {
|
||||
merged.set(key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!merged.has("accept")) {
|
||||
merged.set("accept", "application/json, text/plain, */*");
|
||||
}
|
||||
@@ -285,11 +335,219 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
collectStoredAuthHeaders().forEach((value, key) => {
|
||||
if (!snapshot.has(key)) {
|
||||
snapshot.set(key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Object.fromEntries(snapshot.entries());
|
||||
}
|
||||
|
||||
function collectStoredAuthHeaders() {
|
||||
const headers = new Map();
|
||||
const candidates = [];
|
||||
|
||||
for (const storage of [window.localStorage, window.sessionStorage]) {
|
||||
try {
|
||||
for (let index = 0; index < storage.length; index += 1) {
|
||||
const key = storage.key(index) || "";
|
||||
const value = storage.getItem(key) || "";
|
||||
|
||||
if (key || value) {
|
||||
candidates.push([key, value]);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore storage access restrictions.
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, value] of candidates) {
|
||||
collectAuthFromStorageEntry(headers, key, value);
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
function collectAuthFromStorageEntry(headers, key, value) {
|
||||
const normalizedKey = String(key || "").toLowerCase();
|
||||
const normalizedValue = String(value || "").trim();
|
||||
|
||||
if (!normalizedValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
normalizedKey === "wwb-provider-authorization" ||
|
||||
normalizedKey.includes("wwb-provider-authorization")
|
||||
) {
|
||||
headers.set("wwb-provider-authorization", normalizedValue);
|
||||
}
|
||||
|
||||
if (
|
||||
normalizedKey === "spa-ts-authorization" ||
|
||||
normalizedKey.includes("spa-ts-authorization")
|
||||
) {
|
||||
headers.set("spa-ts-authorization", normalizedValue);
|
||||
}
|
||||
|
||||
if (normalizedKey === "authorization") {
|
||||
headers.set("authorization", normalizedValue);
|
||||
}
|
||||
|
||||
collectAuthFromTokenString(headers, normalizedValue);
|
||||
|
||||
if (!(normalizedValue.startsWith("{") || normalizedValue.startsWith("["))) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
collectAuthFromObject(headers, JSON.parse(normalizedValue));
|
||||
} catch {
|
||||
// Ignore non-JSON values.
|
||||
}
|
||||
}
|
||||
|
||||
function collectAuthFromObject(headers, value, depth = 0) {
|
||||
if (!value || typeof value !== "object" || depth > 5) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item) => collectAuthFromObject(headers, item, depth + 1));
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [key, entryValue] of Object.entries(value)) {
|
||||
const normalizedKey = String(key || "").toLowerCase();
|
||||
|
||||
if (typeof entryValue === "string" && entryValue.trim()) {
|
||||
collectAuthFromTokenString(headers, entryValue.trim());
|
||||
|
||||
if (
|
||||
normalizedKey === "wwb-provider-authorization" ||
|
||||
normalizedKey.includes("wwbprovider") ||
|
||||
normalizedKey.includes("wwb-provider")
|
||||
) {
|
||||
headers.set("wwb-provider-authorization", entryValue.trim());
|
||||
}
|
||||
|
||||
if (
|
||||
normalizedKey === "spa-ts-authorization" ||
|
||||
normalizedKey.includes("spats") ||
|
||||
normalizedKey.includes("spa-ts")
|
||||
) {
|
||||
headers.set("spa-ts-authorization", entryValue.trim());
|
||||
}
|
||||
|
||||
if (normalizedKey === "authorization") {
|
||||
headers.set("authorization", entryValue.trim());
|
||||
}
|
||||
}
|
||||
|
||||
collectAuthFromObject(headers, entryValue, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
function collectAuthFromTokenString(headers, value) {
|
||||
const token = String(value || "").trim();
|
||||
|
||||
if (!looksLikeJwt(token)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = decodeJwtPayload(token);
|
||||
|
||||
if (!payload) {
|
||||
return;
|
||||
}
|
||||
|
||||
const issuedFor = String(payload.issuedFor || payload.aud || "").toUpperCase();
|
||||
const authorities = Array.isArray(payload.authorities)
|
||||
? payload.authorities.map((authority) => String(authority || "").toUpperCase())
|
||||
: [];
|
||||
const scopes = Array.isArray(payload.scope)
|
||||
? payload.scope.map((scope) => String(scope || "").toUpperCase())
|
||||
: String(payload.scope || "")
|
||||
.split(/\s+/)
|
||||
.map((scope) => scope.toUpperCase())
|
||||
.filter(Boolean);
|
||||
const isWwbToken =
|
||||
issuedFor.includes("WWB") ||
|
||||
authorities.some((authority) => authority.startsWith("WLWB.")) ||
|
||||
scopes.some((scope) => scope.includes("WWB") || scope.includes("WLWB"));
|
||||
|
||||
if (isWwbToken && !headers.has("wwb-provider-authorization")) {
|
||||
headers.set("wwb-provider-authorization", token);
|
||||
}
|
||||
}
|
||||
|
||||
function looksLikeJwt(value) {
|
||||
return /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(
|
||||
String(value || "").trim()
|
||||
);
|
||||
}
|
||||
|
||||
function decodeJwtPayload(token) {
|
||||
try {
|
||||
const payloadPart = String(token).split(".")[1];
|
||||
const base64 = payloadPart.replace(/-/g, "+").replace(/_/g, "/");
|
||||
const paddedBase64 = base64.padEnd(
|
||||
base64.length + (4 - (base64.length % 4 || 4)),
|
||||
"="
|
||||
);
|
||||
const json = decodeURIComponent(
|
||||
Array.from(atob(paddedBase64), (char) => {
|
||||
return `%${char.charCodeAt(0).toString(16).padStart(2, "0")}`;
|
||||
}).join("")
|
||||
);
|
||||
|
||||
return JSON.parse(json);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchWithCapturedHeaders(url, options = {}) {
|
||||
const mergedHeaders = buildHeaders(url, options.headers, options.body);
|
||||
const response = await nativeFetch(url, {
|
||||
method: options.method || "GET",
|
||||
credentials: "include",
|
||||
cache: "no-store",
|
||||
headers: mergedHeaders,
|
||||
body: options.body,
|
||||
});
|
||||
const rawText = await response.text();
|
||||
let payload;
|
||||
|
||||
try {
|
||||
payload = rawText ? JSON.parse(rawText) : null;
|
||||
} catch {
|
||||
payload = rawText;
|
||||
}
|
||||
|
||||
return {
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
payload,
|
||||
debug: {
|
||||
url,
|
||||
headerKeys: Array.from(mergedHeaders.keys()),
|
||||
scopeKeys: getScopeKeys(url),
|
||||
spaTabFetch: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
window.__archPanelExtensionBridge = {
|
||||
buildHeaderSnapshot,
|
||||
fetchWithCapturedHeaders,
|
||||
};
|
||||
|
||||
const trackedFetch = async function archPanelTrackedFetch(input, init) {
|
||||
const url = normalizeUrl(typeof input === "string" ? input : input?.url);
|
||||
|
||||
@@ -373,14 +631,13 @@
|
||||
}
|
||||
|
||||
if (message.type === "ARCH_PANEL_HEADERS") {
|
||||
window.postMessage(
|
||||
safePostMessage(
|
||||
{
|
||||
source: MESSAGE_SOURCE,
|
||||
type: "ARCH_PANEL_HEADERS_RESULT",
|
||||
requestId: message.requestId,
|
||||
headers: buildHeaderSnapshot(message.options),
|
||||
},
|
||||
window.location.origin
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -426,7 +683,7 @@
|
||||
payload = rawText;
|
||||
}
|
||||
|
||||
window.postMessage(
|
||||
safePostMessage(
|
||||
{
|
||||
source: MESSAGE_SOURCE,
|
||||
type: "ARCH_PANEL_FETCH_RESULT",
|
||||
@@ -439,11 +696,10 @@
|
||||
transport: useXhr ? "xhr" : "fetch",
|
||||
},
|
||||
payload,
|
||||
},
|
||||
window.location.origin
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
window.postMessage(
|
||||
safePostMessage(
|
||||
{
|
||||
source: MESSAGE_SOURCE,
|
||||
type: "ARCH_PANEL_FETCH_RESULT",
|
||||
@@ -455,17 +711,15 @@
|
||||
transport: message.options?.transport || "fetch",
|
||||
},
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
window.location.origin
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
window.postMessage(
|
||||
safePostMessage(
|
||||
{
|
||||
source: MESSAGE_SOURCE,
|
||||
type: "ARCH_PANEL_BRIDGE_READY",
|
||||
},
|
||||
window.location.origin
|
||||
}
|
||||
);
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user