bm-bmt/utils/webview-token.js

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();
}