358 lines
8.1 KiB
JavaScript
358 lines
8.1 KiB
JavaScript
const WEBVIEW_TOKEN_STORAGE_KEY = "bmt_webview_token";
|
|
let cachedToken = "";
|
|
const MAX_NESTED_PARSE_DEPTH = 4;
|
|
const NESTED_URL_PARAM_KEYS = [
|
|
"url",
|
|
"targetUrl",
|
|
"target_url",
|
|
"redirect",
|
|
"redirectUrl",
|
|
"redirect_url",
|
|
"redirectUri",
|
|
"redirect_uri",
|
|
"returnUrl",
|
|
"return_url",
|
|
"link",
|
|
"href",
|
|
];
|
|
const NESTED_URL_PARAM_KEYS_LOWER = NESTED_URL_PARAM_KEYS.map((item) =>
|
|
item.toLowerCase(),
|
|
);
|
|
|
|
function safeDecodeURIComponent(value) {
|
|
if (!value) {
|
|
return "";
|
|
}
|
|
|
|
try {
|
|
return decodeURIComponent(value);
|
|
} catch (error) {
|
|
return String(value);
|
|
}
|
|
}
|
|
|
|
function decodeMultiEncodedValue(value) {
|
|
let currentValue = String(value || "");
|
|
if (!currentValue) {
|
|
return "";
|
|
}
|
|
|
|
for (let round = 0; round < 4; round += 1) {
|
|
const decodedValue = safeDecodeURIComponent(currentValue);
|
|
if (!decodedValue || decodedValue === currentValue) {
|
|
break;
|
|
}
|
|
currentValue = decodedValue;
|
|
}
|
|
|
|
return currentValue;
|
|
}
|
|
|
|
function sanitizeTokenValue(token) {
|
|
let normalizedToken = decodeMultiEncodedValue(token || "").trim();
|
|
if (!normalizedToken) {
|
|
return "";
|
|
}
|
|
|
|
if (/^bearer\s+/i.test(normalizedToken)) {
|
|
normalizedToken = normalizedToken.replace(/^bearer\s+/i, "").trim();
|
|
}
|
|
|
|
const hashIndex = normalizedToken.indexOf("#");
|
|
if (hashIndex > -1) {
|
|
normalizedToken = normalizedToken.slice(0, hashIndex).trim();
|
|
}
|
|
|
|
const ampersandIndex = normalizedToken.indexOf("&");
|
|
if (ampersandIndex > -1) {
|
|
normalizedToken = normalizedToken.slice(0, ampersandIndex).trim();
|
|
}
|
|
|
|
return normalizedToken;
|
|
}
|
|
|
|
function parseQueryByRegex(queryText) {
|
|
const query = String(queryText || "").trim();
|
|
if (!query) {
|
|
return [];
|
|
}
|
|
|
|
const normalizedQuery = query.replace(/^[?#&/]+/, "");
|
|
if (!normalizedQuery) {
|
|
return [];
|
|
}
|
|
|
|
const entries = [];
|
|
const pairs = normalizedQuery.split("&");
|
|
for (let index = 0; index < pairs.length; index += 1) {
|
|
const pair = String(pairs[index] || "");
|
|
if (!pair) {
|
|
continue;
|
|
}
|
|
|
|
const splitIndex = pair.indexOf("=");
|
|
if (splitIndex === -1) {
|
|
continue;
|
|
}
|
|
|
|
const key = pair.slice(0, splitIndex);
|
|
const value = pair.slice(splitIndex + 1);
|
|
if (!key) {
|
|
continue;
|
|
}
|
|
|
|
entries.push({
|
|
key: key,
|
|
value: value,
|
|
});
|
|
}
|
|
|
|
return entries;
|
|
}
|
|
|
|
function findTokenInText(text, depth = 0) {
|
|
if (depth > MAX_NESTED_PARSE_DEPTH) {
|
|
return "";
|
|
}
|
|
if (!text) {
|
|
return "";
|
|
}
|
|
|
|
const rawText = String(text || "").trim();
|
|
if (!rawText) {
|
|
return "";
|
|
}
|
|
|
|
const candidates = [];
|
|
const queryIndex = rawText.indexOf("?");
|
|
const hashIndex = rawText.indexOf("#");
|
|
|
|
if (queryIndex > -1) {
|
|
let queryCandidate = rawText.slice(queryIndex + 1);
|
|
const hashInQuery = queryCandidate.indexOf("#");
|
|
if (hashInQuery > -1) {
|
|
queryCandidate = queryCandidate.slice(0, hashInQuery);
|
|
}
|
|
candidates.push(queryCandidate);
|
|
}
|
|
|
|
if (hashIndex > -1) {
|
|
candidates.push(rawText.slice(hashIndex + 1));
|
|
}
|
|
|
|
candidates.push(rawText);
|
|
|
|
for (let index = 0; index < candidates.length; index += 1) {
|
|
let queryText = String(candidates[index] || "").trim();
|
|
if (!queryText) {
|
|
continue;
|
|
}
|
|
|
|
const nextQueryIndex = queryText.indexOf("?");
|
|
if (nextQueryIndex > -1) {
|
|
queryText = queryText.slice(nextQueryIndex + 1);
|
|
}
|
|
|
|
queryText = queryText.replace(/^[/#&]+/, "");
|
|
if (!queryText || queryText.indexOf("=") === -1) {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
const search = new URLSearchParams(queryText);
|
|
const token = search.get("token") || search.get("access_token") || "";
|
|
if (token) {
|
|
return sanitizeTokenValue(token);
|
|
}
|
|
|
|
for (let keyIndex = 0; keyIndex < NESTED_URL_PARAM_KEYS.length; keyIndex += 1) {
|
|
const nestedKey = NESTED_URL_PARAM_KEYS[keyIndex];
|
|
const nestedValue = search.get(nestedKey) || "";
|
|
if (!nestedValue) {
|
|
continue;
|
|
}
|
|
|
|
const nestedDecodedValue = decodeMultiEncodedValue(nestedValue);
|
|
const nestedToken =
|
|
findTokenInText(nestedValue, depth + 1) ||
|
|
findTokenInText(nestedDecodedValue, depth + 1);
|
|
if (nestedToken) {
|
|
return nestedToken;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
// Continue to fallback regex mode.
|
|
}
|
|
|
|
const matcher = queryText.match(
|
|
/(?:^|[?&#/])(token|access_token)=([^&#]+)/i,
|
|
);
|
|
if (matcher && matcher[2]) {
|
|
return sanitizeTokenValue(matcher[2]);
|
|
}
|
|
|
|
const fallbackEntries = parseQueryByRegex(queryText);
|
|
for (let entryIndex = 0; entryIndex < fallbackEntries.length; entryIndex += 1) {
|
|
const entry = fallbackEntries[entryIndex];
|
|
const entryKey = String(entry.key || "");
|
|
const entryValue = String(entry.value || "");
|
|
if (!entryKey || !entryValue) {
|
|
continue;
|
|
}
|
|
|
|
const normalizedEntryKey = entryKey.toLowerCase();
|
|
const isTokenKey =
|
|
normalizedEntryKey === "token" || normalizedEntryKey === "access_token";
|
|
if (isTokenKey) {
|
|
return sanitizeTokenValue(entryValue);
|
|
}
|
|
|
|
const isNestedUrlKey =
|
|
NESTED_URL_PARAM_KEYS_LOWER.indexOf(normalizedEntryKey) > -1;
|
|
if (!isNestedUrlKey) {
|
|
continue;
|
|
}
|
|
|
|
const nestedDecodedValue = decodeMultiEncodedValue(entryValue);
|
|
const nestedToken =
|
|
findTokenInText(entryValue, depth + 1) ||
|
|
findTokenInText(nestedDecodedValue, depth + 1);
|
|
if (nestedToken) {
|
|
return nestedToken;
|
|
}
|
|
}
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
export function extractTokenFromUrl(url) {
|
|
if (!url) {
|
|
return "";
|
|
}
|
|
|
|
const directToken = findTokenInText(url);
|
|
if (directToken) {
|
|
return directToken;
|
|
}
|
|
|
|
const hashIndex = url.indexOf("#");
|
|
if (hashIndex === -1) {
|
|
return "";
|
|
}
|
|
|
|
return findTokenInText(url.slice(hashIndex + 1));
|
|
}
|
|
|
|
export function getCurrentWebviewUrl() {
|
|
// #ifdef H5
|
|
return window.location.href || "";
|
|
// #endif
|
|
|
|
// #ifdef APP-PLUS
|
|
if (typeof plus === "undefined" || !plus.webview) {
|
|
return "";
|
|
}
|
|
|
|
const currentWebview =
|
|
plus.webview.currentWebview() || plus.webview.getLaunchWebview();
|
|
|
|
if (!currentWebview || typeof currentWebview.getURL !== "function") {
|
|
return "";
|
|
}
|
|
|
|
return currentWebview.getURL() || "";
|
|
// #endif
|
|
|
|
return "";
|
|
}
|
|
|
|
function getH5ReferrerUrl() {
|
|
// #ifdef H5
|
|
if (typeof document !== "undefined") {
|
|
return String(document.referrer || "").trim();
|
|
}
|
|
// #endif
|
|
|
|
return "";
|
|
}
|
|
|
|
function normalizeToken(token) {
|
|
return sanitizeTokenValue(token);
|
|
}
|
|
|
|
function saveTokenToStorage(token) {
|
|
const normalizedToken = normalizeToken(token);
|
|
if (!normalizedToken) {
|
|
return "";
|
|
}
|
|
|
|
cachedToken = normalizedToken;
|
|
|
|
try {
|
|
if (typeof uni !== "undefined" && uni.setStorageSync) {
|
|
uni.setStorageSync(WEBVIEW_TOKEN_STORAGE_KEY, normalizedToken);
|
|
}
|
|
} catch (error) {
|
|
// Ignore storage failures and keep in-memory fallback.
|
|
}
|
|
|
|
return normalizedToken;
|
|
}
|
|
|
|
function readTokenFromStorage() {
|
|
if (cachedToken) {
|
|
return cachedToken;
|
|
}
|
|
|
|
try {
|
|
if (typeof uni !== "undefined" && uni.getStorageSync) {
|
|
const token = normalizeToken(uni.getStorageSync(WEBVIEW_TOKEN_STORAGE_KEY));
|
|
if (token) {
|
|
cachedToken = token;
|
|
return token;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
// Ignore storage failures and fallback to runtime token extraction.
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
export function setCurrentWebviewToken(token) {
|
|
return saveTokenToStorage(token);
|
|
}
|
|
|
|
export function refreshCurrentWebviewToken(url) {
|
|
const candidateUrls = [];
|
|
const inputUrl = String(url || "").trim();
|
|
const currentUrl = String(getCurrentWebviewUrl() || "").trim();
|
|
const referrerUrl = getH5ReferrerUrl();
|
|
|
|
if (inputUrl) {
|
|
candidateUrls.push(inputUrl);
|
|
}
|
|
|
|
if (currentUrl && candidateUrls.indexOf(currentUrl) === -1) {
|
|
candidateUrls.push(currentUrl);
|
|
}
|
|
|
|
if (referrerUrl && candidateUrls.indexOf(referrerUrl) === -1) {
|
|
candidateUrls.push(referrerUrl);
|
|
}
|
|
|
|
for (let index = 0; index < candidateUrls.length; index += 1) {
|
|
const nextToken = extractTokenFromUrl(candidateUrls[index]);
|
|
if (nextToken) {
|
|
return saveTokenToStorage(nextToken);
|
|
}
|
|
}
|
|
|
|
return readTokenFromStorage();
|
|
}
|
|
|
|
export function getCurrentWebviewToken() {
|
|
return refreshCurrentWebviewToken();
|
|
}
|