hifun-tools 1.3.22 → 1.3.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @jest-environment jsdom
3
3
  */
4
- import { isIPv4, extractRootDomain, isDomainMatch, toStandardUrl, getOptimalDecodedString, } from "../utils";
4
+ import { isIPv4, isDomainMatch, toStandardUrl, getOptimalDecodedString, } from "../utils";
5
5
  // --------------------- isIPv4 -------------------------
6
6
  describe("isIPv4", () => {
7
7
  test("纯 IPv4", () => {
@@ -17,21 +17,6 @@ describe("isIPv4", () => {
17
17
  expect(isIPv4("localhost")).toBe(false);
18
18
  });
19
19
  });
20
- // --------------------- extractRootDomain -------------------------
21
- describe("extractRootDomain", () => {
22
- test("普通域名", () => {
23
- expect(extractRootDomain("https://sub.example.com")).toBe("example.com");
24
- });
25
- test("多级后缀", () => {
26
- expect(extractRootDomain("a.b.example.com.cn")).toBe("example.com.cn");
27
- });
28
- test("无协议", () => {
29
- expect(extractRootDomain("www.test.co.uk")).toBe("test.co.uk");
30
- });
31
- test("非法 URL 返回空", () => {
32
- expect(extractRootDomain("%%%--非法")).toBe("");
33
- });
34
- });
35
20
  // --------------------- isDomainMatch -------------------------
36
21
  describe("isDomainMatch", () => {
37
22
  test("匹配根域", () => {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 通用资源加载函数(支持 Vite Webpack)
2
+ * 通用资源加载函数(支持 Vite / Webpack / Rspack
3
3
  * @param path - 相对路径,如 "tenant/config.json" 或 "tenant/logo.png"
4
4
  */
5
5
  export declare function getResource(path: string): any;
@@ -1,15 +1,38 @@
1
1
  /**
2
- * 通用资源加载函数(支持 Vite Webpack)
2
+ * 通用资源加载函数(支持 Vite / Webpack / Rspack
3
3
  * @param path - 相对路径,如 "tenant/config.json" 或 "tenant/logo.png"
4
4
  */
5
5
  export function getResource(path) {
6
6
  const isVite = typeof import.meta !== "undefined" && !!import.meta.env;
7
- const isWebpack = typeof __webpack_require__ === "function" ||
8
- (typeof require !== "undefined" &&
9
- typeof require.context === "function");
10
- if (isVite) {
11
- // ---------------- VITE ----------------
12
- const modules = import.meta.glob([
7
+ // ---------------- 新增 RSPACK 检测 ----------------
8
+ const isRspack = typeof __rspack_require__ === "function" ||
9
+ (typeof __webpack_require__ === "function" &&
10
+ !!__webpack_require__.s && // Rspack 专属字段
11
+ !__webpack_require__.m); // Webpack m,Rspack 不一定有
12
+ const isWebpack = !isRspack && // 不冲突:优先识别 Rspack
13
+ (typeof __webpack_require__ === "function" ||
14
+ (typeof require !== "undefined" &&
15
+ typeof require.context === "function"));
16
+ // ---------------- RSPACK ----------------
17
+ if (isRspack) {
18
+ try {
19
+ const ctx = require.context("/config", true, /\.(json|png|jpg|jpeg|svg|webp)$/);
20
+ const files = ctx.keys();
21
+ const match = files.find((key) => key.endsWith(path));
22
+ if (!match) {
23
+ console.warn(`[getResource] 未找到文件: ${path}`);
24
+ return null;
25
+ }
26
+ return ctx(match);
27
+ }
28
+ catch (e) {
29
+ console.error("[getResource] Rspack context 加载失败", e);
30
+ }
31
+ }
32
+ // ---------------- VITE ----------------
33
+ else if (isVite) {
34
+ const { glob } = import.meta;
35
+ const modules = glob([
13
36
  "/config/**/*.json",
14
37
  "/config/**/*.png",
15
38
  "/config/**/*.jpg",
@@ -25,11 +48,10 @@ export function getResource(path) {
25
48
  console.warn(`[getResource] 未找到文件: ${path}`);
26
49
  return null;
27
50
  }
28
- const [, mod] = match;
29
- return mod;
51
+ return match[1];
30
52
  }
31
- if (isWebpack) {
32
- // ---------------- WEBPACK ----------------
53
+ // ---------------- WEBPACK ----------------
54
+ else if (isWebpack) {
33
55
  const ctx = require.context("/config", true, /\.(json|png|jpg|jpeg|svg|webp)$/);
34
56
  const files = ctx.keys();
35
57
  const match = files.find((key) => key.endsWith(path));
@@ -39,6 +61,6 @@ export function getResource(path) {
39
61
  }
40
62
  return ctx(match);
41
63
  }
42
- console.error("[getResource] 不支持的构建环境(需要 ViteWebpack)");
64
+ console.error("[getResource] 不支持的构建环境(需要 Vite/Webpack/Rspack)");
43
65
  return null;
44
66
  }
@@ -70,7 +70,7 @@ class InitCls {
70
70
  }
71
71
  // 默认租户匹配
72
72
  if (host.includes("iggame") || host.includes("localhost")) {
73
- console.info(`🏠 iggame匹配到默认租户`);
73
+ console.info(`🏠 iggame匹配到默认租户2`);
74
74
  return { tenant: "iggame" };
75
75
  }
76
76
  throw new Error("未找到匹配租户");
@@ -80,8 +80,9 @@ class InitCls {
80
80
  const host = location.host;
81
81
  if (host.includes("iggame") ||
82
82
  host.includes("localhost") ||
83
- location.origin.includes(this.localPath.replace(/\/+$/, ""))) {
84
- console.info(`🏠 iggame匹配到默认租户`);
83
+ (!!this.localPath &&
84
+ location.origin.includes(this.localPath.replace(/\/+$/, "")))) {
85
+ console.info(`🏠 iggame匹配到默认租户1`);
85
86
  return { tenant: "iggame" };
86
87
  }
87
88
  throw new Error("无法获取有效的租户信息");
@@ -153,10 +154,12 @@ class InitCls {
153
154
  return this.domainBaseUrl;
154
155
  }
155
156
  catch (error) {
157
+ console.error(error);
156
158
  if ((this.defaultBaseUrl &&
157
159
  (location.host.includes("iggame") ||
158
160
  location.host.includes("localhost"))) ||
159
- location.origin.includes(this.localPath.replace(/\/+$/, ""))) {
161
+ (!!this.localPath &&
162
+ location.origin.includes(this.localPath.replace(/\/+$/, "")))) {
160
163
  console.info(`🏠 iggame匹配到默认链接`, this.defaultBaseUrl);
161
164
  this.domainBaseUrl = toStandardUrl(this.defaultBaseUrl);
162
165
  return this.domainBaseUrl;
@@ -6,23 +6,7 @@ export type TenantDict = {
6
6
  };
7
7
  export declare function isIPv4(str: string): boolean;
8
8
  export declare function getOptimalDecodedString(encodedArray: string[]): Promise<string>;
9
- /**
10
- * 提取主域名(支持多级 TLD)
11
- */
12
- export declare function extractRootDomain(url: string): string;
13
- /**
14
- * 判断 b 是否匹配 a[] 中任意一个域名
15
- */
16
9
  export declare function isDomainMatch(list: string[], target: string): boolean;
17
10
  export declare function toStandardUrl(input: string): string;
18
- /**
19
- * 匹配规则:模糊 + 正则 + 不区分大小写
20
- */
21
11
  export declare function matchBrowser(checkItem: string, currentBrowser: string): boolean;
22
- /**
23
- * 1. 过滤 browserCheck
24
- * 2. 按 tenant 过滤
25
- * 3. 按 groupType(可为空)过滤
26
- * 4. 与 list2 求交集
27
- */
28
12
  export declare function filterSmartLines(list1: TenantDict[], list2: string[], groupType?: string, tenant?: string): string[];
@@ -18,7 +18,6 @@ export async function getOptimalDecodedString(encodedArray) {
18
18
  throw new Error("输入数组为空或无效");
19
19
  const hostname = window.location.hostname;
20
20
  const isIp = isIPv4(hostname);
21
- // ✨ 关键增强:先 trim 再过滤
22
21
  const decodedList = encodedArray
23
22
  .filter(Boolean)
24
23
  .map((v) => v.trim());
@@ -30,7 +29,6 @@ export async function getOptimalDecodedString(encodedArray) {
30
29
  return;
31
30
  }
32
31
  const currentProtocol = window.location.protocol;
33
- // 过滤协议
34
32
  const filteredArr = arr.filter((url) => url.startsWith("http") ? url.startsWith(currentProtocol) : true);
35
33
  const targetArr = filteredArr.length ? filteredArr : arr;
36
34
  if (targetArr.length === 1)
@@ -62,77 +60,70 @@ export async function getOptimalDecodedString(encodedArray) {
62
60
  return isIp ? await getOptimal(ipList) : await getOptimal(domainList);
63
61
  }
64
62
  /**
65
- * 常见多级后缀(Public Suffix List 的子集,可按需扩展)
66
- * 如 .co.uk、.com.cn、.gov.cn、.org.cn 等
63
+ * normalizeHost:保留端口,只清理协议
67
64
  */
68
- const MULTI_LEVEL_TLDS = new Set([
69
- "co.uk",
70
- "org.uk",
71
- "gov.uk",
72
- "com.cn",
73
- "net.cn",
74
- "gov.cn",
75
- "org.cn",
76
- "co.jp",
77
- "ne.jp",
78
- "or.jp",
79
- "go.jp",
80
- "com.au",
81
- "net.au",
82
- "org.au",
83
- "co.kr",
84
- "ne.kr",
85
- "or.kr",
86
- ]);
87
- /**
88
- * 提取主域名(支持多级 TLD)
89
- */
90
- export function extractRootDomain(url) {
65
+ function normalizeHost(hostname) {
66
+ hostname = hostname.trim().toLowerCase();
67
+ // 如果没有协议,加上 http:// 方便 URL 解析
68
+ let urlStr = hostname;
69
+ if (!/^https?:\/\//i.test(hostname)) {
70
+ urlStr = "http://" + hostname;
71
+ }
91
72
  try {
92
- url = url.trim(); // ✨ 增加
93
- if (!/^https?:\/\//i.test(url)) {
94
- url = "http://" + url;
95
- }
96
- const { hostname } = new URL(url);
97
- const parts = hostname.split(".").filter(Boolean);
98
- if (parts.length < 2)
99
- return hostname;
100
- const last2 = parts.slice(-2).join(".");
101
- if (MULTI_LEVEL_TLDS.has(last2)) {
102
- if (parts.length >= 3) {
103
- return parts.slice(-3).join(".");
104
- }
105
- }
106
- return last2;
73
+ const url = new URL(urlStr);
74
+ // 保留 hostname + port
75
+ return url.port ? `${url.hostname}:${url.port}` : url.hostname;
107
76
  }
108
- catch (e) {
109
- return "";
77
+ catch (_) {
78
+ return hostname; // 保留原始字符串
110
79
  }
111
80
  }
112
81
  /**
113
- * 判断 b 是否匹配 a[] 中任意一个域名
82
+ * 域名严格匹配,允许 www 特例
114
83
  */
84
+ function isDomainStrictEqual(a, b) {
85
+ a = normalizeHost(a);
86
+ b = normalizeHost(b);
87
+ if (a === b)
88
+ return true;
89
+ const stripWww = (h) => (h.startsWith("www.") ? h.slice(4) : h);
90
+ return stripWww(a) === stripWww(b);
91
+ }
115
92
  export function isDomainMatch(list, target) {
116
- const normalizedList = list.map(extractRootDomain).filter(Boolean);
117
- const targetDomain = extractRootDomain(target);
118
- return normalizedList.includes(targetDomain);
93
+ const t = normalizeHost(target);
94
+ return list.some((item) => {
95
+ const h = normalizeHost(item);
96
+ return isDomainStrictEqual(h, t);
97
+ });
119
98
  }
120
99
  export function toStandardUrl(input) {
121
100
  try {
122
- input = input.trim(); // ✨ 增加
101
+ if (!input)
102
+ return "";
103
+ input = input.trim();
123
104
  let protocol = window?.location?.protocol || "https:";
124
105
  if (!/^https?:\/\//i.test(input)) {
125
106
  input = protocol + "//" + input;
126
107
  }
127
108
  const url = new URL(input);
128
- return `${url.protocol}//${url.hostname}`;
109
+ const hostname = url.hostname;
110
+ const port = url.port;
111
+ const isHostnameValid = hostname === "localhost" ||
112
+ (/^[a-zA-Z0-9.-]+$/.test(hostname) &&
113
+ (hostname.includes(".") || isIPv4(hostname)));
114
+ if (!isHostnameValid) {
115
+ return "";
116
+ }
117
+ return port
118
+ ? `${url.protocol}//${hostname}:${port}`
119
+ : `${url.protocol}//${hostname}`;
129
120
  }
130
121
  catch (e) {
131
- return input;
122
+ return "";
132
123
  }
133
124
  }
134
125
  /**
135
- * 浏览器识别:返回一个归一化的浏览器类型字符串
126
+ * 浏览器识别
136
127
  */
137
128
  function detectBrowser() {
138
129
  const ua = navigator.userAgent.toLowerCase();
@@ -165,17 +156,11 @@ function detectBrowser() {
165
156
  }
166
157
  return "unknown";
167
158
  }
168
- /**
169
- * 匹配规则:模糊 + 正则 + 不区分大小写
170
- */
171
159
  export function matchBrowser(checkItem, currentBrowser) {
172
- if (!checkItem)
173
- return false;
174
- if (!currentBrowser)
160
+ if (!checkItem || !currentBrowser)
175
161
  return false;
176
162
  const normalized = checkItem.toString().toLowerCase();
177
163
  const browser = currentBrowser.toLowerCase();
178
- // 正则格式,如 "/uc/i"
179
164
  if (normalized.startsWith("/") && normalized.endsWith("/")) {
180
165
  try {
181
166
  const body = normalized.slice(1, -1);
@@ -184,15 +169,8 @@ export function matchBrowser(checkItem, currentBrowser) {
184
169
  }
185
170
  catch (_) { }
186
171
  }
187
- // 字符串模糊匹配
188
172
  return browser.includes(normalized);
189
173
  }
190
- /**
191
- * 1. 过滤 browserCheck
192
- * 2. 按 tenant 过滤
193
- * 3. 按 groupType(可为空)过滤
194
- * 4. 与 list2 求交集
195
- */
196
174
  export function filterSmartLines(list1, list2, groupType, tenant) {
197
175
  if (!Array.isArray(list1) || !Array.isArray(list2)) {
198
176
  throw new Error("前两个参数必须是数组");
@@ -203,12 +181,10 @@ export function filterSmartLines(list1, list2, groupType, tenant) {
203
181
  const blocked = checks.some((checkItem) => matchBrowser(checkItem, currentBrowser));
204
182
  return !blocked;
205
183
  });
206
- if (tenant) {
184
+ if (tenant)
207
185
  result = result.filter((item) => item.tenant === tenant);
208
- }
209
- if (groupType) {
186
+ if (groupType)
210
187
  result = result.filter((item) => item.lineGroup === groupType);
211
- }
212
188
  const lines = result.map((item) => toStandardUrl(item.line));
213
- return list2.map(toStandardUrl).filter((v) => isDomainMatch(lines, v)); // ✨ 使用域名匹配
189
+ return list2.map(toStandardUrl).filter((v) => isDomainMatch(lines, v));
214
190
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hifun-tools",
3
- "version": "1.3.22",
3
+ "version": "1.3.24",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",