befly 3.69.0 → 3.71.0
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.
- package/apis/admin/insert.js +2 -4
- package/apis/admin/update.js +4 -1
- package/apis/auth/login.js +2 -1
- package/apis/tongJi/todayOnline.js +47 -0
- package/lib/dbHelper.js +0 -4
- package/package.json +1 -1
- package/plugins/tool.js +1 -41
- package/sync/dev.js +2 -1
- package/utils/crypto.js +62 -0
- package/utils/userAgent.js +0 -297
package/apis/admin/insert.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import adminTable from "#befly/tables/admin.json";
|
|
2
|
-
import {
|
|
2
|
+
import { hashPassword } from "#befly/utils/crypto.js";
|
|
3
3
|
|
|
4
4
|
export default {
|
|
5
5
|
name: "添加管理员",
|
|
@@ -43,9 +43,7 @@ export default {
|
|
|
43
43
|
return befly.tool.No("角色不存在");
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
const
|
|
47
|
-
const isSha256Hex = /^[a-f0-9]{64}$/.test(passwordSource);
|
|
48
|
-
const hashedPassword = isSha256Hex ? befly.tool.sha256(passwordSource) : befly.tool.sha256(befly.tool.sha256(ctx.body.password));
|
|
46
|
+
const hashedPassword = await hashPassword(ctx.body.password);
|
|
49
47
|
|
|
50
48
|
const adminId = await befly.mysql.insData({
|
|
51
49
|
table: "beflyAdmin",
|
package/apis/admin/update.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import adminTable from "#befly/tables/admin.json";
|
|
2
|
+
import { hashPassword } from "#befly/utils/crypto.js";
|
|
2
3
|
|
|
3
4
|
export default {
|
|
4
5
|
name: "更新管理员",
|
|
@@ -74,7 +75,9 @@ export default {
|
|
|
74
75
|
if (ctx.body.email !== undefined) updateData.email = ctx.body.email;
|
|
75
76
|
if (ctx.body.lastLoginIp !== undefined) updateData.lastLoginIp = ctx.body.lastLoginIp;
|
|
76
77
|
if (ctx.body.lastLoginTime !== undefined) updateData.lastLoginTime = ctx.body.lastLoginTime;
|
|
77
|
-
if (ctx.body.password !== undefined)
|
|
78
|
+
if (ctx.body.password !== undefined) {
|
|
79
|
+
updateData.password = await hashPassword(ctx.body.password);
|
|
80
|
+
}
|
|
78
81
|
if (ctx.body.phone !== undefined) updateData.phone = ctx.body.phone;
|
|
79
82
|
if (ctx.body.roleType !== undefined) updateData.roleType = ctx.body.roleType;
|
|
80
83
|
if (ctx.body.username !== undefined) updateData.username = ctx.body.username;
|
package/apis/auth/login.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import adminTable from "#befly/tables/admin.json";
|
|
2
2
|
import loginLogTable from "#befly/tables/loginLog.json";
|
|
3
|
+
import { verifyPassword } from "#befly/utils/crypto.js";
|
|
3
4
|
import { toSessionTtlSeconds } from "#befly/utils/util.js";
|
|
4
5
|
|
|
5
6
|
export default {
|
|
@@ -79,7 +80,7 @@ export default {
|
|
|
79
80
|
logData.username = admin.data.username;
|
|
80
81
|
logData.nickname = admin.data.nickname;
|
|
81
82
|
|
|
82
|
-
if (
|
|
83
|
+
if (!(await verifyPassword(ctx.body.password, admin.data.password))) {
|
|
83
84
|
logData.failReason = "密码错误";
|
|
84
85
|
await befly.mysql.insData({ table: "beflyLoginLog", data: logData });
|
|
85
86
|
return befly.tool.No("账号或密码错误");
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getTongJiDateYmdNumber } from "./_tongJi.js";
|
|
2
|
+
|
|
3
|
+
async function getTodayCount(befly, reportDate, productCode) {
|
|
4
|
+
if (!befly.redis) {
|
|
5
|
+
return 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return Number(await befly.redis.scard(`daily:day:${reportDate}:members:${productCode}`)) || 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function loadProjects(befly) {
|
|
12
|
+
if (!befly.mysql) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const result = await befly.mysql.getAll({
|
|
17
|
+
table: "beflyProject",
|
|
18
|
+
fields: ["code"],
|
|
19
|
+
where: { state: 1 }
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return result.data?.lists || [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default {
|
|
26
|
+
name: "获取今日在线总数",
|
|
27
|
+
method: "POST",
|
|
28
|
+
body: "none",
|
|
29
|
+
auth: true,
|
|
30
|
+
fields: {},
|
|
31
|
+
required: [],
|
|
32
|
+
handler: async (befly, _ctx) => {
|
|
33
|
+
const reportDate = getTongJiDateYmdNumber(Date.now(), befly.config?.tz);
|
|
34
|
+
const projects = await loadProjects(befly);
|
|
35
|
+
let total = 0;
|
|
36
|
+
|
|
37
|
+
for (const item of projects) {
|
|
38
|
+
total += await getTodayCount(befly, reportDate, item.code);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return befly.tool.Yes("获取成功", {
|
|
42
|
+
today: {
|
|
43
|
+
count: total
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
};
|
package/lib/dbHelper.js
CHANGED
|
@@ -79,10 +79,6 @@ function safeStringify(value) {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
function getExecuteErrorMessage(error) {
|
|
82
|
-
if (error instanceof Error && typeof error.message === "string" && error.message.trim().length > 0) {
|
|
83
|
-
return error.message.trim();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
82
|
if (!error || typeof error !== "object") {
|
|
87
83
|
return String(error);
|
|
88
84
|
}
|
package/package.json
CHANGED
package/plugins/tool.js
CHANGED
|
@@ -103,53 +103,13 @@ export function Raw(ctx, data, options = {}) {
|
|
|
103
103
|
});
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
/**
|
|
107
|
-
* 计算 md5(hex)
|
|
108
|
-
* @param value 输入字符串
|
|
109
|
-
* @returns md5 hex
|
|
110
|
-
*/
|
|
111
|
-
export function md5(value) {
|
|
112
|
-
if (!isString(value) || value.length < 1) {
|
|
113
|
-
throw new Error("md5 输入必须是非空字符串", {
|
|
114
|
-
cause: null,
|
|
115
|
-
code: "validation",
|
|
116
|
-
subsystem: "tool",
|
|
117
|
-
operation: "md5"
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
const hasher = new Bun.CryptoHasher("md5");
|
|
121
|
-
hasher.update(value);
|
|
122
|
-
return hasher.digest("hex");
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* 计算 sha256(hex)
|
|
127
|
-
* @param value 输入字符串
|
|
128
|
-
* @returns sha256 hex
|
|
129
|
-
*/
|
|
130
|
-
export function sha256(value) {
|
|
131
|
-
if (!isString(value) || value.length < 1) {
|
|
132
|
-
throw new Error("sha256 输入必须是非空字符串", {
|
|
133
|
-
cause: null,
|
|
134
|
-
code: "validation",
|
|
135
|
-
subsystem: "tool",
|
|
136
|
-
operation: "sha256"
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
const hasher = new Bun.CryptoHasher("sha256");
|
|
140
|
-
hasher.update(value);
|
|
141
|
-
return hasher.digest("hex");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
106
|
export default {
|
|
145
107
|
order: 3,
|
|
146
108
|
handler: () => {
|
|
147
109
|
return {
|
|
148
110
|
Yes: Yes,
|
|
149
111
|
No: No,
|
|
150
|
-
Raw: Raw
|
|
151
|
-
md5: md5,
|
|
152
|
-
sha256: sha256
|
|
112
|
+
Raw: Raw
|
|
153
113
|
};
|
|
154
114
|
}
|
|
155
115
|
};
|
package/sync/dev.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hashPassword } from "../utils/crypto.js";
|
|
1
2
|
import { isNumber } from "../utils/is.js";
|
|
2
3
|
|
|
3
4
|
const MENU_TABLE_NAME = "beflyMenu";
|
|
@@ -72,7 +73,7 @@ export async function syncDev(ctx) {
|
|
|
72
73
|
});
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
const password =
|
|
76
|
+
const password = await hashPassword(ctx.config.devPassword);
|
|
76
77
|
|
|
77
78
|
const devAdminData = {
|
|
78
79
|
nickname: "开发者",
|
package/utils/crypto.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 通用加密/哈希工具
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { isString } from "./is.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 计算 md5(hex)
|
|
9
|
+
* @param value 输入字符串
|
|
10
|
+
* @returns md5 hex
|
|
11
|
+
*/
|
|
12
|
+
export function md5(value) {
|
|
13
|
+
if (!isString(value) || value.length < 1) {
|
|
14
|
+
throw new Error("md5 输入必须是非空字符串", {
|
|
15
|
+
cause: null,
|
|
16
|
+
code: "validation",
|
|
17
|
+
subsystem: "crypto",
|
|
18
|
+
operation: "md5"
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
const hasher = new Bun.CryptoHasher("md5");
|
|
22
|
+
hasher.update(value);
|
|
23
|
+
return hasher.digest("hex");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 计算 sha256(hex)
|
|
28
|
+
* @param value 输入字符串
|
|
29
|
+
* @returns sha256 hex
|
|
30
|
+
*/
|
|
31
|
+
export function sha256(value) {
|
|
32
|
+
if (!isString(value) || value.length < 1) {
|
|
33
|
+
throw new Error("sha256 输入必须是非空字符串", {
|
|
34
|
+
cause: null,
|
|
35
|
+
code: "validation",
|
|
36
|
+
subsystem: "crypto",
|
|
37
|
+
operation: "sha256"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
41
|
+
hasher.update(value);
|
|
42
|
+
return hasher.digest("hex");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 使用 bcrypt 对密码进行哈希
|
|
47
|
+
* @param password 原始密码
|
|
48
|
+
* @returns bcrypt hash 字符串
|
|
49
|
+
*/
|
|
50
|
+
export async function hashPassword(password) {
|
|
51
|
+
return Bun.password.hash(password, { algorithm: "bcrypt" });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 校验密码与 bcrypt hash 是否匹配
|
|
56
|
+
* @param password 原始密码
|
|
57
|
+
* @param hashedPassword 数据库中存储的 bcrypt hash
|
|
58
|
+
* @returns 是否匹配
|
|
59
|
+
*/
|
|
60
|
+
export async function verifyPassword(password, hashedPassword) {
|
|
61
|
+
return Bun.password.verify(password, hashedPassword);
|
|
62
|
+
}
|
package/utils/userAgent.js
DELETED
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
const WINDOWS_VERSION_MAP = {
|
|
2
|
-
"10.0": "10",
|
|
3
|
-
6.3: "8.1",
|
|
4
|
-
6.2: "8",
|
|
5
|
-
6.1: "7"
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
function firstMatch(text, regexp) {
|
|
9
|
-
const matched = regexp.exec(text);
|
|
10
|
-
|
|
11
|
-
if (!matched) {
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
for (let index = 1; index < matched.length; index++) {
|
|
16
|
-
if (matched[index]) {
|
|
17
|
-
return matched[index];
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function firstNonEmptyMatch(text, regexp) {
|
|
25
|
-
const matched = regexp.exec(text);
|
|
26
|
-
|
|
27
|
-
if (!matched) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
for (let index = 1; index < matched.length; index++) {
|
|
32
|
-
if (matched[index]) {
|
|
33
|
-
return matched[index];
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function hasMatch(text, regexp) {
|
|
41
|
-
return regexp.test(text) ? "" : null;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function normalizeVersion(version) {
|
|
45
|
-
return String(version || "").replace(/_/g, ".");
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function majorVersion(version) {
|
|
49
|
-
const parts = String(version || "").split(".");
|
|
50
|
-
return parts[0] || "";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function matchBrowser(text) {
|
|
54
|
-
const rules = [
|
|
55
|
-
{ name: "wxwork", regexp: /wxwork\/([\d.]+)/ },
|
|
56
|
-
{ name: "WeChat", regexp: /MicroMessenger\/([\d.]+)/ },
|
|
57
|
-
{ name: "DingTalk", regexp: /DingTalk\/([\d.]+)/ },
|
|
58
|
-
{ name: "Alipay", regexp: /AlipayClient\/([\d.]+)/ },
|
|
59
|
-
{ name: "Taobao", regexp: /(?:TaoBao|AliApp\(TB|WindVane)/, match: hasMatch },
|
|
60
|
-
{ name: "Jingdong", regexp: /jdapp/i, match: hasMatch },
|
|
61
|
-
{ name: "Weibo", regexp: /Weibo|__weibo__/, match: hasMatch },
|
|
62
|
-
{ name: "Douyin", regexp: /(?:aweme|Trill|Douyin)/i, match: hasMatch },
|
|
63
|
-
{ name: "Lark", regexp: /(?:Lark|Feishu)\S*?\/([\d.]+)/ },
|
|
64
|
-
{ name: "QQ Browser", regexp: /(?:MQQBrowser|QQBrowser)\/([\d.]+)/ },
|
|
65
|
-
{ name: "UC Browser", regexp: /UCBrowser\/([\d.]+)/ },
|
|
66
|
-
{ name: "Quark", regexp: /Quark\/([\d.]+)/ },
|
|
67
|
-
{ name: "Baidu", regexp: /(?:baiduboxapp|baiduapp|BaiduBrowser|FlyFlow)\S*?\/([\d.]+)/i },
|
|
68
|
-
{ name: "Sogou", regexp: /(?:SogouMobileBrowser|SogouMSE|MetaSr)\S*?\/([\d.]+)/i },
|
|
69
|
-
{ name: "360", regexp: /(?:360SE|360EE|QHBrowser|QihooBrowser)\S*?(?:\s|v|\/)([\d.]+)/i },
|
|
70
|
-
{ name: "Huawei Browser", regexp: /HuaweiBrowser\/([\d.]+)/ },
|
|
71
|
-
{ name: "MiuiBrowser", regexp: /MiuiBrowser\/([\d.]+)/ },
|
|
72
|
-
{ name: "HeyTapBrowser", regexp: /HeyTapBrowser\/([\d.]+)/ },
|
|
73
|
-
{ name: "VivoBrowser", regexp: /VivoBrowser\/([\d.]+)/ },
|
|
74
|
-
{ name: "Samsung Browser", regexp: /SamsungBrowser\/([\d.]+)/ },
|
|
75
|
-
{ name: "Maxthon", regexp: /Maxthon\/([\d.]+)/ },
|
|
76
|
-
{ name: "Edge", regexp: /Edg(?:e|A|iOS)?\/([\d.]+)/ },
|
|
77
|
-
{ name: "Opera", regexp: /(?:OPR|Opera)\/([\d.]+)/ },
|
|
78
|
-
{ name: "Firefox", regexp: /(?:FxiOS|Firefox)\/([\d.]+)/ },
|
|
79
|
-
{ name: "Chrome", regexp: /(?:CriOS|Chrome)\/([\d.]+)/ },
|
|
80
|
-
{ name: "Safari", regexp: /Version\/([\d.]+).*Safari/ },
|
|
81
|
-
{ name: "IE", regexp: /(?:MSIE\s([\d.]+)|Trident\/.*rv:([\d.]+))/, match: firstNonEmptyMatch }
|
|
82
|
-
];
|
|
83
|
-
|
|
84
|
-
for (const rule of rules) {
|
|
85
|
-
const matcher = rule.match || firstMatch;
|
|
86
|
-
const version = matcher(text, rule.regexp);
|
|
87
|
-
|
|
88
|
-
if (version !== null) {
|
|
89
|
-
const name = rule.name === "Safari" && /iPhone|iPad|iPod/i.test(text) ? "Mobile Safari" : rule.name;
|
|
90
|
-
|
|
91
|
-
return { name: name, version: version };
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return { name: "", version: "" };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function matchEngine(text) {
|
|
99
|
-
if (/AppleWebKit/i.test(text) && /iPhone|iPad|iPod/i.test(text)) {
|
|
100
|
-
return { name: "WebKit", version: firstMatch(text, /AppleWebKit\/([\d.]+)/) };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const rules = [
|
|
104
|
-
{ name: "Blink", regexp: /Chrome\/([\d.]+)/ },
|
|
105
|
-
{ name: "Blink", regexp: /CriOS\/([\d.]+)/ },
|
|
106
|
-
{ name: "Blink", regexp: /Edg(?:e|A|iOS)?\/([\d.]+)/ },
|
|
107
|
-
{ name: "Blink", regexp: /OPR\/([\d.]+)/ },
|
|
108
|
-
{ name: "Gecko", regexp: /Firefox\/([\d.]+)/ },
|
|
109
|
-
{ name: "Gecko", regexp: /FxiOS\/([\d.]+)/ },
|
|
110
|
-
{ name: "WebKit", regexp: /AppleWebKit\/([\d.]+)/ },
|
|
111
|
-
{ name: "Trident", regexp: /Trident\/([\d.]+)/ },
|
|
112
|
-
{ name: "Trident", regexp: /MSIE\s([\d.]+)/ }
|
|
113
|
-
];
|
|
114
|
-
|
|
115
|
-
for (const rule of rules) {
|
|
116
|
-
const version = firstMatch(text, rule.regexp);
|
|
117
|
-
|
|
118
|
-
if (version) {
|
|
119
|
-
return { name: rule.name, version: version };
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return { name: "", version: "" };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function matchOs(text) {
|
|
127
|
-
const androidVersion = firstMatch(text, /Android\s([\d.]+)/);
|
|
128
|
-
|
|
129
|
-
if (androidVersion) {
|
|
130
|
-
return { name: "Android", version: androidVersion };
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const iosVersion = firstMatch(text, /(?:iPhone|iPad|iPod).*OS\s([\d_]+)/);
|
|
134
|
-
|
|
135
|
-
if (iosVersion) {
|
|
136
|
-
return { name: "iOS", version: normalizeVersion(iosVersion) };
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const windowsVersion = firstMatch(text, /Windows NT\s([\d.]+)/);
|
|
140
|
-
|
|
141
|
-
if (windowsVersion) {
|
|
142
|
-
return { name: "Windows", version: WINDOWS_VERSION_MAP[windowsVersion] || windowsVersion };
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const macVersion = firstMatch(text, /Mac OS X\s([\d_]+)/);
|
|
146
|
-
|
|
147
|
-
if (macVersion) {
|
|
148
|
-
return { name: "macOS", version: normalizeVersion(macVersion) };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (/HarmonyOS/i.test(text)) {
|
|
152
|
-
return { name: "HarmonyOS", version: "" };
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (/Linux/i.test(text)) {
|
|
156
|
-
return { name: "Linux", version: "" };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (/CrOS/i.test(text)) {
|
|
160
|
-
return { name: "Chrome OS", version: "" };
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return { name: "", version: "" };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function getDeviceType(text) {
|
|
167
|
-
if (/iPad|Tablet|PlayBook|Silk/i.test(text) || (/Android/i.test(text) && !/Mobile/i.test(text))) {
|
|
168
|
-
return "tablet";
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (/PlayStation|Xbox|Nintendo|CrKey|Roku|AppleTV|SmartTV|TV/i.test(text)) {
|
|
172
|
-
return "console";
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (/bot|crawler|spider|googlebot|bingbot|baiduspider|slurp|yandex|duckduckgo/i.test(text)) {
|
|
176
|
-
return "bot";
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (/Mobi|iPhone|iPod|Phone\s*\d|Android.*Mobile|Windows Phone/i.test(text)) {
|
|
180
|
-
return "mobile";
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (/HarmonyOS/i.test(text) && !/Pad|Tablet/i.test(text)) {
|
|
184
|
-
return "mobile";
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (/HarmonyOS/i.test(text)) {
|
|
188
|
-
return "tablet";
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return "desktop";
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function matchAndroidModel(text) {
|
|
195
|
-
const matched = /Android[\d.\s]*;\s*([^;)]+?)(?:\s+Build\/|;|\))/i.exec(text);
|
|
196
|
-
return matched ? matched[1].trim() : "";
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function matchVendor(text) {
|
|
200
|
-
if (/(iPhone|iPad|iPod|Macintosh)\b/i.test(text)) {
|
|
201
|
-
return "Apple";
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (/Samsung|SM-\w+/i.test(text)) {
|
|
205
|
-
return "Samsung";
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (/Huawei|HUAWEI|Honor|HONOR/i.test(text)) {
|
|
209
|
-
return "Huawei";
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (/Xiaomi|Redmi|Mi\s|MI\s|M200|M201|M202|M210|M211|M220|M221|M230|M231|M240/i.test(text)) {
|
|
213
|
-
return "Xiaomi";
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (/OPPO|PB[A-Z]|PCHM\d+|PCAM\d+/i.test(text)) {
|
|
217
|
-
return "OPPO";
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (/vivo|V\d{4}[A-Z]/i.test(text)) {
|
|
221
|
-
return "vivo";
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return "";
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function matchDevice(text) {
|
|
228
|
-
const type = getDeviceType(text);
|
|
229
|
-
|
|
230
|
-
if (/iPad/i.test(text)) {
|
|
231
|
-
return { vendor: "Apple", model: "iPad", type: type };
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (/iPod/i.test(text)) {
|
|
235
|
-
return { vendor: "Apple", model: "iPod", type: type };
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (/iPhone/i.test(text)) {
|
|
239
|
-
return { vendor: "Apple", model: "iPhone", type: type };
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return { vendor: matchVendor(text), model: matchAndroidModel(text), type: type };
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function matchCpuArchitecture(text) {
|
|
246
|
-
if (/x86_64|Win64|x64|amd64/i.test(text)) {
|
|
247
|
-
return "amd64";
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (/arm64|aarch64/i.test(text)) {
|
|
251
|
-
return "arm64";
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (/arm/i.test(text)) {
|
|
255
|
-
return "arm";
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return "";
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
export function parseUserAgent(userAgent) {
|
|
262
|
-
const text = String(userAgent || "");
|
|
263
|
-
const browser = matchBrowser(text);
|
|
264
|
-
const engine = matchEngine(text);
|
|
265
|
-
const os = matchOs(text);
|
|
266
|
-
const device = matchDevice(text);
|
|
267
|
-
const cpu = { architecture: matchCpuArchitecture(text) };
|
|
268
|
-
|
|
269
|
-
return {
|
|
270
|
-
userAgent: text,
|
|
271
|
-
browserName: browser.name,
|
|
272
|
-
browserVersion: browser.version,
|
|
273
|
-
osName: os.name,
|
|
274
|
-
osVersion: os.version,
|
|
275
|
-
deviceType: device.type,
|
|
276
|
-
deviceVendor: device.vendor,
|
|
277
|
-
deviceModel: device.model,
|
|
278
|
-
engineName: engine.name,
|
|
279
|
-
engineVersion: engine.version,
|
|
280
|
-
cpuArchitecture: cpu.architecture,
|
|
281
|
-
browser: { name: browser.name, version: browser.version, major: majorVersion(browser.version) },
|
|
282
|
-
engine: engine,
|
|
283
|
-
os: os,
|
|
284
|
-
device: device,
|
|
285
|
-
cpu: cpu
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
export function getUserAgentFields(userAgent) {
|
|
290
|
-
const text = typeof userAgent === "string" ? userAgent.trim() : "";
|
|
291
|
-
|
|
292
|
-
if (text.length === 0) {
|
|
293
|
-
return null;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return parseUserAgent(text);
|
|
297
|
-
}
|