hifun-tools 1.3.16 → 1.3.18

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.
@@ -35,15 +35,18 @@ describe("extractRootDomain", () => {
35
35
  // --------------------- isDomainMatch -------------------------
36
36
  describe("isDomainMatch", () => {
37
37
  test("匹配根域", () => {
38
- const list = ["a.example.com", "b.test.co.uk"];
39
- expect(isDomainMatch(list, "https://api.example.com")).toBe(true);
38
+ const list = [
39
+ " localhost:3000/asdnajkd ",
40
+ " http://192.168.101.85:3000/asdasdasd ",
41
+ ];
42
+ expect(isDomainMatch(list, "localhost:3000")).toBe(true);
40
43
  });
41
44
  test("多级后缀匹配", () => {
42
- const list = ["node.test.co.uk"];
45
+ const list = [" node.test.co.uk "];
43
46
  expect(isDomainMatch(list, "api.test.co.uk")).toBe(true);
44
47
  });
45
48
  test("不匹配", () => {
46
- const list = ["a.example.com"];
49
+ const list = [" a.example.com "];
47
50
  expect(isDomainMatch(list, "another.com")).toBe(false);
48
51
  });
49
52
  });
@@ -1,4 +1,4 @@
1
- import { isDomainMatch, toStandardUrl } from "./utils";
1
+ import { filterSmartLines, isDomainMatch, matchBrowser, TenantDict, toStandardUrl } from "./utils";
2
2
  import { AesDecrypt, AesEncrypt } from "./ende";
3
3
  type TenantConfig = {
4
4
  PUBLIC_TITLE: string;
@@ -9,6 +9,8 @@ type TenantConfig = {
9
9
  declare class InitCls {
10
10
  private domainBaseUrl;
11
11
  private tenantConfig;
12
+ private tenantDict;
13
+ private tenantDictList;
12
14
  private initialized;
13
15
  private tenant;
14
16
  private defaultBaseUrl;
@@ -16,6 +18,8 @@ declare class InitCls {
16
18
  getBaseUrl(): string;
17
19
  /** 获取配置 */
18
20
  getTenantConfig(): TenantConfig | null;
21
+ getTenantDict(): TenantDict | null;
22
+ getTenantDictList(): TenantDict[];
19
23
  /** 获取租户名 */
20
24
  getTenant(): string;
21
25
  getImgPath(imgName: string): string;
@@ -33,4 +37,4 @@ declare class InitCls {
33
37
  AesDecrypt: typeof AesDecrypt;
34
38
  }
35
39
  declare const HF: InitCls;
36
- export { HF, isDomainMatch, toStandardUrl, AesEncrypt, AesDecrypt };
40
+ export { HF, isDomainMatch, toStandardUrl, AesEncrypt, AesDecrypt, filterSmartLines, matchBrowser, };
@@ -1,9 +1,11 @@
1
1
  import { getResource } from "./getResource";
2
- import { getOptimalDecodedString, isDomainMatch, toStandardUrl } from "./utils";
2
+ import { filterSmartLines, getOptimalDecodedString, isDomainMatch, matchBrowser, toStandardUrl, } from "./utils";
3
3
  import { AesDecrypt, AesEncrypt } from "./ende";
4
4
  class InitCls {
5
5
  domainBaseUrl = "";
6
6
  tenantConfig = null;
7
+ tenantDict = null;
8
+ tenantDictList = [];
7
9
  initialized = false;
8
10
  tenant = "";
9
11
  defaultBaseUrl = "";
@@ -21,6 +23,18 @@ class InitCls {
21
23
  }
22
24
  return this.tenantConfig;
23
25
  }
26
+ getTenantDict() {
27
+ if (!this.initialized || !this.tenant) {
28
+ throw new Error("租户尚未初始化或初始化失败getTenantDict");
29
+ }
30
+ return this.tenantDict;
31
+ }
32
+ getTenantDictList() {
33
+ if (!this.initialized || !this.tenant) {
34
+ throw new Error("租户尚未初始化或初始化失败getTenantDictList");
35
+ }
36
+ return this.tenantDictList;
37
+ }
24
38
  /** 获取租户名 */
25
39
  getTenant() {
26
40
  return this.tenant;
@@ -34,7 +48,7 @@ class InitCls {
34
48
  /** 严格同步获取租户信息 */
35
49
  async getTenantInfoStrictSync() {
36
50
  try {
37
- const response = await fetch(`/lineTenants.txt?t=${Date.now()}`, {
51
+ const response = await fetch(`/lineDict.txt?t=${Date.now()}`, {
38
52
  method: "GET",
39
53
  cache: "no-store",
40
54
  });
@@ -45,15 +59,14 @@ class InitCls {
45
59
  const rawData = AesDecrypt(rawText);
46
60
  const data = JSON.parse(rawData);
47
61
  const host = location.host;
48
- const matchedEntry = Object.entries(data).find(([key]) => {
49
- return isDomainMatch([key], host);
62
+ const matchedEntry = data.find((item) => {
63
+ return isDomainMatch([item.line], host);
50
64
  });
65
+ this.tenantDictList = data;
51
66
  if (matchedEntry) {
52
- const matched = matchedEntry[1];
53
- if (data && matched) {
54
- console.info(`🏠 匹配租户: ${matched}`);
55
- return { tenant: matched };
56
- }
67
+ this.tenantDict = matchedEntry;
68
+ console.info(`🏠 匹配租户: ${matchedEntry.tenant}`);
69
+ return { tenant: matchedEntry.tenant };
57
70
  }
58
71
  // 默认租户匹配
59
72
  if (host.includes("iggame") || host.includes("localhost")) {
@@ -63,7 +76,7 @@ class InitCls {
63
76
  throw new Error("未找到匹配租户");
64
77
  }
65
78
  catch (error) {
66
- console.error("解析 lineTenants.txt 失败:", error);
79
+ console.error("解析 lineDict.txt 失败:", error);
67
80
  const host = location.host;
68
81
  if (host.includes("iggame") ||
69
82
  host.includes("localhost") ||
@@ -99,17 +112,17 @@ class InitCls {
99
112
  this.localPath = localPath;
100
113
  }
101
114
  console.log("加载类型:", fileType);
102
- const tasks = [];
115
+ const results = [];
116
+ if (fileType.includes("lineDict")) {
117
+ console.log("初始化 lineDict...");
118
+ const res1 = await this.initialize();
119
+ results.push(res1);
120
+ }
103
121
  if (fileType.includes("lineAddress")) {
104
122
  console.log("初始化 lineAddress...");
105
- tasks.push(this.LoadGatewayConfig());
106
- }
107
- if (fileType.includes("lineTenants")) {
108
- console.log("初始化 lineTenants...");
109
- tasks.push(this.initialize());
123
+ const res2 = await this.LoadGatewayConfig();
124
+ results.push(res2);
110
125
  }
111
- // 等待所有异步任务完成
112
- const results = await Promise.all(tasks);
113
126
  console.log("所有初始化完成:", results);
114
127
  return results;
115
128
  }
@@ -119,7 +132,10 @@ class InitCls {
119
132
  try {
120
133
  const response = await fetch(`/lineAddress.txt?t=${Date.now()}`);
121
134
  const configText = await response.text();
122
- const baseUrl = JSON.parse(AesDecrypt(configText));
135
+ let baseUrl = JSON.parse(AesDecrypt(configText));
136
+ const dict = this.getTenantDict();
137
+ const dictList = this.getTenantDictList();
138
+ baseUrl = filterSmartLines(dictList, baseUrl, dict?.lineGroup);
123
139
  if (Array.isArray(baseUrl)) {
124
140
  try {
125
141
  this.domainBaseUrl = toStandardUrl(await getOptimalDecodedString(baseUrl));
@@ -155,4 +171,4 @@ class InitCls {
155
171
  AesDecrypt = AesDecrypt;
156
172
  }
157
173
  const HF = new InitCls();
158
- export { HF, isDomainMatch, toStandardUrl, AesEncrypt, AesDecrypt };
174
+ export { HF, isDomainMatch, toStandardUrl, AesEncrypt, AesDecrypt, filterSmartLines, matchBrowser, };
@@ -1,3 +1,9 @@
1
+ export type TenantDict = {
2
+ browserCheck: string[];
3
+ line: string;
4
+ lineGroup: string;
5
+ tenant: string;
6
+ };
1
7
  export declare function isIPv4(str: string): boolean;
2
8
  export declare function getOptimalDecodedString(encodedArray: string[]): Promise<string>;
3
9
  /**
@@ -9,3 +15,14 @@ export declare function extractRootDomain(url: string): string;
9
15
  */
10
16
  export declare function isDomainMatch(list: string[], target: string): boolean;
11
17
  export declare function toStandardUrl(input: string): string;
18
+ /**
19
+ * 匹配规则:模糊 + 正则 + 不区分大小写
20
+ */
21
+ export declare function matchBrowser(checkItem: string, currentBrowser: string): boolean;
22
+ /**
23
+ * 1. 过滤 browserCheck
24
+ * 2. 按 tenant 过滤
25
+ * 3. 按 groupType(可为空)过滤
26
+ * 4. 与 list2 求交集
27
+ */
28
+ export declare function filterSmartLines(list1: TenantDict[], list2: string[], groupType?: string, tenant?: string): string[];
@@ -131,3 +131,90 @@ export function toStandardUrl(input) {
131
131
  return input;
132
132
  }
133
133
  }
134
+ /**
135
+ * 浏览器识别:返回一个归一化的浏览器类型字符串
136
+ */
137
+ function detectBrowser() {
138
+ const ua = navigator.userAgent.toLowerCase();
139
+ const rules = [
140
+ { key: "baidu", regex: /baidubrowser|baiduboxapp|baidu/i },
141
+ { key: "uc", regex: /ucbrowser|ucweb/i },
142
+ { key: "qq", regex: /mqqbrowser|qqbrowser/i },
143
+ { key: "quark", regex: /quarkbrowser|quark/i },
144
+ { key: "sougou", regex: /se 2\.x|sogoumobilebrowser|sogou/i },
145
+ { key: "360", regex: /360browser|qhbrowser|360se|360ee/i },
146
+ { key: "xiaomi", regex: /miuibrowser/i },
147
+ { key: "huawei", regex: /huaweibrowser|hwbrowser/i },
148
+ { key: "oppo", regex: /oppobrowser/i },
149
+ { key: "vivo", regex: /vivobrowser/i },
150
+ { key: "samsung", regex: /samsungbrowser/i },
151
+ { key: "edge", regex: /edg|edge/i },
152
+ { key: "firefox", regex: /firefox/i },
153
+ { key: "opera", regex: /opr|opera/i },
154
+ { key: "safari", regex: /safari/i, exclude: /chrome|crios|android/i },
155
+ { key: "chrome", regex: /chrome|crios/i },
156
+ { key: "weixin", regex: /micromessenger/i },
157
+ { key: "alipay", regex: /alipayclient/i },
158
+ { key: "douyin", regex: /aweme|douyin/i },
159
+ ];
160
+ for (const item of rules) {
161
+ if (item.exclude && item.exclude.test(ua))
162
+ continue;
163
+ if (item.regex.test(ua))
164
+ return item.key;
165
+ }
166
+ return "unknown";
167
+ }
168
+ /**
169
+ * 匹配规则:模糊 + 正则 + 不区分大小写
170
+ */
171
+ export function matchBrowser(checkItem, currentBrowser) {
172
+ if (!checkItem)
173
+ return false;
174
+ if (!currentBrowser)
175
+ return false;
176
+ const normalized = checkItem.toString().toLowerCase();
177
+ const browser = currentBrowser.toLowerCase();
178
+ // 正则格式,如 "/uc/i"
179
+ if (normalized.startsWith("/") && normalized.endsWith("/")) {
180
+ try {
181
+ const body = normalized.slice(1, -1);
182
+ const regex = new RegExp(body, "i");
183
+ return regex.test(browser);
184
+ }
185
+ catch (_) { }
186
+ }
187
+ // 字符串模糊匹配
188
+ return browser.includes(normalized);
189
+ }
190
+ /**
191
+ * 1. 过滤 browserCheck
192
+ * 2. 按 tenant 过滤
193
+ * 3. 按 groupType(可为空)过滤
194
+ * 4. 与 list2 求交集
195
+ */
196
+ export function filterSmartLines(list1, list2, groupType, tenant) {
197
+ if (!Array.isArray(list1) || !Array.isArray(list2)) {
198
+ throw new Error("前两个参数必须是数组");
199
+ }
200
+ const currentBrowser = detectBrowser();
201
+ // 1. 浏览器过滤
202
+ let result = list1.filter((item) => {
203
+ const checks = item.browserCheck || [];
204
+ const blocked = checks.some((checkItem) => matchBrowser(checkItem, currentBrowser));
205
+ return !blocked;
206
+ });
207
+ // 2. tenant 过滤(如果有传值)
208
+ if (tenant) {
209
+ result = result.filter((item) => item.tenant === tenant);
210
+ }
211
+ // 3. 按 groupType 过滤
212
+ if (groupType) {
213
+ result = result.filter((item) => item.lineGroup === groupType);
214
+ }
215
+ // 4. 提取 lines
216
+ const lines = result.map((item) => toStandardUrl(item.line));
217
+ // 5. 与 list2 求交集
218
+ const set = new Set(lines);
219
+ return list2.map(toStandardUrl).filter((v) => set.has(toStandardUrl(v)));
220
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hifun-tools",
3
- "version": "1.3.16",
3
+ "version": "1.3.18",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",