Files
auto-login/utils.js
T
2026-09-14 13:45:17 +08:00

100 lines
3.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 共享纯函数模块:URL 匹配 + HTML 转义
* 同时在浏览器(content script / popup)和 Node 测试环境下可用。
* 浏览器中挂载到全局作用域,Node 中通过 module.exports 暴露。
*/
(function (root, factory) {
if (typeof module === "object" && module.exports) {
module.exports = factory();
} else {
const api = factory();
root.isDomainMatch = api.isDomainMatch;
root.escapeHtml = api.escapeHtml;
}
})(typeof globalThis !== "undefined" ? globalThis : this, function () {
"use strict";
/**
* 判断当前页面 URL 是否匹配配置的 pattern。
* 兼容两种格式:
* 1. 纯 hostname:"example.com" / "*.example.com" —— 只匹配域名
* 2. hostname + path:"example.com/login" / "*.example.com/admin/*" —— 域名和路径都要匹配
* 路径部分支持 "*" 通配:
* - "login" 精确匹配 pathname 为 /login
* - "login/*" 前缀匹配:/login 或 /login/xxx
* - "*" 匹配任意路径
* - "a/* /b" 路径内任意位置通配(转正则)
* @param {string} url 当前页面完整 URL
* @param {string} pattern 配置的匹配规则
* @returns {boolean}
*/
function isDomainMatch(url, pattern) {
if (!url || !pattern) return false;
try {
const u = new URL(url);
const hostname = u.hostname;
const rawPattern = String(pattern).trim();
// 带 / 的规则走 hostname + path 匹配
if (rawPattern.includes("/")) {
const slashIdx = rawPattern.indexOf("/");
const hostPart = rawPattern.slice(0, slashIdx);
const pathPart = rawPattern.slice(slashIdx + 1) || "";
if (!matchHost(hostname, hostPart)) return false;
return matchPath(u.pathname, pathPart);
}
// 纯 hostname 匹配
return matchHost(hostname, rawPattern);
} catch {
return false;
}
}
function matchHost(hostname, hostPart) {
if (hostPart.startsWith("*.")) {
const suffix = hostPart.slice(2);
return hostname === suffix || hostname.endsWith("." + suffix);
}
if (hostPart.includes("*")) {
const regexStr = hostPart.replace(/\./g, "\\.").replace(/\*/g, ".*");
return new RegExp("^" + regexStr + "$").test(hostname);
}
return hostname === hostPart;
}
function matchPath(pathname, pathPart) {
// pathname 始终以 / 开头,去掉前导 / 再比较
const pagePath = pathname.startsWith("/") ? pathname.slice(1) : pathname;
if (!pathPart) return true;
if (pathPart === "*") return true;
// 末尾 /* 表示前缀匹配
if (pathPart.endsWith("/*")) {
const prefix = pathPart.slice(0, -2);
return pagePath === prefix || pagePath.startsWith(prefix + "/");
}
if (pathPart.includes("*")) {
const regexStr = pathPart.replace(/\//g, "\\/").replace(/\*/g, ".*");
return new RegExp("^" + regexStr + "$").test(pagePath);
}
return pagePath === pathPart;
}
/**
* HTML 转义,防止配置中的特殊字符破坏页面结构。
* @param {*} str
* @returns {string}
*/
function escapeHtml(str) {
if (str == null) return "";
return String(str)
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
return { isDomainMatch, escapeHtml };
});