@alibarbar/common 1.0.9 → 1.1.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/dist/{chunk-O3O67R4I.js → algorithm.cjs} +1 -3
- package/dist/algorithm.js +130 -44
- package/dist/{chunk-LOAZSPGQ.js → array.cjs} +1 -3
- package/dist/array.js +135 -84
- package/dist/{chunk-Y364QIQH.js → color.cjs} +1 -3
- package/dist/color.js +127 -40
- package/dist/{chunk-DYBSRI7V.js → crypto.cjs} +109 -3
- package/dist/crypto.d.mts +48 -1
- package/dist/crypto.d.ts +48 -1
- package/dist/crypto.js +274 -60
- package/dist/{chunk-BHCRFURU.js → data-structure.cjs} +1 -3
- package/dist/data-structure.js +481 -32
- package/dist/{chunk-I3L42475.js → date.cjs} +1 -3
- package/dist/date.js +125 -72
- package/dist/{chunk-JK2SE3I2.js → dom.cjs} +1 -3
- package/dist/dom.js +84 -56
- package/dist/{chunk-7V5UQXIO.js → file.cjs} +1 -3
- package/dist/file.js +79 -32
- package/dist/{chunk-OX5PLOWB.js → i18n.cjs} +1 -3
- package/dist/i18n.js +78 -40
- package/dist/index.cjs +4543 -0
- package/dist/index.d.mts +6 -162
- package/dist/index.d.ts +6 -162
- package/dist/index.js +3712 -17374
- package/dist/{chunk-4RGXV4SJ.js → network.cjs} +1 -3
- package/dist/network.js +97 -28
- package/dist/{chunk-3FRYJPHG.js → number.cjs} +1 -3
- package/dist/number.js +77 -56
- package/dist/{chunk-27UDDVLZ.js → object.cjs} +1 -3
- package/dist/object.js +237 -80
- package/dist/{chunk-JQZBPAPO.js → performance.cjs} +1 -3
- package/dist/performance.js +145 -40
- package/dist/services.cjs +291 -0
- package/dist/services.d.mts +161 -0
- package/dist/services.d.ts +161 -0
- package/dist/services.js +281 -0
- package/dist/storage.cjs +943 -0
- package/dist/storage.d.mts +46 -1
- package/dist/storage.d.ts +46 -1
- package/dist/storage.js +930 -32
- package/dist/{chunk-D7CS5EKF.js → string.cjs} +1 -3
- package/dist/string.js +91 -68
- package/dist/{chunk-56W6YECK.js → tracking.cjs} +1 -3
- package/dist/tracking.js +359 -52
- package/dist/{chunk-ZDMFMUDR.js → transform.cjs} +1 -3
- package/dist/transform.js +299 -32
- package/dist/{chunk-NJARVI6X.mjs → upload.cjs} +42 -15
- package/dist/upload.d.mts +1 -1
- package/dist/upload.d.ts +1 -1
- package/dist/upload.js +402 -20
- package/dist/{chunk-KGFTD255.js → url.cjs} +1 -3
- package/dist/url.js +91 -44
- package/dist/{chunk-TQN37HIN.js → validation.cjs} +1 -3
- package/dist/validation.js +77 -60
- package/package.json +7 -2
- package/dist/algorithm.js.map +0 -1
- package/dist/algorithm.mjs +0 -4
- package/dist/algorithm.mjs.map +0 -1
- package/dist/array.js.map +0 -1
- package/dist/array.mjs +0 -4
- package/dist/array.mjs.map +0 -1
- package/dist/chunk-27UDDVLZ.js.map +0 -1
- package/dist/chunk-2R2QWFJC.mjs +0 -138
- package/dist/chunk-2R2QWFJC.mjs.map +0 -1
- package/dist/chunk-3FRYJPHG.js.map +0 -1
- package/dist/chunk-4RGXV4SJ.js.map +0 -1
- package/dist/chunk-56W6YECK.js.map +0 -1
- package/dist/chunk-5BGSUGTI.mjs +0 -128
- package/dist/chunk-5BGSUGTI.mjs.map +0 -1
- package/dist/chunk-7E6GELHJ.mjs +0 -302
- package/dist/chunk-7E6GELHJ.mjs.map +0 -1
- package/dist/chunk-7V5UQXIO.js.map +0 -1
- package/dist/chunk-A4SWQXX7.mjs +0 -484
- package/dist/chunk-A4SWQXX7.mjs.map +0 -1
- package/dist/chunk-BHCRFURU.js.map +0 -1
- package/dist/chunk-CDSGEAOK.mjs +0 -80
- package/dist/chunk-CDSGEAOK.mjs.map +0 -1
- package/dist/chunk-D7CS5EKF.js.map +0 -1
- package/dist/chunk-DYBSRI7V.js.map +0 -1
- package/dist/chunk-FEBKPX5A.js +0 -386
- package/dist/chunk-FEBKPX5A.js.map +0 -1
- package/dist/chunk-FJ6ZGZIA.mjs +0 -39
- package/dist/chunk-FJ6ZGZIA.mjs.map +0 -1
- package/dist/chunk-HLDFI7R2.mjs +0 -175
- package/dist/chunk-HLDFI7R2.mjs.map +0 -1
- package/dist/chunk-I3L42475.js.map +0 -1
- package/dist/chunk-JBLX27WD.mjs +0 -240
- package/dist/chunk-JBLX27WD.mjs.map +0 -1
- package/dist/chunk-JHZ7M2MR.mjs +0 -133
- package/dist/chunk-JHZ7M2MR.mjs.map +0 -1
- package/dist/chunk-JK2SE3I2.js.map +0 -1
- package/dist/chunk-JQZBPAPO.js.map +0 -1
- package/dist/chunk-JXYGC2C5.mjs +0 -100
- package/dist/chunk-JXYGC2C5.mjs.map +0 -1
- package/dist/chunk-KGFTD255.js.map +0 -1
- package/dist/chunk-LBHBNPNJ.mjs +0 -148
- package/dist/chunk-LBHBNPNJ.mjs.map +0 -1
- package/dist/chunk-LF4CILQS.mjs +0 -87
- package/dist/chunk-LF4CILQS.mjs.map +0 -1
- package/dist/chunk-LOAZSPGQ.js.map +0 -1
- package/dist/chunk-NJARVI6X.mjs.map +0 -1
- package/dist/chunk-NSSDYX2U.mjs +0 -80
- package/dist/chunk-NSSDYX2U.mjs.map +0 -1
- package/dist/chunk-O3O67R4I.js.map +0 -1
- package/dist/chunk-OIXQ3E6W.mjs +0 -354
- package/dist/chunk-OIXQ3E6W.mjs.map +0 -1
- package/dist/chunk-OX5PLOWB.js.map +0 -1
- package/dist/chunk-PJ7UCTX4.mjs +0 -362
- package/dist/chunk-PJ7UCTX4.mjs.map +0 -1
- package/dist/chunk-PR4QN5HX.js +0 -44
- package/dist/chunk-PR4QN5HX.js.map +0 -1
- package/dist/chunk-QIBE7GVN.mjs +0 -81
- package/dist/chunk-QIBE7GVN.mjs.map +0 -1
- package/dist/chunk-QIOC54LQ.mjs +0 -130
- package/dist/chunk-QIOC54LQ.mjs.map +0 -1
- package/dist/chunk-TQN37HIN.js.map +0 -1
- package/dist/chunk-WZDOPUJW.js +0 -361
- package/dist/chunk-WZDOPUJW.js.map +0 -1
- package/dist/chunk-XJTZDXSR.mjs +0 -94
- package/dist/chunk-XJTZDXSR.mjs.map +0 -1
- package/dist/chunk-Y364QIQH.js.map +0 -1
- package/dist/chunk-YXM6Q4JS.mjs +0 -94
- package/dist/chunk-YXM6Q4JS.mjs.map +0 -1
- package/dist/chunk-ZDMFMUDR.js.map +0 -1
- package/dist/chunk-ZVJ6NQUM.mjs +0 -82
- package/dist/chunk-ZVJ6NQUM.mjs.map +0 -1
- package/dist/color.js.map +0 -1
- package/dist/color.mjs +0 -4
- package/dist/color.mjs.map +0 -1
- package/dist/crypto.js.map +0 -1
- package/dist/crypto.mjs +0 -4
- package/dist/crypto.mjs.map +0 -1
- package/dist/data-structure.js.map +0 -1
- package/dist/data-structure.mjs +0 -4
- package/dist/data-structure.mjs.map +0 -1
- package/dist/date.js.map +0 -1
- package/dist/date.mjs +0 -4
- package/dist/date.mjs.map +0 -1
- package/dist/dom.js.map +0 -1
- package/dist/dom.mjs +0 -4
- package/dist/dom.mjs.map +0 -1
- package/dist/file.js.map +0 -1
- package/dist/file.mjs +0 -4
- package/dist/file.mjs.map +0 -1
- package/dist/i18n.js.map +0 -1
- package/dist/i18n.mjs +0 -4
- package/dist/i18n.mjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -17155
- package/dist/index.mjs.map +0 -1
- package/dist/network.js.map +0 -1
- package/dist/network.mjs +0 -4
- package/dist/network.mjs.map +0 -1
- package/dist/number.js.map +0 -1
- package/dist/number.mjs +0 -4
- package/dist/number.mjs.map +0 -1
- package/dist/object.js.map +0 -1
- package/dist/object.mjs +0 -4
- package/dist/object.mjs.map +0 -1
- package/dist/performance.js.map +0 -1
- package/dist/performance.mjs +0 -4
- package/dist/performance.mjs.map +0 -1
- package/dist/storage.js.map +0 -1
- package/dist/storage.mjs +0 -5
- package/dist/storage.mjs.map +0 -1
- package/dist/string.js.map +0 -1
- package/dist/string.mjs +0 -4
- package/dist/string.mjs.map +0 -1
- package/dist/tracking.js.map +0 -1
- package/dist/tracking.mjs +0 -4
- package/dist/tracking.mjs.map +0 -1
- package/dist/transform.js.map +0 -1
- package/dist/transform.mjs +0 -4
- package/dist/transform.mjs.map +0 -1
- package/dist/upload.js.map +0 -1
- package/dist/upload.mjs +0 -5
- package/dist/upload.mjs.map +0 -1
- package/dist/url.js.map +0 -1
- package/dist/url.mjs +0 -4
- package/dist/url.mjs.map +0 -1
- package/dist/validation.js.map +0 -1
- package/dist/validation.mjs +0 -4
- package/dist/validation.mjs.map +0 -1
- /package/dist/{upload-DchqyDBQ.d.mts → index-DchqyDBQ.d.mts} +0 -0
- /package/dist/{upload-DchqyDBQ.d.ts → index-DchqyDBQ.d.ts} +0 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,4543 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var Qs = require('qs');
|
|
4
|
+
var axios = require('axios');
|
|
5
|
+
|
|
6
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var Qs__default = /*#__PURE__*/_interopDefault(Qs);
|
|
9
|
+
var axios__default = /*#__PURE__*/_interopDefault(axios);
|
|
10
|
+
|
|
11
|
+
// src/core/string/index.ts
|
|
12
|
+
function capitalize(str) {
|
|
13
|
+
if (!str) return str;
|
|
14
|
+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
15
|
+
}
|
|
16
|
+
function camelCase(str) {
|
|
17
|
+
if (!str) return str;
|
|
18
|
+
const words = str.replace(/[A-Z]/g, (letter) => ` ${letter.toLowerCase()}`).split(/[\s\-_]+/).filter((word) => word.length > 0);
|
|
19
|
+
if (words.length === 0) return "";
|
|
20
|
+
return words[0].toLowerCase() + words.slice(1).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
21
|
+
}
|
|
22
|
+
function kebabCase(str) {
|
|
23
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
24
|
+
}
|
|
25
|
+
function truncate(str, length, suffix = "...") {
|
|
26
|
+
if (str.length <= length) return str;
|
|
27
|
+
return str.slice(0, length - suffix.length) + suffix;
|
|
28
|
+
}
|
|
29
|
+
function snakeCase(str) {
|
|
30
|
+
if (!str) return str;
|
|
31
|
+
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`).replace(/[\s-]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
32
|
+
}
|
|
33
|
+
function pascalCase(str) {
|
|
34
|
+
if (!str) return str;
|
|
35
|
+
const camel = camelCase(str);
|
|
36
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
37
|
+
}
|
|
38
|
+
function mask(str, options = {}) {
|
|
39
|
+
if (!str) return str;
|
|
40
|
+
const { start = 0, end = str.length, maskChar = "*" } = options;
|
|
41
|
+
const startIndex = Math.max(0, start);
|
|
42
|
+
const endIndex = Math.min(str.length, end);
|
|
43
|
+
if (startIndex >= endIndex) return str;
|
|
44
|
+
return str.slice(0, startIndex) + maskChar.repeat(endIndex - startIndex) + str.slice(endIndex);
|
|
45
|
+
}
|
|
46
|
+
function maskPhone(phone) {
|
|
47
|
+
if (!phone || phone.length < 7) return phone;
|
|
48
|
+
return mask(phone, { start: 3, end: phone.length - 4 });
|
|
49
|
+
}
|
|
50
|
+
function maskEmail(email) {
|
|
51
|
+
if (!email || !email.includes("@")) return email;
|
|
52
|
+
const [local, domain] = email.split("@");
|
|
53
|
+
if (local.length <= 2) return email;
|
|
54
|
+
return `${local.slice(0, 2)}***@${domain}`;
|
|
55
|
+
}
|
|
56
|
+
function slugify(str) {
|
|
57
|
+
if (!str) return "";
|
|
58
|
+
return str.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
59
|
+
}
|
|
60
|
+
function removeAccents(str) {
|
|
61
|
+
if (!str) return str;
|
|
62
|
+
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
63
|
+
}
|
|
64
|
+
function escapeHtml(str) {
|
|
65
|
+
if (!str) return str;
|
|
66
|
+
const map = {
|
|
67
|
+
"&": "&",
|
|
68
|
+
"<": "<",
|
|
69
|
+
">": ">",
|
|
70
|
+
'"': """,
|
|
71
|
+
"'": "'"
|
|
72
|
+
};
|
|
73
|
+
return str.replace(/[&<>"']/g, (char) => map[char]);
|
|
74
|
+
}
|
|
75
|
+
function unescapeHtml(str) {
|
|
76
|
+
if (!str) return str;
|
|
77
|
+
const map = {
|
|
78
|
+
"&": "&",
|
|
79
|
+
"<": "<",
|
|
80
|
+
">": ">",
|
|
81
|
+
""": '"',
|
|
82
|
+
"'": "'"
|
|
83
|
+
};
|
|
84
|
+
return str.replace(/&|<|>|"|'/g, (char) => map[char]);
|
|
85
|
+
}
|
|
86
|
+
function template(template2, data) {
|
|
87
|
+
if (!template2) return template2;
|
|
88
|
+
return template2.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
89
|
+
const value = data[key];
|
|
90
|
+
return value !== void 0 && value !== null ? String(value) : match;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function highlight(text, keyword, tag = "mark") {
|
|
94
|
+
if (!text || !keyword) return text;
|
|
95
|
+
const regex = new RegExp(`(${escapeRegex(keyword)})`, "gi");
|
|
96
|
+
return text.replace(regex, `<${tag}>$1</${tag}>`);
|
|
97
|
+
}
|
|
98
|
+
function escapeRegex(str) {
|
|
99
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/core/array/index.ts
|
|
103
|
+
function unique(arr) {
|
|
104
|
+
return Array.from(new Set(arr));
|
|
105
|
+
}
|
|
106
|
+
function groupBy(arr, keyFn) {
|
|
107
|
+
const getKey = typeof keyFn === "string" ? (item) => item[keyFn] : keyFn;
|
|
108
|
+
return arr.reduce(
|
|
109
|
+
(acc, item) => {
|
|
110
|
+
const key = getKey(item);
|
|
111
|
+
if (!acc[key]) {
|
|
112
|
+
acc[key] = [];
|
|
113
|
+
}
|
|
114
|
+
acc[key].push(item);
|
|
115
|
+
return acc;
|
|
116
|
+
},
|
|
117
|
+
{}
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
function chunk(arr, size) {
|
|
121
|
+
const chunks = [];
|
|
122
|
+
for (let i = 0; i < arr.length; i += size) {
|
|
123
|
+
chunks.push(arr.slice(i, i + size));
|
|
124
|
+
}
|
|
125
|
+
return chunks;
|
|
126
|
+
}
|
|
127
|
+
function flatten(arr, depth = 1) {
|
|
128
|
+
return depth > 0 ? arr.reduce(
|
|
129
|
+
(acc, val) => acc.concat(Array.isArray(val) ? flatten(val, depth - 1) : val),
|
|
130
|
+
[]
|
|
131
|
+
) : arr;
|
|
132
|
+
}
|
|
133
|
+
function shuffle(arr) {
|
|
134
|
+
const result = [...arr];
|
|
135
|
+
for (let i = result.length - 1; i > 0; i--) {
|
|
136
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
137
|
+
[result[i], result[j]] = [result[j], result[i]];
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
function sample(arr, count = 1) {
|
|
142
|
+
if (arr.length === 0) {
|
|
143
|
+
throw new Error("Cannot sample from empty array");
|
|
144
|
+
}
|
|
145
|
+
if (count === 1) {
|
|
146
|
+
const shuffled2 = shuffle(arr);
|
|
147
|
+
return shuffled2[0];
|
|
148
|
+
}
|
|
149
|
+
if (count >= arr.length) return [...arr];
|
|
150
|
+
const shuffled = shuffle(arr);
|
|
151
|
+
return shuffled.slice(0, count);
|
|
152
|
+
}
|
|
153
|
+
function difference(arr1, arr2) {
|
|
154
|
+
const set2 = new Set(arr2);
|
|
155
|
+
return arr1.filter((item) => !set2.has(item));
|
|
156
|
+
}
|
|
157
|
+
function intersection(arr1, arr2) {
|
|
158
|
+
const set2 = new Set(arr2);
|
|
159
|
+
return arr1.filter((item) => set2.has(item));
|
|
160
|
+
}
|
|
161
|
+
function union(arr1, arr2) {
|
|
162
|
+
return unique([...arr1, ...arr2]);
|
|
163
|
+
}
|
|
164
|
+
function sortBy(arr, keyFn, order = "asc") {
|
|
165
|
+
const getKey = typeof keyFn === "string" ? (item) => item[keyFn] : keyFn;
|
|
166
|
+
const result = [...arr];
|
|
167
|
+
result.sort((a, b) => {
|
|
168
|
+
const keyA = getKey(a);
|
|
169
|
+
const keyB = getKey(b);
|
|
170
|
+
if (keyA < keyB) return order === "asc" ? -1 : 1;
|
|
171
|
+
if (keyA > keyB) return order === "asc" ? 1 : -1;
|
|
172
|
+
return 0;
|
|
173
|
+
});
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
function findIndexBy(arr, predicate) {
|
|
177
|
+
return arr.findIndex(predicate);
|
|
178
|
+
}
|
|
179
|
+
function partition(arr, predicate) {
|
|
180
|
+
const truthy = [];
|
|
181
|
+
const falsy = [];
|
|
182
|
+
arr.forEach((item) => {
|
|
183
|
+
if (predicate(item)) {
|
|
184
|
+
truthy.push(item);
|
|
185
|
+
} else {
|
|
186
|
+
falsy.push(item);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
return [truthy, falsy];
|
|
190
|
+
}
|
|
191
|
+
function zip(...arrays) {
|
|
192
|
+
const maxLength = Math.max(...arrays.map((arr) => arr.length));
|
|
193
|
+
const result = [];
|
|
194
|
+
for (let i = 0; i < maxLength; i++) {
|
|
195
|
+
result.push(
|
|
196
|
+
arrays.map((arr) => arr[i])
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
function unzip(array) {
|
|
202
|
+
if (array.length === 0) return [];
|
|
203
|
+
const length = array[0].length;
|
|
204
|
+
const result = Array.from({ length }, () => []);
|
|
205
|
+
array.forEach((item) => {
|
|
206
|
+
item.forEach((val, index) => {
|
|
207
|
+
result[index].push(val);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
function compact(arr) {
|
|
213
|
+
return arr.filter((item) => Boolean(item));
|
|
214
|
+
}
|
|
215
|
+
function take(arr, n) {
|
|
216
|
+
return arr.slice(0, n);
|
|
217
|
+
}
|
|
218
|
+
function drop(arr, n) {
|
|
219
|
+
return arr.slice(n);
|
|
220
|
+
}
|
|
221
|
+
function takeWhile(arr, predicate) {
|
|
222
|
+
const result = [];
|
|
223
|
+
for (const item of arr) {
|
|
224
|
+
if (!predicate(item)) break;
|
|
225
|
+
result.push(item);
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
function dropWhile(arr, predicate) {
|
|
230
|
+
let index = 0;
|
|
231
|
+
while (index < arr.length && predicate(arr[index])) {
|
|
232
|
+
index++;
|
|
233
|
+
}
|
|
234
|
+
return arr.slice(index);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/core/object/index.ts
|
|
238
|
+
function deepClone(obj) {
|
|
239
|
+
if (obj === null || typeof obj !== "object") {
|
|
240
|
+
return obj;
|
|
241
|
+
}
|
|
242
|
+
if (obj instanceof Date) {
|
|
243
|
+
return new Date(obj.getTime());
|
|
244
|
+
}
|
|
245
|
+
if (obj instanceof RegExp) {
|
|
246
|
+
return new RegExp(obj.source, obj.flags);
|
|
247
|
+
}
|
|
248
|
+
if (Array.isArray(obj)) {
|
|
249
|
+
return obj.map((item) => deepClone(item));
|
|
250
|
+
}
|
|
251
|
+
if (obj instanceof Map) {
|
|
252
|
+
const clonedMap = /* @__PURE__ */ new Map();
|
|
253
|
+
obj.forEach((value, key) => {
|
|
254
|
+
clonedMap.set(deepClone(key), deepClone(value));
|
|
255
|
+
});
|
|
256
|
+
return clonedMap;
|
|
257
|
+
}
|
|
258
|
+
if (obj instanceof Set) {
|
|
259
|
+
const clonedSet = /* @__PURE__ */ new Set();
|
|
260
|
+
obj.forEach((value) => {
|
|
261
|
+
clonedSet.add(deepClone(value));
|
|
262
|
+
});
|
|
263
|
+
return clonedSet;
|
|
264
|
+
}
|
|
265
|
+
if (typeof obj === "object") {
|
|
266
|
+
const clonedObj = {};
|
|
267
|
+
for (const key in obj) {
|
|
268
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
269
|
+
clonedObj[key] = deepClone(obj[key]);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return clonedObj;
|
|
273
|
+
}
|
|
274
|
+
return obj;
|
|
275
|
+
}
|
|
276
|
+
function merge(target, ...sources) {
|
|
277
|
+
return Object.assign({}, target, ...sources);
|
|
278
|
+
}
|
|
279
|
+
function deepMerge(target, ...sources) {
|
|
280
|
+
if (!sources.length) return target;
|
|
281
|
+
const source = sources.shift();
|
|
282
|
+
if (!source) {
|
|
283
|
+
return deepMerge(target, ...sources);
|
|
284
|
+
}
|
|
285
|
+
if (isObject(target) && isObject(source)) {
|
|
286
|
+
for (const key in source) {
|
|
287
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
288
|
+
const sourceValue = source[key];
|
|
289
|
+
const targetValue = target[key];
|
|
290
|
+
if (isObject(sourceValue) && isObject(targetValue)) {
|
|
291
|
+
target[key] = deepMerge(
|
|
292
|
+
targetValue,
|
|
293
|
+
sourceValue
|
|
294
|
+
);
|
|
295
|
+
} else if (sourceValue !== void 0) {
|
|
296
|
+
target[key] = sourceValue;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return deepMerge(target, ...sources);
|
|
302
|
+
}
|
|
303
|
+
function isObject(item) {
|
|
304
|
+
return item !== null && typeof item === "object" && !Array.isArray(item) && !(item instanceof Date) && !(item instanceof RegExp) && !(item instanceof Map) && !(item instanceof Set);
|
|
305
|
+
}
|
|
306
|
+
function get(obj, path, defaultValue) {
|
|
307
|
+
if (!obj || typeof obj !== "object") {
|
|
308
|
+
return defaultValue;
|
|
309
|
+
}
|
|
310
|
+
const keys2 = Array.isArray(path) ? path : path.split(".");
|
|
311
|
+
if (keys2.length === 0) {
|
|
312
|
+
return defaultValue;
|
|
313
|
+
}
|
|
314
|
+
let result = obj;
|
|
315
|
+
for (const key of keys2) {
|
|
316
|
+
if (result == null || typeof result !== "object" || Array.isArray(result)) {
|
|
317
|
+
return defaultValue;
|
|
318
|
+
}
|
|
319
|
+
const record = result;
|
|
320
|
+
if (!(key in record)) {
|
|
321
|
+
return defaultValue;
|
|
322
|
+
}
|
|
323
|
+
result = record[key];
|
|
324
|
+
}
|
|
325
|
+
return result !== void 0 ? result : defaultValue;
|
|
326
|
+
}
|
|
327
|
+
function set(obj, path, value) {
|
|
328
|
+
const keys2 = Array.isArray(path) ? path : path.split(".");
|
|
329
|
+
if (keys2.length === 0) return obj;
|
|
330
|
+
const result = deepClone(obj);
|
|
331
|
+
let current = result;
|
|
332
|
+
for (let i = 0; i < keys2.length - 1; i++) {
|
|
333
|
+
const key = keys2[i];
|
|
334
|
+
if (!(key in current) || typeof current[key] !== "object" || current[key] === null) {
|
|
335
|
+
current[key] = {};
|
|
336
|
+
}
|
|
337
|
+
current = current[key];
|
|
338
|
+
}
|
|
339
|
+
current[keys2[keys2.length - 1]] = value;
|
|
340
|
+
return result;
|
|
341
|
+
}
|
|
342
|
+
function pick(obj, keys2) {
|
|
343
|
+
const result = {};
|
|
344
|
+
keys2.forEach((key) => {
|
|
345
|
+
if (key in obj) {
|
|
346
|
+
result[key] = obj[key];
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
return result;
|
|
350
|
+
}
|
|
351
|
+
function omit(obj, keys2) {
|
|
352
|
+
const result = { ...obj };
|
|
353
|
+
keys2.forEach((key) => {
|
|
354
|
+
delete result[key];
|
|
355
|
+
});
|
|
356
|
+
return result;
|
|
357
|
+
}
|
|
358
|
+
function keys(obj, deep = false) {
|
|
359
|
+
if (!deep) return Object.keys(obj);
|
|
360
|
+
const result = [];
|
|
361
|
+
const traverse = (current, prefix = "") => {
|
|
362
|
+
Object.keys(current).forEach((key) => {
|
|
363
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
364
|
+
result.push(fullKey);
|
|
365
|
+
const value = current[key];
|
|
366
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof RegExp)) {
|
|
367
|
+
traverse(value, fullKey);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
};
|
|
371
|
+
traverse(obj);
|
|
372
|
+
return result;
|
|
373
|
+
}
|
|
374
|
+
function values(obj, deep = false) {
|
|
375
|
+
if (!deep) return Object.values(obj);
|
|
376
|
+
const result = [];
|
|
377
|
+
const traverse = (current) => {
|
|
378
|
+
Object.values(current).forEach((value) => {
|
|
379
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof RegExp)) {
|
|
380
|
+
traverse(value);
|
|
381
|
+
} else {
|
|
382
|
+
result.push(value);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
};
|
|
386
|
+
traverse(obj);
|
|
387
|
+
return result;
|
|
388
|
+
}
|
|
389
|
+
function mapKeys(obj, fn) {
|
|
390
|
+
const result = {};
|
|
391
|
+
Object.keys(obj).forEach((key) => {
|
|
392
|
+
const newKey = fn(key, obj[key]);
|
|
393
|
+
result[newKey] = obj[key];
|
|
394
|
+
});
|
|
395
|
+
return result;
|
|
396
|
+
}
|
|
397
|
+
function mapValues(obj, fn) {
|
|
398
|
+
const result = {};
|
|
399
|
+
Object.keys(obj).forEach((key) => {
|
|
400
|
+
result[key] = fn(obj[key], key);
|
|
401
|
+
});
|
|
402
|
+
return result;
|
|
403
|
+
}
|
|
404
|
+
function invert(obj) {
|
|
405
|
+
const result = {};
|
|
406
|
+
Object.keys(obj).forEach((key) => {
|
|
407
|
+
result[String(obj[key])] = key;
|
|
408
|
+
});
|
|
409
|
+
return result;
|
|
410
|
+
}
|
|
411
|
+
function defaults(obj, ...defaults2) {
|
|
412
|
+
const result = { ...obj };
|
|
413
|
+
defaults2.reverse().forEach((defaultObj) => {
|
|
414
|
+
Object.keys(defaultObj).forEach((key) => {
|
|
415
|
+
const typedKey = key;
|
|
416
|
+
if (!(typedKey in result) || result[typedKey] === void 0) {
|
|
417
|
+
result[typedKey] = defaultObj[typedKey];
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
return result;
|
|
422
|
+
}
|
|
423
|
+
function isEmptyObject(obj) {
|
|
424
|
+
return Object.keys(obj).length === 0;
|
|
425
|
+
}
|
|
426
|
+
function isEqual(obj1, obj2) {
|
|
427
|
+
if (obj1 === obj2) return true;
|
|
428
|
+
if (obj1 === null || obj2 === null) return false;
|
|
429
|
+
if (typeof obj1 !== "object" || typeof obj2 !== "object") return false;
|
|
430
|
+
if (obj1 instanceof Date && obj2 instanceof Date) {
|
|
431
|
+
return obj1.getTime() === obj2.getTime();
|
|
432
|
+
}
|
|
433
|
+
if (Array.isArray(obj1) && Array.isArray(obj2)) {
|
|
434
|
+
if (obj1.length !== obj2.length) return false;
|
|
435
|
+
return obj1.every((item, index) => isEqual(item, obj2[index]));
|
|
436
|
+
}
|
|
437
|
+
if (Array.isArray(obj1) || Array.isArray(obj2)) return false;
|
|
438
|
+
const keys1 = Object.keys(obj1);
|
|
439
|
+
const keys2 = Object.keys(obj2);
|
|
440
|
+
if (keys1.length !== keys2.length) return false;
|
|
441
|
+
return keys1.every((key) => {
|
|
442
|
+
const val1 = obj1[key];
|
|
443
|
+
const val2 = obj2[key];
|
|
444
|
+
return isEqual(val1, val2);
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
function transform(obj, iteratee, accumulator = {}) {
|
|
448
|
+
Object.keys(obj).forEach((key) => {
|
|
449
|
+
iteratee(accumulator, obj[key], key);
|
|
450
|
+
});
|
|
451
|
+
return accumulator;
|
|
452
|
+
}
|
|
453
|
+
function pickBy(obj, predicate) {
|
|
454
|
+
const result = {};
|
|
455
|
+
Object.keys(obj).forEach((key) => {
|
|
456
|
+
const typedKey = key;
|
|
457
|
+
if (predicate(obj[typedKey], key)) {
|
|
458
|
+
result[typedKey] = obj[typedKey];
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
return result;
|
|
462
|
+
}
|
|
463
|
+
function omitBy(obj, predicate) {
|
|
464
|
+
const result = {};
|
|
465
|
+
Object.keys(obj).forEach((key) => {
|
|
466
|
+
const typedKey = key;
|
|
467
|
+
if (!predicate(obj[typedKey], key)) {
|
|
468
|
+
result[typedKey] = obj[typedKey];
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
return result;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// src/core/date/index.ts
|
|
475
|
+
function formatDate(date, format = "YYYY-MM-DD HH:mm:ss") {
|
|
476
|
+
const d = typeof date === "number" ? new Date(date) : date;
|
|
477
|
+
const year = d.getFullYear();
|
|
478
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
479
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
480
|
+
const hours = String(d.getHours()).padStart(2, "0");
|
|
481
|
+
const minutes = String(d.getMinutes()).padStart(2, "0");
|
|
482
|
+
const seconds = String(d.getSeconds()).padStart(2, "0");
|
|
483
|
+
return format.replace("YYYY", String(year)).replace("MM", month).replace("DD", day).replace("HH", hours).replace("mm", minutes).replace("ss", seconds);
|
|
484
|
+
}
|
|
485
|
+
function getRelativeTime(date) {
|
|
486
|
+
const now = /* @__PURE__ */ new Date();
|
|
487
|
+
const target = typeof date === "number" ? new Date(date) : date;
|
|
488
|
+
const diff = now.getTime() - target.getTime();
|
|
489
|
+
const seconds = Math.floor(diff / 1e3);
|
|
490
|
+
const minutes = Math.floor(seconds / 60);
|
|
491
|
+
const hours = Math.floor(minutes / 60);
|
|
492
|
+
const days = Math.floor(hours / 24);
|
|
493
|
+
const months = Math.floor(days / 30);
|
|
494
|
+
const years = Math.floor(days / 365);
|
|
495
|
+
if (years > 0) return `${years}\u5E74\u524D`;
|
|
496
|
+
if (months > 0) return `${months}\u4E2A\u6708\u524D`;
|
|
497
|
+
if (days > 0) return `${days}\u5929\u524D`;
|
|
498
|
+
if (hours > 0) return `${hours}\u5C0F\u65F6\u524D`;
|
|
499
|
+
if (minutes > 0) return `${minutes}\u5206\u949F\u524D`;
|
|
500
|
+
if (seconds > 0) return `${seconds}\u79D2\u524D`;
|
|
501
|
+
return "\u521A\u521A";
|
|
502
|
+
}
|
|
503
|
+
function isToday(date) {
|
|
504
|
+
const d = typeof date === "number" ? new Date(date) : date;
|
|
505
|
+
const today = /* @__PURE__ */ new Date();
|
|
506
|
+
return d.getDate() === today.getDate() && d.getMonth() === today.getMonth() && d.getFullYear() === today.getFullYear();
|
|
507
|
+
}
|
|
508
|
+
function isYesterday(date) {
|
|
509
|
+
const d = typeof date === "number" ? new Date(date) : date;
|
|
510
|
+
const yesterday = /* @__PURE__ */ new Date();
|
|
511
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
512
|
+
return d.getDate() === yesterday.getDate() && d.getMonth() === yesterday.getMonth() && d.getFullYear() === yesterday.getFullYear();
|
|
513
|
+
}
|
|
514
|
+
function getTimeFromGMT(gmt) {
|
|
515
|
+
const now = /* @__PURE__ */ new Date();
|
|
516
|
+
const localOffset = now.getTimezoneOffset();
|
|
517
|
+
const gmtOffsetMinutes = gmt * 60;
|
|
518
|
+
const targetTime = new Date(now.getTime() + localOffset * 6e4 + gmtOffsetMinutes * 6e4);
|
|
519
|
+
const format = getDateFormatByGMT(gmt);
|
|
520
|
+
return formatDate(targetTime, format);
|
|
521
|
+
}
|
|
522
|
+
function getDateFormatByGMT(gmt) {
|
|
523
|
+
switch (gmt) {
|
|
524
|
+
case 0:
|
|
525
|
+
return "YYYY-MM-DD HH:mm:ss";
|
|
526
|
+
case 1:
|
|
527
|
+
case 2:
|
|
528
|
+
case 3:
|
|
529
|
+
return "YYYY-MM-DD HH:mm:ss";
|
|
530
|
+
case 8:
|
|
531
|
+
return "YYYY-MM-DD HH:mm:ss";
|
|
532
|
+
case 9:
|
|
533
|
+
return "YYYY-MM-DD HH:mm:ss";
|
|
534
|
+
case -5:
|
|
535
|
+
case -6:
|
|
536
|
+
case -7:
|
|
537
|
+
case -8:
|
|
538
|
+
return "MM/DD/YYYY HH:mm:ss";
|
|
539
|
+
default:
|
|
540
|
+
return "YYYY-MM-DD HH:mm:ss";
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
function addDays(date, days) {
|
|
544
|
+
const d = typeof date === "number" ? new Date(date) : new Date(date);
|
|
545
|
+
d.setDate(d.getDate() + days);
|
|
546
|
+
return d;
|
|
547
|
+
}
|
|
548
|
+
function addMonths(date, months) {
|
|
549
|
+
const d = typeof date === "number" ? new Date(date) : new Date(date);
|
|
550
|
+
d.setMonth(d.getMonth() + months);
|
|
551
|
+
return d;
|
|
552
|
+
}
|
|
553
|
+
function addYears(date, years) {
|
|
554
|
+
const d = typeof date === "number" ? new Date(date) : new Date(date);
|
|
555
|
+
d.setFullYear(d.getFullYear() + years);
|
|
556
|
+
return d;
|
|
557
|
+
}
|
|
558
|
+
function diffDays(date1, date2) {
|
|
559
|
+
const d1 = typeof date1 === "number" ? new Date(date1) : date1;
|
|
560
|
+
const d2 = typeof date2 === "number" ? new Date(date2) : date2;
|
|
561
|
+
const diff = d1.getTime() - d2.getTime();
|
|
562
|
+
return Math.floor(diff / (1e3 * 60 * 60 * 24));
|
|
563
|
+
}
|
|
564
|
+
function startOfDay(date) {
|
|
565
|
+
const d = typeof date === "number" ? new Date(date) : new Date(date);
|
|
566
|
+
d.setHours(0, 0, 0, 0);
|
|
567
|
+
return d;
|
|
568
|
+
}
|
|
569
|
+
function endOfDay(date) {
|
|
570
|
+
const d = typeof date === "number" ? new Date(date) : new Date(date);
|
|
571
|
+
d.setHours(23, 59, 59, 999);
|
|
572
|
+
return d;
|
|
573
|
+
}
|
|
574
|
+
function isWeekend(date) {
|
|
575
|
+
const d = typeof date === "number" ? new Date(date) : date;
|
|
576
|
+
const day = d.getDay();
|
|
577
|
+
return day === 0 || day === 6;
|
|
578
|
+
}
|
|
579
|
+
function isWeekday(date) {
|
|
580
|
+
return !isWeekend(date);
|
|
581
|
+
}
|
|
582
|
+
function getWeekNumber(date) {
|
|
583
|
+
const d = typeof date === "number" ? new Date(date) : new Date(date);
|
|
584
|
+
const target = new Date(d.valueOf());
|
|
585
|
+
const dayNr = (d.getDay() + 6) % 7;
|
|
586
|
+
target.setDate(target.getDate() - dayNr + 3);
|
|
587
|
+
const firstThursday = target.valueOf();
|
|
588
|
+
target.setMonth(0, 1);
|
|
589
|
+
if (target.getDay() !== 4) {
|
|
590
|
+
target.setMonth(0, 1 + (4 - target.getDay() + 7) % 7);
|
|
591
|
+
}
|
|
592
|
+
return 1 + Math.ceil((firstThursday - target.valueOf()) / 6048e5);
|
|
593
|
+
}
|
|
594
|
+
function getQuarter(date) {
|
|
595
|
+
const d = typeof date === "number" ? new Date(date) : date;
|
|
596
|
+
return Math.floor(d.getMonth() / 3) + 1;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// src/core/validation/index.ts
|
|
600
|
+
function isValidEmail(email) {
|
|
601
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
602
|
+
return emailRegex.test(email);
|
|
603
|
+
}
|
|
604
|
+
function isValidPhone(phone) {
|
|
605
|
+
const phoneRegex = /^1[3-9]\d{9}$/;
|
|
606
|
+
return phoneRegex.test(phone);
|
|
607
|
+
}
|
|
608
|
+
function isValidUrl(url) {
|
|
609
|
+
try {
|
|
610
|
+
new URL(url);
|
|
611
|
+
return true;
|
|
612
|
+
} catch {
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
function isValidIdCard(idCard) {
|
|
617
|
+
const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/;
|
|
618
|
+
return idCardRegex.test(idCard);
|
|
619
|
+
}
|
|
620
|
+
function isEmpty(value) {
|
|
621
|
+
if (value === null || value === void 0) return true;
|
|
622
|
+
if (typeof value === "string") return value.trim().length === 0;
|
|
623
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
624
|
+
if (typeof value === "object") return Object.keys(value).length === 0;
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
function isValidUUID(uuid) {
|
|
628
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
629
|
+
return uuidRegex.test(uuid);
|
|
630
|
+
}
|
|
631
|
+
function isValidIP(ip) {
|
|
632
|
+
const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
633
|
+
return ipRegex.test(ip);
|
|
634
|
+
}
|
|
635
|
+
function isValidDomain(domain) {
|
|
636
|
+
const domainRegex = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i;
|
|
637
|
+
return domainRegex.test(domain);
|
|
638
|
+
}
|
|
639
|
+
function isValidHexColor(color) {
|
|
640
|
+
const hexColorRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
|
|
641
|
+
return hexColorRegex.test(color);
|
|
642
|
+
}
|
|
643
|
+
function isValidJSON(json) {
|
|
644
|
+
try {
|
|
645
|
+
JSON.parse(json);
|
|
646
|
+
return true;
|
|
647
|
+
} catch {
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function isNumeric(value) {
|
|
652
|
+
if (typeof value === "number") return !isNaN(value) && isFinite(value);
|
|
653
|
+
if (typeof value === "string") {
|
|
654
|
+
return value.trim() !== "" && !isNaN(Number(value)) && isFinite(Number(value));
|
|
655
|
+
}
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
658
|
+
function isInteger(value) {
|
|
659
|
+
if (typeof value === "number") return Number.isInteger(value);
|
|
660
|
+
if (typeof value === "string") {
|
|
661
|
+
const num = Number(value);
|
|
662
|
+
return !isNaN(num) && Number.isInteger(num);
|
|
663
|
+
}
|
|
664
|
+
return false;
|
|
665
|
+
}
|
|
666
|
+
function isFloat(value) {
|
|
667
|
+
if (typeof value === "number")
|
|
668
|
+
return !isNaN(value) && isFinite(value) && !Number.isInteger(value);
|
|
669
|
+
if (typeof value === "string") {
|
|
670
|
+
const num = Number(value);
|
|
671
|
+
return !isNaN(num) && isFinite(num) && !Number.isInteger(num);
|
|
672
|
+
}
|
|
673
|
+
return false;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// src/format/number/index.ts
|
|
677
|
+
function formatNumber(num, options = {}) {
|
|
678
|
+
const { decimals, separator = ",", decimalPoint = ".", minimumFractionDigits } = options;
|
|
679
|
+
let numStr;
|
|
680
|
+
if (decimals !== void 0) {
|
|
681
|
+
numStr = num.toFixed(decimals);
|
|
682
|
+
} else if (minimumFractionDigits !== void 0) {
|
|
683
|
+
numStr = num.toFixed(minimumFractionDigits);
|
|
684
|
+
} else {
|
|
685
|
+
const str = num.toString();
|
|
686
|
+
if (str.includes(".")) {
|
|
687
|
+
numStr = str;
|
|
688
|
+
} else {
|
|
689
|
+
numStr = num.toFixed(0);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
const parts = numStr.split(".");
|
|
693
|
+
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, separator);
|
|
694
|
+
return parts.join(decimalPoint);
|
|
695
|
+
}
|
|
696
|
+
function random(min, max, integer = false) {
|
|
697
|
+
const value = Math.random() * (max - min) + min;
|
|
698
|
+
return integer ? Math.floor(value) : value;
|
|
699
|
+
}
|
|
700
|
+
function clamp(num, min, max) {
|
|
701
|
+
return Math.min(Math.max(num, min), max);
|
|
702
|
+
}
|
|
703
|
+
function round(num, decimals = 0) {
|
|
704
|
+
const factor = Math.pow(10, decimals);
|
|
705
|
+
return Math.round(num * factor) / factor;
|
|
706
|
+
}
|
|
707
|
+
function floor(num, decimals = 0) {
|
|
708
|
+
const factor = Math.pow(10, decimals);
|
|
709
|
+
return Math.floor(num * factor) / factor;
|
|
710
|
+
}
|
|
711
|
+
function ceil(num, decimals = 0) {
|
|
712
|
+
const factor = Math.pow(10, decimals);
|
|
713
|
+
return Math.ceil(num * factor) / factor;
|
|
714
|
+
}
|
|
715
|
+
function toFixed(num, decimals) {
|
|
716
|
+
return num.toFixed(decimals);
|
|
717
|
+
}
|
|
718
|
+
function parseNumber(str, defaultValue = 0) {
|
|
719
|
+
const num = Number(str);
|
|
720
|
+
return isNaN(num) ? defaultValue : num;
|
|
721
|
+
}
|
|
722
|
+
function isBetween(num, min, max, inclusive = true) {
|
|
723
|
+
if (inclusive) {
|
|
724
|
+
return num >= min && num <= max;
|
|
725
|
+
}
|
|
726
|
+
return num > min && num < max;
|
|
727
|
+
}
|
|
728
|
+
function percent(value, decimalsOrTotal, decimals) {
|
|
729
|
+
if (value >= 0 && value <= 1 && (decimalsOrTotal === void 0 || decimalsOrTotal < 10)) {
|
|
730
|
+
const dec2 = decimalsOrTotal ?? 0;
|
|
731
|
+
return Number((value * 100).toFixed(dec2));
|
|
732
|
+
}
|
|
733
|
+
if (decimalsOrTotal !== void 0 && decimalsOrTotal >= 10) {
|
|
734
|
+
const total = decimalsOrTotal;
|
|
735
|
+
const dec2 = decimals ?? 2;
|
|
736
|
+
if (total === 0) return "0%";
|
|
737
|
+
return `${(value / total * 100).toFixed(dec2)}%`;
|
|
738
|
+
}
|
|
739
|
+
const dec = decimalsOrTotal ?? 0;
|
|
740
|
+
return Number((value * 100).toFixed(dec));
|
|
741
|
+
}
|
|
742
|
+
function formatCurrency(num, currency = "\xA5", decimals = 2) {
|
|
743
|
+
return `${currency}${formatNumber(num, { decimals, separator: "," })}`;
|
|
744
|
+
}
|
|
745
|
+
function formatBytes(bytes, decimals = 2) {
|
|
746
|
+
if (bytes === 0) return "0 Bytes";
|
|
747
|
+
const k = 1024;
|
|
748
|
+
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB"];
|
|
749
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
750
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// src/format/url/index.ts
|
|
754
|
+
function parseUrl(url) {
|
|
755
|
+
try {
|
|
756
|
+
const urlObj = new URL(url);
|
|
757
|
+
return {
|
|
758
|
+
protocol: urlObj.protocol,
|
|
759
|
+
host: urlObj.host,
|
|
760
|
+
hostname: urlObj.hostname,
|
|
761
|
+
port: urlObj.port,
|
|
762
|
+
pathname: urlObj.pathname,
|
|
763
|
+
search: urlObj.search,
|
|
764
|
+
hash: urlObj.hash,
|
|
765
|
+
origin: urlObj.origin
|
|
766
|
+
};
|
|
767
|
+
} catch {
|
|
768
|
+
throw new Error(`Invalid URL: ${url}`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function buildUrl(parts) {
|
|
772
|
+
const { protocol = "https:", host = "", pathname = "/", search = "", hash: hash2 = "" } = parts;
|
|
773
|
+
return `${protocol}//${host}${pathname}${search}${hash2}`;
|
|
774
|
+
}
|
|
775
|
+
function getQueryParams(url) {
|
|
776
|
+
const searchParams = url ? new URL(url).searchParams : new URLSearchParams(window.location.search);
|
|
777
|
+
const params = {};
|
|
778
|
+
searchParams.forEach((value, key) => {
|
|
779
|
+
params[key] = value;
|
|
780
|
+
});
|
|
781
|
+
return params;
|
|
782
|
+
}
|
|
783
|
+
function setQueryParams(url, params) {
|
|
784
|
+
try {
|
|
785
|
+
const urlObj = new URL(url);
|
|
786
|
+
Object.keys(params).forEach((key) => {
|
|
787
|
+
urlObj.searchParams.set(key, String(params[key]));
|
|
788
|
+
});
|
|
789
|
+
return urlObj.toString();
|
|
790
|
+
} catch {
|
|
791
|
+
throw new Error(`Invalid URL: ${url}`);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
function removeQueryParams(url, keys2) {
|
|
795
|
+
try {
|
|
796
|
+
const urlObj = new URL(url);
|
|
797
|
+
keys2.forEach((key) => {
|
|
798
|
+
urlObj.searchParams.delete(key);
|
|
799
|
+
});
|
|
800
|
+
return urlObj.toString();
|
|
801
|
+
} catch {
|
|
802
|
+
throw new Error(`Invalid URL: ${url}`);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
function updateQueryParams(url, params) {
|
|
806
|
+
try {
|
|
807
|
+
const urlObj = new URL(url);
|
|
808
|
+
Object.keys(params).forEach((key) => {
|
|
809
|
+
const value = params[key];
|
|
810
|
+
if (value === null) {
|
|
811
|
+
urlObj.searchParams.delete(key);
|
|
812
|
+
} else {
|
|
813
|
+
urlObj.searchParams.set(key, String(value));
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
return urlObj.toString();
|
|
817
|
+
} catch {
|
|
818
|
+
throw new Error(`Invalid URL: ${url}`);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
function isAbsoluteUrl(url) {
|
|
822
|
+
try {
|
|
823
|
+
new URL(url);
|
|
824
|
+
return true;
|
|
825
|
+
} catch {
|
|
826
|
+
return /^https?:\/\//i.test(url);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
function joinUrl(baseUrl, ...paths) {
|
|
830
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
831
|
+
const joinedPaths = paths.map((path) => path.replace(/^\/+|\/+$/g, "")).filter((path) => path.length > 0).join("/");
|
|
832
|
+
return `${base}/${joinedPaths}`;
|
|
833
|
+
}
|
|
834
|
+
function normalizeUrl(url) {
|
|
835
|
+
try {
|
|
836
|
+
const urlObj = new URL(url);
|
|
837
|
+
urlObj.pathname = urlObj.pathname.replace(/\/+/g, "/");
|
|
838
|
+
return urlObj.toString();
|
|
839
|
+
} catch {
|
|
840
|
+
return url;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// src/format/color/index.ts
|
|
845
|
+
function hexToRgb(hex) {
|
|
846
|
+
const cleanHex = hex.replace("#", "");
|
|
847
|
+
if (!/^[0-9A-Fa-f]{6}$/.test(cleanHex)) {
|
|
848
|
+
throw new Error("Invalid hex color format");
|
|
849
|
+
}
|
|
850
|
+
return {
|
|
851
|
+
r: parseInt(cleanHex.substring(0, 2), 16),
|
|
852
|
+
g: parseInt(cleanHex.substring(2, 4), 16),
|
|
853
|
+
b: parseInt(cleanHex.substring(4, 6), 16)
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
function rgbToHex(rgb) {
|
|
857
|
+
let r;
|
|
858
|
+
let g;
|
|
859
|
+
let b;
|
|
860
|
+
if (typeof rgb === "string") {
|
|
861
|
+
const match = rgb.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
862
|
+
if (!match) {
|
|
863
|
+
throw new Error("Invalid RGB string format");
|
|
864
|
+
}
|
|
865
|
+
r = parseInt(match[1], 10);
|
|
866
|
+
g = parseInt(match[2], 10);
|
|
867
|
+
b = parseInt(match[3], 10);
|
|
868
|
+
} else {
|
|
869
|
+
r = rgb.r;
|
|
870
|
+
g = rgb.g;
|
|
871
|
+
b = rgb.b;
|
|
872
|
+
}
|
|
873
|
+
r = Math.max(0, Math.min(255, Math.round(r)));
|
|
874
|
+
g = Math.max(0, Math.min(255, Math.round(g)));
|
|
875
|
+
b = Math.max(0, Math.min(255, Math.round(b)));
|
|
876
|
+
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`.toUpperCase();
|
|
877
|
+
}
|
|
878
|
+
function rgbToHsl(rgb) {
|
|
879
|
+
const r = rgb.r / 255;
|
|
880
|
+
const g = rgb.g / 255;
|
|
881
|
+
const b = rgb.b / 255;
|
|
882
|
+
const max = Math.max(r, g, b);
|
|
883
|
+
const min = Math.min(r, g, b);
|
|
884
|
+
const delta = max - min;
|
|
885
|
+
let h = 0;
|
|
886
|
+
let s = 0;
|
|
887
|
+
const l = (max + min) / 2;
|
|
888
|
+
if (delta !== 0) {
|
|
889
|
+
s = l > 0.5 ? delta / (2 - max - min) : delta / (max + min);
|
|
890
|
+
if (max === r) {
|
|
891
|
+
h = ((g - b) / delta + (g < b ? 6 : 0)) / 6;
|
|
892
|
+
} else if (max === g) {
|
|
893
|
+
h = ((b - r) / delta + 2) / 6;
|
|
894
|
+
} else {
|
|
895
|
+
h = ((r - g) / delta + 4) / 6;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return {
|
|
899
|
+
h: Math.round(h * 360),
|
|
900
|
+
s: Math.round(s * 100),
|
|
901
|
+
l: Math.round(l * 100)
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
function hslToRgb(hsl) {
|
|
905
|
+
const h = hsl.h / 360;
|
|
906
|
+
const s = hsl.s / 100;
|
|
907
|
+
const l = hsl.l / 100;
|
|
908
|
+
let r;
|
|
909
|
+
let g;
|
|
910
|
+
let b;
|
|
911
|
+
if (s === 0) {
|
|
912
|
+
r = g = b = l;
|
|
913
|
+
} else {
|
|
914
|
+
const hue2rgb = (p2, q2, t) => {
|
|
915
|
+
if (t < 0) t += 1;
|
|
916
|
+
if (t > 1) t -= 1;
|
|
917
|
+
if (t < 1 / 6) return p2 + (q2 - p2) * 6 * t;
|
|
918
|
+
if (t < 1 / 2) return q2;
|
|
919
|
+
if (t < 2 / 3) return p2 + (q2 - p2) * (2 / 3 - t) * 6;
|
|
920
|
+
return p2;
|
|
921
|
+
};
|
|
922
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
923
|
+
const p = 2 * l - q;
|
|
924
|
+
r = hue2rgb(p, q, h + 1 / 3);
|
|
925
|
+
g = hue2rgb(p, q, h);
|
|
926
|
+
b = hue2rgb(p, q, h - 1 / 3);
|
|
927
|
+
}
|
|
928
|
+
return {
|
|
929
|
+
r: Math.round(r * 255),
|
|
930
|
+
g: Math.round(g * 255),
|
|
931
|
+
b: Math.round(b * 255)
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
function lighten(color, amount) {
|
|
935
|
+
const rgb = typeof color === "string" ? hexToRgb(color) : color;
|
|
936
|
+
const hsl = rgbToHsl(rgb);
|
|
937
|
+
hsl.l = Math.min(100, hsl.l + amount);
|
|
938
|
+
return rgbToHex(hslToRgb(hsl));
|
|
939
|
+
}
|
|
940
|
+
function darken(color, amount) {
|
|
941
|
+
const rgb = typeof color === "string" ? hexToRgb(color) : color;
|
|
942
|
+
const hsl = rgbToHsl(rgb);
|
|
943
|
+
hsl.l = Math.max(0, hsl.l - amount);
|
|
944
|
+
return rgbToHex(hslToRgb(hsl));
|
|
945
|
+
}
|
|
946
|
+
function mix(color1, color2, ratio) {
|
|
947
|
+
const rgb1 = hexToRgb(color1);
|
|
948
|
+
const rgb2 = hexToRgb(color2);
|
|
949
|
+
const r = Math.round(rgb1.r * (1 - ratio) + rgb2.r * ratio);
|
|
950
|
+
const g = Math.round(rgb1.g * (1 - ratio) + rgb2.g * ratio);
|
|
951
|
+
const b = Math.round(rgb1.b * (1 - ratio) + rgb2.b * ratio);
|
|
952
|
+
return rgbToHex({ r, g, b });
|
|
953
|
+
}
|
|
954
|
+
function contrast(color1, color2) {
|
|
955
|
+
const rgb1 = hexToRgb(color1);
|
|
956
|
+
const rgb2 = hexToRgb(color2);
|
|
957
|
+
const getLuminance = (rgb) => {
|
|
958
|
+
const normalize = (value) => {
|
|
959
|
+
value = value / 255;
|
|
960
|
+
return value <= 0.03928 ? value / 12.92 : Math.pow((value + 0.055) / 1.055, 2.4);
|
|
961
|
+
};
|
|
962
|
+
return 0.2126 * normalize(rgb.r) + 0.7152 * normalize(rgb.g) + 0.0722 * normalize(rgb.b);
|
|
963
|
+
};
|
|
964
|
+
const lum1 = getLuminance(rgb1);
|
|
965
|
+
const lum2 = getLuminance(rgb2);
|
|
966
|
+
const lighter = Math.max(lum1, lum2);
|
|
967
|
+
const darker = Math.min(lum1, lum2);
|
|
968
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// src/format/i18n/index.ts
|
|
972
|
+
function getLocale() {
|
|
973
|
+
if (typeof navigator !== "undefined" && navigator.language) {
|
|
974
|
+
return navigator.language;
|
|
975
|
+
}
|
|
976
|
+
return "en-US";
|
|
977
|
+
}
|
|
978
|
+
function formatNumberI18n(value, locale, options) {
|
|
979
|
+
const loc = locale || getLocale();
|
|
980
|
+
return new Intl.NumberFormat(loc, options).format(value);
|
|
981
|
+
}
|
|
982
|
+
function formatDateI18n(date, locale, options) {
|
|
983
|
+
const loc = locale || getLocale();
|
|
984
|
+
const dateObj = typeof date === "number" ? new Date(date) : date;
|
|
985
|
+
return new Intl.DateTimeFormat(loc, options).format(dateObj);
|
|
986
|
+
}
|
|
987
|
+
function formatCurrencyI18n(value, currency, locale, options) {
|
|
988
|
+
const loc = locale || getLocale();
|
|
989
|
+
return new Intl.NumberFormat(loc, {
|
|
990
|
+
style: "currency",
|
|
991
|
+
currency,
|
|
992
|
+
...options
|
|
993
|
+
}).format(value);
|
|
994
|
+
}
|
|
995
|
+
function translate(key, dictionary, defaultValue) {
|
|
996
|
+
const keys2 = key.split(".");
|
|
997
|
+
let current = dictionary;
|
|
998
|
+
for (const k of keys2) {
|
|
999
|
+
if (typeof current === "object" && current !== null && k in current) {
|
|
1000
|
+
current = current[k];
|
|
1001
|
+
} else {
|
|
1002
|
+
return defaultValue || key;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
if (typeof current === "string") {
|
|
1006
|
+
return current;
|
|
1007
|
+
}
|
|
1008
|
+
return defaultValue || key;
|
|
1009
|
+
}
|
|
1010
|
+
function createTranslator(dictionary) {
|
|
1011
|
+
return (key, defaultValue) => {
|
|
1012
|
+
return translate(key, dictionary, defaultValue);
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
function formatRelativeTime(date, locale) {
|
|
1016
|
+
const loc = locale || getLocale();
|
|
1017
|
+
const dateObj = typeof date === "number" ? new Date(date) : date;
|
|
1018
|
+
const now = /* @__PURE__ */ new Date();
|
|
1019
|
+
const diffInSeconds = Math.floor((now.getTime() - dateObj.getTime()) / 1e3);
|
|
1020
|
+
const rtf = new Intl.RelativeTimeFormat(loc, { numeric: "auto" });
|
|
1021
|
+
const intervals = [
|
|
1022
|
+
{ unit: "year", seconds: 31536e3 },
|
|
1023
|
+
{ unit: "month", seconds: 2592e3 },
|
|
1024
|
+
{ unit: "week", seconds: 604800 },
|
|
1025
|
+
{ unit: "day", seconds: 86400 },
|
|
1026
|
+
{ unit: "hour", seconds: 3600 },
|
|
1027
|
+
{ unit: "minute", seconds: 60 },
|
|
1028
|
+
{ unit: "second", seconds: 1 }
|
|
1029
|
+
];
|
|
1030
|
+
for (const interval of intervals) {
|
|
1031
|
+
const count = Math.floor(Math.abs(diffInSeconds) / interval.seconds);
|
|
1032
|
+
if (count >= 1) {
|
|
1033
|
+
return rtf.format(diffInSeconds < 0 ? count : -count, interval.unit);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
return rtf.format(0, "second");
|
|
1037
|
+
}
|
|
1038
|
+
function pluralize(count, singular, plural, locale) {
|
|
1039
|
+
const loc = locale || getLocale();
|
|
1040
|
+
const pluralForm = plural || `${singular}s`;
|
|
1041
|
+
const pluralRules = new Intl.PluralRules(loc);
|
|
1042
|
+
const rule = pluralRules.select(count);
|
|
1043
|
+
if (rule === "one" || count === 1) {
|
|
1044
|
+
return `${count} ${singular}`;
|
|
1045
|
+
}
|
|
1046
|
+
return `${count} ${pluralForm}`;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// src/browser/file/index.ts
|
|
1050
|
+
async function calculateFileMD5(file) {
|
|
1051
|
+
return new Promise((resolve, reject) => {
|
|
1052
|
+
if (typeof FileReader === "undefined") {
|
|
1053
|
+
reject(new Error("FileReader is not supported in this environment"));
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
const reader = new FileReader();
|
|
1057
|
+
reader.onload = async (e) => {
|
|
1058
|
+
try {
|
|
1059
|
+
const arrayBuffer = e.target?.result;
|
|
1060
|
+
if (!arrayBuffer) {
|
|
1061
|
+
reject(new Error("Failed to read file"));
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", arrayBuffer);
|
|
1065
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1066
|
+
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1067
|
+
resolve(hashHex);
|
|
1068
|
+
} catch (error) {
|
|
1069
|
+
reject(error);
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
reader.onerror = reject;
|
|
1073
|
+
reader.readAsArrayBuffer(file);
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
async function calculateBlobMD5(blob) {
|
|
1077
|
+
return new Promise((resolve, reject) => {
|
|
1078
|
+
if (typeof FileReader === "undefined") {
|
|
1079
|
+
reject(new Error("FileReader is not supported in this environment"));
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
const reader = new FileReader();
|
|
1083
|
+
reader.onload = async (e) => {
|
|
1084
|
+
try {
|
|
1085
|
+
const arrayBuffer = e.target?.result;
|
|
1086
|
+
if (!arrayBuffer) {
|
|
1087
|
+
reject(new Error("Failed to read blob"));
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", arrayBuffer);
|
|
1091
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1092
|
+
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1093
|
+
resolve(hashHex);
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
reject(error);
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
reader.onerror = reject;
|
|
1099
|
+
reader.readAsArrayBuffer(blob);
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
function splitFileIntoChunks(file, chunkSize) {
|
|
1103
|
+
const chunks = [];
|
|
1104
|
+
let start = 0;
|
|
1105
|
+
while (start < file.size) {
|
|
1106
|
+
const end = Math.min(start + chunkSize, file.size);
|
|
1107
|
+
chunks.push(file.slice(start, end));
|
|
1108
|
+
start = end;
|
|
1109
|
+
}
|
|
1110
|
+
return chunks;
|
|
1111
|
+
}
|
|
1112
|
+
function formatFileSize(bytes) {
|
|
1113
|
+
if (bytes === 0) return "0 B";
|
|
1114
|
+
const k = 1024;
|
|
1115
|
+
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
|
1116
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
1117
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
1118
|
+
}
|
|
1119
|
+
function getFileExtension(fileName) {
|
|
1120
|
+
const lastDot = fileName.lastIndexOf(".");
|
|
1121
|
+
return lastDot > 0 ? fileName.slice(lastDot + 1) : "";
|
|
1122
|
+
}
|
|
1123
|
+
function getFileNameWithoutExtension(fileName) {
|
|
1124
|
+
const lastDot = fileName.lastIndexOf(".");
|
|
1125
|
+
return lastDot > 0 ? fileName.slice(0, lastDot) : fileName;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// src/types/upload.ts
|
|
1129
|
+
var UploadStatus = /* @__PURE__ */ ((UploadStatus2) => {
|
|
1130
|
+
UploadStatus2["PENDING"] = "pending";
|
|
1131
|
+
UploadStatus2["UPLOADING"] = "uploading";
|
|
1132
|
+
UploadStatus2["PAUSED"] = "paused";
|
|
1133
|
+
UploadStatus2["COMPLETED"] = "completed";
|
|
1134
|
+
UploadStatus2["FAILED"] = "failed";
|
|
1135
|
+
UploadStatus2["CANCELLED"] = "cancelled";
|
|
1136
|
+
return UploadStatus2;
|
|
1137
|
+
})(UploadStatus || {});
|
|
1138
|
+
|
|
1139
|
+
// src/browser/upload/index.ts
|
|
1140
|
+
var ChunkUploader = class {
|
|
1141
|
+
constructor(file, options = {}) {
|
|
1142
|
+
this.taskId = null;
|
|
1143
|
+
this.chunks = [];
|
|
1144
|
+
this.uploadedChunks = /* @__PURE__ */ new Set();
|
|
1145
|
+
this.status = "pending" /* PENDING */;
|
|
1146
|
+
this.abortController = null;
|
|
1147
|
+
this.file = file;
|
|
1148
|
+
this.options = {
|
|
1149
|
+
chunkSize: options.chunkSize || 2 * 1024 * 1024,
|
|
1150
|
+
// 默认2MB
|
|
1151
|
+
concurrency: options.concurrency || 3,
|
|
1152
|
+
retryCount: options.retryCount || 3,
|
|
1153
|
+
retryDelay: options.retryDelay || 1e3,
|
|
1154
|
+
baseURL: options.baseURL || "",
|
|
1155
|
+
headers: options.headers || {},
|
|
1156
|
+
onProgress: options.onProgress || (() => {
|
|
1157
|
+
}),
|
|
1158
|
+
onComplete: options.onComplete || (() => {
|
|
1159
|
+
}),
|
|
1160
|
+
onError: options.onError || (() => {
|
|
1161
|
+
})
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* 初始化上传
|
|
1166
|
+
*/
|
|
1167
|
+
async initUpload() {
|
|
1168
|
+
const fileMd5 = await this.calculateFileMD5();
|
|
1169
|
+
const request2 = {
|
|
1170
|
+
fileName: this.file.name,
|
|
1171
|
+
fileSize: this.file.size,
|
|
1172
|
+
fileMd5,
|
|
1173
|
+
chunkSize: this.options.chunkSize
|
|
1174
|
+
};
|
|
1175
|
+
const response = await this.request("/api/files/common/init", {
|
|
1176
|
+
method: "POST",
|
|
1177
|
+
headers: {
|
|
1178
|
+
"Content-Type": "application/json",
|
|
1179
|
+
...this.options.headers
|
|
1180
|
+
},
|
|
1181
|
+
body: JSON.stringify(request2)
|
|
1182
|
+
});
|
|
1183
|
+
if (response.code !== 200) {
|
|
1184
|
+
throw new Error(response.message || "\u521D\u59CB\u5316\u4E0A\u4F20\u5931\u8D25");
|
|
1185
|
+
}
|
|
1186
|
+
return response.data;
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* 计算文件MD5
|
|
1190
|
+
*/
|
|
1191
|
+
async calculateFileMD5() {
|
|
1192
|
+
const chunks = splitFileIntoChunks(this.file, this.options.chunkSize);
|
|
1193
|
+
const md5Promises = chunks.map(
|
|
1194
|
+
(chunk2, index) => calculateBlobMD5(chunk2).then((md5) => ({ index, md5 }))
|
|
1195
|
+
);
|
|
1196
|
+
const md5Results = await Promise.all(md5Promises);
|
|
1197
|
+
return md5Results.map((r) => r.md5).join("");
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* 准备分片
|
|
1201
|
+
*/
|
|
1202
|
+
prepareChunks() {
|
|
1203
|
+
const blobChunks = splitFileIntoChunks(this.file, this.options.chunkSize);
|
|
1204
|
+
this.chunks = blobChunks.map((blob, index) => ({
|
|
1205
|
+
index,
|
|
1206
|
+
start: index * this.options.chunkSize,
|
|
1207
|
+
end: Math.min((index + 1) * this.options.chunkSize, this.file.size),
|
|
1208
|
+
blob
|
|
1209
|
+
}));
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* 获取已上传的分片列表(用于断点续传)
|
|
1213
|
+
*/
|
|
1214
|
+
async getUploadedChunks() {
|
|
1215
|
+
if (!this.taskId) return [];
|
|
1216
|
+
try {
|
|
1217
|
+
const response = await this.request(
|
|
1218
|
+
`/api/files/common/chunks/${this.taskId}`,
|
|
1219
|
+
{
|
|
1220
|
+
method: "GET",
|
|
1221
|
+
headers: this.options.headers
|
|
1222
|
+
}
|
|
1223
|
+
);
|
|
1224
|
+
if (response.code === 200) {
|
|
1225
|
+
return response.data || [];
|
|
1226
|
+
}
|
|
1227
|
+
} catch (error) {
|
|
1228
|
+
console.warn("\u83B7\u53D6\u5DF2\u4E0A\u4F20\u5206\u7247\u5931\u8D25:", error);
|
|
1229
|
+
}
|
|
1230
|
+
return [];
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* 上传单个分片
|
|
1234
|
+
*/
|
|
1235
|
+
async uploadChunk(chunkInfo, retryCount = 0) {
|
|
1236
|
+
if (!this.taskId) {
|
|
1237
|
+
throw new Error("\u4EFB\u52A1ID\u4E0D\u5B58\u5728");
|
|
1238
|
+
}
|
|
1239
|
+
if (this.uploadedChunks.has(chunkInfo.index)) {
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
try {
|
|
1243
|
+
const chunkMd5 = await calculateBlobMD5(chunkInfo.blob);
|
|
1244
|
+
const formData = new FormData();
|
|
1245
|
+
formData.append("file", chunkInfo.blob, this.file.name);
|
|
1246
|
+
const url = `/api/files/common/chunk?taskId=${this.taskId}&chunkIndex=${chunkInfo.index}&chunkMd5=${chunkMd5}`;
|
|
1247
|
+
const response = await this.request(url, {
|
|
1248
|
+
method: "POST",
|
|
1249
|
+
headers: this.options.headers,
|
|
1250
|
+
body: formData
|
|
1251
|
+
});
|
|
1252
|
+
if (response.code === 200 && response.data.success) {
|
|
1253
|
+
this.uploadedChunks.add(chunkInfo.index);
|
|
1254
|
+
await this.updateProgress();
|
|
1255
|
+
} else {
|
|
1256
|
+
throw new Error(response.message || "\u5206\u7247\u4E0A\u4F20\u5931\u8D25");
|
|
1257
|
+
}
|
|
1258
|
+
} catch (error) {
|
|
1259
|
+
if (retryCount < this.options.retryCount) {
|
|
1260
|
+
await this.delay(this.options.retryDelay);
|
|
1261
|
+
return this.uploadChunk(chunkInfo, retryCount + 1);
|
|
1262
|
+
}
|
|
1263
|
+
throw error;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* 并发上传分片
|
|
1268
|
+
*/
|
|
1269
|
+
async uploadChunksConcurrently() {
|
|
1270
|
+
const chunksToUpload = this.chunks.filter((chunk2) => !this.uploadedChunks.has(chunk2.index));
|
|
1271
|
+
if (chunksToUpload.length === 0) {
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
let currentIndex = 0;
|
|
1275
|
+
const uploadNext = async () => {
|
|
1276
|
+
while (this.status === "uploading" /* UPLOADING */) {
|
|
1277
|
+
const chunkIndex = currentIndex++;
|
|
1278
|
+
if (chunkIndex >= chunksToUpload.length) {
|
|
1279
|
+
break;
|
|
1280
|
+
}
|
|
1281
|
+
const chunk2 = chunksToUpload[chunkIndex];
|
|
1282
|
+
await this.uploadChunk(chunk2);
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
const tasks = Array(this.options.concurrency).fill(null).map(() => uploadNext());
|
|
1286
|
+
await Promise.all(tasks);
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* 更新上传进度(仅用于回调前端显示)
|
|
1290
|
+
*/
|
|
1291
|
+
async updateProgress() {
|
|
1292
|
+
if (!this.taskId) return;
|
|
1293
|
+
try {
|
|
1294
|
+
const response = await this.request(
|
|
1295
|
+
`/api/files/common/progress/${this.taskId}`,
|
|
1296
|
+
{
|
|
1297
|
+
method: "GET",
|
|
1298
|
+
headers: this.options.headers
|
|
1299
|
+
}
|
|
1300
|
+
);
|
|
1301
|
+
if (response.code === 200 && response.data) {
|
|
1302
|
+
this.options.onProgress(response.data);
|
|
1303
|
+
}
|
|
1304
|
+
} catch (error) {
|
|
1305
|
+
console.warn("\u83B7\u53D6\u4E0A\u4F20\u8FDB\u5EA6\u5931\u8D25:", error);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* 获取上传进度
|
|
1310
|
+
*/
|
|
1311
|
+
async getUploadProgress() {
|
|
1312
|
+
if (!this.taskId) {
|
|
1313
|
+
return null;
|
|
1314
|
+
}
|
|
1315
|
+
try {
|
|
1316
|
+
const response = await this.request(
|
|
1317
|
+
`/api/files/common/progress/${this.taskId}`,
|
|
1318
|
+
{
|
|
1319
|
+
method: "GET",
|
|
1320
|
+
headers: this.options.headers
|
|
1321
|
+
}
|
|
1322
|
+
);
|
|
1323
|
+
if (response.code === 200 && response.data) {
|
|
1324
|
+
console.log("[\u83B7\u53D6\u8FDB\u5EA6]", response.data);
|
|
1325
|
+
return response.data;
|
|
1326
|
+
}
|
|
1327
|
+
} catch (error) {
|
|
1328
|
+
console.warn("\u83B7\u53D6\u4E0A\u4F20\u8FDB\u5EA6\u5931\u8D25:", error);
|
|
1329
|
+
}
|
|
1330
|
+
return null;
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* 完成上传(调用后端合并分片接口)
|
|
1334
|
+
*/
|
|
1335
|
+
async completeUpload() {
|
|
1336
|
+
if (!this.taskId) {
|
|
1337
|
+
throw new Error("\u4EFB\u52A1ID\u4E0D\u5B58\u5728");
|
|
1338
|
+
}
|
|
1339
|
+
console.log("[\u5B8C\u6210\u4E0A\u4F20] \u8C03\u7528\u5B8C\u6210\u63A5\u53E3:", `/api/files/common/complete/${this.taskId}`);
|
|
1340
|
+
const response = await this.request(
|
|
1341
|
+
`/api/files/common/complete/${this.taskId}`,
|
|
1342
|
+
{
|
|
1343
|
+
method: "POST",
|
|
1344
|
+
headers: this.options.headers
|
|
1345
|
+
}
|
|
1346
|
+
);
|
|
1347
|
+
console.log("[\u5B8C\u6210\u4E0A\u4F20] \u63A5\u53E3\u54CD\u5E94:", response);
|
|
1348
|
+
if (response.code !== 200) {
|
|
1349
|
+
throw new Error(response.message || "\u5B8C\u6210\u4E0A\u4F20\u5931\u8D25");
|
|
1350
|
+
}
|
|
1351
|
+
return response.data;
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* 开始上传
|
|
1355
|
+
*/
|
|
1356
|
+
async upload() {
|
|
1357
|
+
try {
|
|
1358
|
+
this.status = "uploading" /* UPLOADING */;
|
|
1359
|
+
this.abortController = new AbortController();
|
|
1360
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 1. \u521D\u59CB\u5316\u4E0A\u4F20");
|
|
1361
|
+
const initResponse = await this.initUpload();
|
|
1362
|
+
this.taskId = initResponse.taskId;
|
|
1363
|
+
if (initResponse.instantUpload && initResponse.fileUrl) {
|
|
1364
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] \u79D2\u4F20\u6210\u529F");
|
|
1365
|
+
this.status = "completed" /* COMPLETED */;
|
|
1366
|
+
const result = {
|
|
1367
|
+
taskId: this.taskId,
|
|
1368
|
+
fileUrl: initResponse.fileUrl,
|
|
1369
|
+
fileName: this.file.name,
|
|
1370
|
+
fileSize: this.file.size,
|
|
1371
|
+
fileMd5: "",
|
|
1372
|
+
success: true,
|
|
1373
|
+
message: "\u6587\u4EF6\u5DF2\u5B58\u5728\uFF0C\u79D2\u4F20\u6210\u529F"
|
|
1374
|
+
};
|
|
1375
|
+
this.options.onComplete(result);
|
|
1376
|
+
return result;
|
|
1377
|
+
}
|
|
1378
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 2. \u51C6\u5907\u5206\u7247");
|
|
1379
|
+
this.prepareChunks();
|
|
1380
|
+
const totalChunks = this.chunks.length;
|
|
1381
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] \u5171 ${totalChunks} \u4E2A\u5206\u7247`);
|
|
1382
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 3. \u68C0\u67E5\u5DF2\u4E0A\u4F20\u5206\u7247");
|
|
1383
|
+
const existingChunks = await this.getUploadedChunks();
|
|
1384
|
+
existingChunks.forEach((index) => this.uploadedChunks.add(index));
|
|
1385
|
+
console.log(
|
|
1386
|
+
`[\u4E0A\u4F20\u6D41\u7A0B] \u5DF2\u4E0A\u4F20 ${existingChunks.length} \u4E2A\u5206\u7247\uFF0C\u8FD8\u9700\u4E0A\u4F20 ${totalChunks - existingChunks.length} \u4E2A`
|
|
1387
|
+
);
|
|
1388
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 4. \u5F00\u59CB\u4E0A\u4F20\u5206\u7247");
|
|
1389
|
+
await this.uploadChunksConcurrently();
|
|
1390
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 5. \u6240\u6709\u5206\u7247\u4E0A\u4F20\u5B8C\u6210");
|
|
1391
|
+
const uploadedCount = this.uploadedChunks.size;
|
|
1392
|
+
if (uploadedCount < totalChunks) {
|
|
1393
|
+
throw new Error(`\u4E0A\u4F20\u9A8C\u8BC1\u5931\u8D25\uFF1A\u5DF2\u4E0A\u4F20 ${uploadedCount}/${totalChunks} \u4E2A\u5206\u7247`);
|
|
1394
|
+
}
|
|
1395
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] 6. \u9A8C\u8BC1\u901A\u8FC7\uFF1A${uploadedCount}/${totalChunks} \u4E2A\u5206\u7247\u5DF2\u4E0A\u4F20`);
|
|
1396
|
+
const localPercentage = Math.round(uploadedCount / totalChunks * 100);
|
|
1397
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] 7. \u672C\u5730\u8FDB\u5EA6\uFF1A${localPercentage}%`);
|
|
1398
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 8. \u83B7\u53D6\u670D\u52A1\u7AEF\u8FDB\u5EA6");
|
|
1399
|
+
const serverProgress = await this.getUploadProgress();
|
|
1400
|
+
const serverPercentage = serverProgress?.percentage ?? 0;
|
|
1401
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] \u670D\u52A1\u7AEF\u8FDB\u5EA6\uFF1A${serverPercentage}%`);
|
|
1402
|
+
const finalPercentage = serverProgress?.percentage ?? localPercentage;
|
|
1403
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] 9. \u6700\u7EC8\u8FDB\u5EA6\uFF1A${finalPercentage}%`);
|
|
1404
|
+
if (finalPercentage >= 100) {
|
|
1405
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 10. \u2705 \u8FDB\u5EA6\u8FBE\u5230100%\uFF0C\u8C03\u7528\u5B8C\u6210\u63A5\u53E3");
|
|
1406
|
+
const result = await this.completeUpload();
|
|
1407
|
+
this.status = "completed" /* COMPLETED */;
|
|
1408
|
+
this.options.onComplete(result);
|
|
1409
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] \u2705 \u4E0A\u4F20\u5B8C\u6210");
|
|
1410
|
+
return result;
|
|
1411
|
+
} else {
|
|
1412
|
+
console.error(`[\u4E0A\u4F20\u6D41\u7A0B] \u274C \u8FDB\u5EA6\u4E0D\u8DB3100%\uFF1A${finalPercentage}%`);
|
|
1413
|
+
throw new Error(`\u4E0A\u4F20\u672A\u5B8C\u6210\uFF1A\u5F53\u524D\u8FDB\u5EA6 ${finalPercentage.toFixed(2)}%`);
|
|
1414
|
+
}
|
|
1415
|
+
} catch (error) {
|
|
1416
|
+
this.status = "failed" /* FAILED */;
|
|
1417
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1418
|
+
this.options.onError(err);
|
|
1419
|
+
console.error("[\u4E0A\u4F20\u6D41\u7A0B] \u274C \u4E0A\u4F20\u5931\u8D25:", err);
|
|
1420
|
+
throw err;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* 暂停上传
|
|
1425
|
+
*/
|
|
1426
|
+
pause() {
|
|
1427
|
+
if (this.status === "uploading" /* UPLOADING */) {
|
|
1428
|
+
this.status = "paused" /* PAUSED */;
|
|
1429
|
+
if (this.abortController) {
|
|
1430
|
+
this.abortController.abort();
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* 恢复上传
|
|
1436
|
+
*/
|
|
1437
|
+
async resume() {
|
|
1438
|
+
if (this.status === "paused" /* PAUSED */) {
|
|
1439
|
+
return this.upload();
|
|
1440
|
+
}
|
|
1441
|
+
throw new Error("\u5F53\u524D\u72B6\u6001\u65E0\u6CD5\u6062\u590D\u4E0A\u4F20");
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* 取消上传
|
|
1445
|
+
*/
|
|
1446
|
+
async cancel() {
|
|
1447
|
+
if (this.taskId && this.status === "uploading" /* UPLOADING */) {
|
|
1448
|
+
try {
|
|
1449
|
+
await this.request(`/api/files/common/cancel/${this.taskId}`, {
|
|
1450
|
+
method: "POST",
|
|
1451
|
+
headers: this.options.headers
|
|
1452
|
+
});
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
console.warn("\u53D6\u6D88\u4E0A\u4F20\u5931\u8D25:", error);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
this.status = "cancelled" /* CANCELLED */;
|
|
1458
|
+
if (this.abortController) {
|
|
1459
|
+
this.abortController.abort();
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* 获取当前状态
|
|
1464
|
+
*/
|
|
1465
|
+
getStatus() {
|
|
1466
|
+
return this.status;
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* 获取任务ID
|
|
1470
|
+
*/
|
|
1471
|
+
getTaskId() {
|
|
1472
|
+
return this.taskId;
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* HTTP请求封装
|
|
1476
|
+
*/
|
|
1477
|
+
async request(url, options = {}) {
|
|
1478
|
+
const fullUrl = `${this.options.baseURL}${url}`;
|
|
1479
|
+
const signal = this.abortController?.signal;
|
|
1480
|
+
const response = await fetch(fullUrl, {
|
|
1481
|
+
...options,
|
|
1482
|
+
signal
|
|
1483
|
+
});
|
|
1484
|
+
if (!response.ok) {
|
|
1485
|
+
throw new Error(`HTTP\u9519\u8BEF: ${response.status} ${response.statusText}`);
|
|
1486
|
+
}
|
|
1487
|
+
return response.json();
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* 延迟函数
|
|
1491
|
+
*/
|
|
1492
|
+
delay(ms) {
|
|
1493
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1494
|
+
}
|
|
1495
|
+
};
|
|
1496
|
+
function createUploader(file, options) {
|
|
1497
|
+
return new ChunkUploader(file, options);
|
|
1498
|
+
}
|
|
1499
|
+
async function uploadFile(file, options) {
|
|
1500
|
+
const uploader = createUploader(file, options);
|
|
1501
|
+
return uploader.upload();
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
// src/helper/crypto/index.ts
|
|
1505
|
+
async function sha256(data) {
|
|
1506
|
+
const buffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
1507
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
|
|
1508
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1509
|
+
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1510
|
+
}
|
|
1511
|
+
function base64Encode(data) {
|
|
1512
|
+
if (typeof data === "string") {
|
|
1513
|
+
return btoa(unescape(encodeURIComponent(data)));
|
|
1514
|
+
}
|
|
1515
|
+
const bytes = new Uint8Array(data);
|
|
1516
|
+
let binary = "";
|
|
1517
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1518
|
+
binary += String.fromCharCode(bytes[i]);
|
|
1519
|
+
}
|
|
1520
|
+
return btoa(binary);
|
|
1521
|
+
}
|
|
1522
|
+
function base64Decode(data) {
|
|
1523
|
+
try {
|
|
1524
|
+
return decodeURIComponent(escape(atob(data)));
|
|
1525
|
+
} catch {
|
|
1526
|
+
throw new Error("Invalid Base64 string");
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
function generateUUID() {
|
|
1530
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
1531
|
+
return crypto.randomUUID();
|
|
1532
|
+
}
|
|
1533
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
1534
|
+
const r = Math.random() * 16 | 0;
|
|
1535
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
1536
|
+
return v.toString(16);
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
function generateRandomString(length, charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") {
|
|
1540
|
+
let result = "";
|
|
1541
|
+
for (let i = 0; i < length; i++) {
|
|
1542
|
+
result += charset.charAt(Math.floor(Math.random() * charset.length));
|
|
1543
|
+
}
|
|
1544
|
+
return result;
|
|
1545
|
+
}
|
|
1546
|
+
function hash(data) {
|
|
1547
|
+
let hashValue = 0;
|
|
1548
|
+
for (let i = 0; i < data.length; i++) {
|
|
1549
|
+
const char = data.charCodeAt(i);
|
|
1550
|
+
hashValue = (hashValue << 5) - hashValue + char;
|
|
1551
|
+
hashValue = hashValue & hashValue;
|
|
1552
|
+
}
|
|
1553
|
+
return Math.abs(hashValue);
|
|
1554
|
+
}
|
|
1555
|
+
async function generateRSAKeyPair(modulusLength = 2048) {
|
|
1556
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1557
|
+
throw new Error("Web Crypto API is not available");
|
|
1558
|
+
}
|
|
1559
|
+
const keyPair = await crypto.subtle.generateKey(
|
|
1560
|
+
{
|
|
1561
|
+
name: "RSA-OAEP",
|
|
1562
|
+
modulusLength,
|
|
1563
|
+
publicExponent: new Uint8Array([1, 0, 1]),
|
|
1564
|
+
hash: "SHA-256"
|
|
1565
|
+
},
|
|
1566
|
+
true,
|
|
1567
|
+
["encrypt", "decrypt"]
|
|
1568
|
+
);
|
|
1569
|
+
return {
|
|
1570
|
+
publicKey: keyPair.publicKey,
|
|
1571
|
+
privateKey: keyPair.privateKey
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
async function rsaEncrypt(data, publicKey) {
|
|
1575
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1576
|
+
throw new Error("Web Crypto API is not available");
|
|
1577
|
+
}
|
|
1578
|
+
const encoder = new TextEncoder();
|
|
1579
|
+
const dataBuffer = encoder.encode(data);
|
|
1580
|
+
const maxChunkSize = 245;
|
|
1581
|
+
const chunks = [];
|
|
1582
|
+
for (let i = 0; i < dataBuffer.length; i += maxChunkSize) {
|
|
1583
|
+
const chunk2 = dataBuffer.slice(i, i + maxChunkSize);
|
|
1584
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
1585
|
+
{
|
|
1586
|
+
name: "RSA-OAEP"
|
|
1587
|
+
},
|
|
1588
|
+
publicKey,
|
|
1589
|
+
chunk2
|
|
1590
|
+
);
|
|
1591
|
+
chunks.push(encrypted);
|
|
1592
|
+
}
|
|
1593
|
+
const totalLength = chunks.reduce((sum, chunk2) => sum + chunk2.byteLength, 0);
|
|
1594
|
+
const merged = new Uint8Array(totalLength);
|
|
1595
|
+
let offset = 0;
|
|
1596
|
+
for (const chunk2 of chunks) {
|
|
1597
|
+
merged.set(new Uint8Array(chunk2), offset);
|
|
1598
|
+
offset += chunk2.byteLength;
|
|
1599
|
+
}
|
|
1600
|
+
return base64Encode(merged.buffer);
|
|
1601
|
+
}
|
|
1602
|
+
async function rsaDecrypt(encryptedData, privateKey) {
|
|
1603
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1604
|
+
throw new Error("Web Crypto API is not available");
|
|
1605
|
+
}
|
|
1606
|
+
const binaryString = atob(encryptedData);
|
|
1607
|
+
const encryptedArray = new Uint8Array(binaryString.length);
|
|
1608
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
1609
|
+
encryptedArray[i] = binaryString.charCodeAt(i);
|
|
1610
|
+
}
|
|
1611
|
+
const chunkSize = 256;
|
|
1612
|
+
const chunks = [];
|
|
1613
|
+
for (let i = 0; i < encryptedArray.length; i += chunkSize) {
|
|
1614
|
+
const chunk2 = encryptedArray.slice(i, i + chunkSize);
|
|
1615
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
1616
|
+
{
|
|
1617
|
+
name: "RSA-OAEP"
|
|
1618
|
+
},
|
|
1619
|
+
privateKey,
|
|
1620
|
+
chunk2
|
|
1621
|
+
);
|
|
1622
|
+
const decoder = new TextDecoder();
|
|
1623
|
+
chunks.push(decoder.decode(decrypted));
|
|
1624
|
+
}
|
|
1625
|
+
return chunks.join("");
|
|
1626
|
+
}
|
|
1627
|
+
async function exportPublicKey(publicKey) {
|
|
1628
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1629
|
+
throw new Error("Web Crypto API is not available");
|
|
1630
|
+
}
|
|
1631
|
+
const exported = await crypto.subtle.exportKey("spki", publicKey);
|
|
1632
|
+
return base64Encode(exported);
|
|
1633
|
+
}
|
|
1634
|
+
async function exportPrivateKey(privateKey) {
|
|
1635
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1636
|
+
throw new Error("Web Crypto API is not available");
|
|
1637
|
+
}
|
|
1638
|
+
const exported = await crypto.subtle.exportKey("pkcs8", privateKey);
|
|
1639
|
+
return base64Encode(exported);
|
|
1640
|
+
}
|
|
1641
|
+
async function importPublicKey(keyData) {
|
|
1642
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1643
|
+
throw new Error("Web Crypto API is not available");
|
|
1644
|
+
}
|
|
1645
|
+
const keyBuffer = base64Decode(keyData);
|
|
1646
|
+
const keyArray = new Uint8Array(keyBuffer.split("").map((char) => char.charCodeAt(0)));
|
|
1647
|
+
return crypto.subtle.importKey(
|
|
1648
|
+
"spki",
|
|
1649
|
+
keyArray.buffer,
|
|
1650
|
+
{
|
|
1651
|
+
name: "RSA-OAEP",
|
|
1652
|
+
hash: "SHA-256"
|
|
1653
|
+
},
|
|
1654
|
+
true,
|
|
1655
|
+
["encrypt"]
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
async function importPrivateKey(keyData) {
|
|
1659
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1660
|
+
throw new Error("Web Crypto API is not available");
|
|
1661
|
+
}
|
|
1662
|
+
const keyBuffer = base64Decode(keyData);
|
|
1663
|
+
const keyArray = new Uint8Array(keyBuffer.split("").map((char) => char.charCodeAt(0)));
|
|
1664
|
+
return crypto.subtle.importKey(
|
|
1665
|
+
"pkcs8",
|
|
1666
|
+
keyArray.buffer,
|
|
1667
|
+
{
|
|
1668
|
+
name: "RSA-OAEP",
|
|
1669
|
+
hash: "SHA-256"
|
|
1670
|
+
},
|
|
1671
|
+
true,
|
|
1672
|
+
["decrypt"]
|
|
1673
|
+
);
|
|
1674
|
+
}
|
|
1675
|
+
async function generateHMACKey() {
|
|
1676
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1677
|
+
throw new Error("Web Crypto API is not available");
|
|
1678
|
+
}
|
|
1679
|
+
return crypto.subtle.generateKey(
|
|
1680
|
+
{
|
|
1681
|
+
name: "HMAC",
|
|
1682
|
+
hash: "SHA-256"
|
|
1683
|
+
},
|
|
1684
|
+
true,
|
|
1685
|
+
["sign", "verify"]
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
async function computeHMAC(data, key) {
|
|
1689
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1690
|
+
throw new Error("Web Crypto API is not available");
|
|
1691
|
+
}
|
|
1692
|
+
const buffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
1693
|
+
const signature = await crypto.subtle.sign("HMAC", key, buffer);
|
|
1694
|
+
return base64Encode(signature);
|
|
1695
|
+
}
|
|
1696
|
+
async function verifyHMAC(data, signature, key) {
|
|
1697
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1698
|
+
throw new Error("Web Crypto API is not available");
|
|
1699
|
+
}
|
|
1700
|
+
try {
|
|
1701
|
+
const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
1702
|
+
const signatureBuffer = new Uint8Array(
|
|
1703
|
+
atob(signature).split("").map((char) => char.charCodeAt(0))
|
|
1704
|
+
);
|
|
1705
|
+
return await crypto.subtle.verify("HMAC", key, signatureBuffer, dataBuffer);
|
|
1706
|
+
} catch {
|
|
1707
|
+
return false;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
async function deriveKeyFromPassword(password, salt, iterations = 1e5, keyLength = 256) {
|
|
1711
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1712
|
+
throw new Error("Web Crypto API is not available");
|
|
1713
|
+
}
|
|
1714
|
+
const saltBuffer = typeof salt === "string" ? new TextEncoder().encode(salt) : salt;
|
|
1715
|
+
const passwordKey = await crypto.subtle.importKey(
|
|
1716
|
+
"raw",
|
|
1717
|
+
new TextEncoder().encode(password),
|
|
1718
|
+
"PBKDF2",
|
|
1719
|
+
false,
|
|
1720
|
+
["deriveBits", "deriveKey"]
|
|
1721
|
+
);
|
|
1722
|
+
return crypto.subtle.deriveKey(
|
|
1723
|
+
{
|
|
1724
|
+
name: "PBKDF2",
|
|
1725
|
+
salt: saltBuffer,
|
|
1726
|
+
iterations,
|
|
1727
|
+
hash: "SHA-256"
|
|
1728
|
+
},
|
|
1729
|
+
passwordKey,
|
|
1730
|
+
{
|
|
1731
|
+
name: "AES-GCM",
|
|
1732
|
+
length: keyLength
|
|
1733
|
+
},
|
|
1734
|
+
false,
|
|
1735
|
+
["encrypt", "decrypt"]
|
|
1736
|
+
);
|
|
1737
|
+
}
|
|
1738
|
+
async function aesGCMEncrypt(data, key) {
|
|
1739
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1740
|
+
throw new Error("Web Crypto API is not available");
|
|
1741
|
+
}
|
|
1742
|
+
const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
1743
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
1744
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
1745
|
+
{
|
|
1746
|
+
name: "AES-GCM",
|
|
1747
|
+
iv
|
|
1748
|
+
},
|
|
1749
|
+
key,
|
|
1750
|
+
dataBuffer
|
|
1751
|
+
);
|
|
1752
|
+
return {
|
|
1753
|
+
encrypted: base64Encode(encrypted),
|
|
1754
|
+
iv: base64Encode(iv.buffer)
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
async function aesGCMDecrypt(encryptedData, iv, key) {
|
|
1758
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1759
|
+
throw new Error("Web Crypto API is not available");
|
|
1760
|
+
}
|
|
1761
|
+
const encryptedBuffer = new Uint8Array(
|
|
1762
|
+
atob(encryptedData).split("").map((char) => char.charCodeAt(0))
|
|
1763
|
+
);
|
|
1764
|
+
const ivBuffer = new Uint8Array(
|
|
1765
|
+
atob(iv).split("").map((char) => char.charCodeAt(0))
|
|
1766
|
+
);
|
|
1767
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
1768
|
+
{
|
|
1769
|
+
name: "AES-GCM",
|
|
1770
|
+
iv: ivBuffer
|
|
1771
|
+
},
|
|
1772
|
+
key,
|
|
1773
|
+
encryptedBuffer
|
|
1774
|
+
);
|
|
1775
|
+
return new TextDecoder().decode(decrypted);
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
// src/browser/SecureStorage/index.ts
|
|
1779
|
+
var globalKeyPair = null;
|
|
1780
|
+
var globalHMACKey = null;
|
|
1781
|
+
var keyPairInitialized = false;
|
|
1782
|
+
var initOptions = {
|
|
1783
|
+
autoGenerateKeys: true,
|
|
1784
|
+
persistKeys: false,
|
|
1785
|
+
keyStorageKey: void 0,
|
|
1786
|
+
// 将自动生成随机键名
|
|
1787
|
+
keyEncryptionPassword: void 0,
|
|
1788
|
+
pbkdf2Iterations: 1e5,
|
|
1789
|
+
keyModulusLength: 2048,
|
|
1790
|
+
enableHMAC: true,
|
|
1791
|
+
enableTimestampValidation: true,
|
|
1792
|
+
timestampMaxAge: 7 * 24 * 60 * 60 * 1e3,
|
|
1793
|
+
// 7 天
|
|
1794
|
+
isProduction: false
|
|
1795
|
+
};
|
|
1796
|
+
var actualKeyStorageKey = null;
|
|
1797
|
+
var keyUsageCount = 0;
|
|
1798
|
+
var initializationPromise = null;
|
|
1799
|
+
function generateKeyStorageKey() {
|
|
1800
|
+
return `_sk_${generateRandomString(32)}_${Date.now()}`;
|
|
1801
|
+
}
|
|
1802
|
+
async function initializeStorageKeys(options = {}) {
|
|
1803
|
+
if (options.forceReinitialize) {
|
|
1804
|
+
globalKeyPair = null;
|
|
1805
|
+
globalHMACKey = null;
|
|
1806
|
+
keyPairInitialized = false;
|
|
1807
|
+
actualKeyStorageKey = null;
|
|
1808
|
+
keyUsageCount = 0;
|
|
1809
|
+
initializationPromise = null;
|
|
1810
|
+
} else if (keyPairInitialized && globalKeyPair) {
|
|
1811
|
+
initOptions = { ...initOptions, ...options };
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
initOptions = { ...initOptions, ...options };
|
|
1815
|
+
if (initOptions.keyStorageKey) {
|
|
1816
|
+
actualKeyStorageKey = initOptions.keyStorageKey;
|
|
1817
|
+
} else {
|
|
1818
|
+
actualKeyStorageKey = generateKeyStorageKey();
|
|
1819
|
+
}
|
|
1820
|
+
if (initOptions.persistKeys && typeof window !== "undefined" && window.localStorage) {
|
|
1821
|
+
try {
|
|
1822
|
+
const storedKeys = window.localStorage.getItem(actualKeyStorageKey);
|
|
1823
|
+
if (storedKeys) {
|
|
1824
|
+
const keyData = JSON.parse(storedKeys);
|
|
1825
|
+
if (keyData.encrypted && keyData.privateKeyEncrypted && keyData.salt) {
|
|
1826
|
+
if (!initOptions.keyEncryptionPassword) {
|
|
1827
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
1828
|
+
console.error("Encrypted keys found but no password provided");
|
|
1829
|
+
}
|
|
1830
|
+
throw new Error("Password required to decrypt stored keys");
|
|
1831
|
+
}
|
|
1832
|
+
const saltArray = new Uint8Array(
|
|
1833
|
+
atob(keyData.salt).split("").map((char) => char.charCodeAt(0))
|
|
1834
|
+
);
|
|
1835
|
+
const iterations = keyData.pbkdf2Iterations || initOptions.pbkdf2Iterations;
|
|
1836
|
+
const derivedKey = await deriveKeyFromPassword(
|
|
1837
|
+
initOptions.keyEncryptionPassword,
|
|
1838
|
+
saltArray.buffer,
|
|
1839
|
+
iterations
|
|
1840
|
+
);
|
|
1841
|
+
const decryptedPrivateKey = await aesGCMDecrypt(
|
|
1842
|
+
keyData.privateKeyEncrypted,
|
|
1843
|
+
keyData.iv,
|
|
1844
|
+
derivedKey
|
|
1845
|
+
);
|
|
1846
|
+
globalKeyPair = {
|
|
1847
|
+
publicKey: await importPublicKey(keyData.publicKey),
|
|
1848
|
+
privateKey: await importPrivateKey(decryptedPrivateKey)
|
|
1849
|
+
};
|
|
1850
|
+
} else if (keyData.publicKey && keyData.privateKey) {
|
|
1851
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
1852
|
+
console.warn(
|
|
1853
|
+
"\u26A0\uFE0F SECURITY WARNING: Loading unencrypted keys from storage. Consider re-initializing with password encryption."
|
|
1854
|
+
);
|
|
1855
|
+
}
|
|
1856
|
+
globalKeyPair = {
|
|
1857
|
+
publicKey: await importPublicKey(keyData.publicKey),
|
|
1858
|
+
privateKey: await importPrivateKey(keyData.privateKey)
|
|
1859
|
+
};
|
|
1860
|
+
}
|
|
1861
|
+
if (globalKeyPair) {
|
|
1862
|
+
keyPairInitialized = true;
|
|
1863
|
+
if (initOptions.enableHMAC && !globalHMACKey) {
|
|
1864
|
+
globalHMACKey = await generateHMACKey();
|
|
1865
|
+
}
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
} catch (error) {
|
|
1870
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
1871
|
+
console.warn("Failed to load persisted keys, will generate new ones");
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
if (initOptions.autoGenerateKeys && !globalKeyPair) {
|
|
1876
|
+
try {
|
|
1877
|
+
globalKeyPair = await generateRSAKeyPair(initOptions.keyModulusLength);
|
|
1878
|
+
if (initOptions.enableHMAC && !globalHMACKey) {
|
|
1879
|
+
globalHMACKey = await generateHMACKey();
|
|
1880
|
+
}
|
|
1881
|
+
keyPairInitialized = true;
|
|
1882
|
+
keyUsageCount = 0;
|
|
1883
|
+
if (initOptions.persistKeys && typeof window !== "undefined" && window.localStorage) {
|
|
1884
|
+
try {
|
|
1885
|
+
const publicKeyStr = await exportPublicKey(globalKeyPair.publicKey);
|
|
1886
|
+
if (initOptions.keyEncryptionPassword) {
|
|
1887
|
+
const privateKeyStr = await exportPrivateKey(globalKeyPair.privateKey);
|
|
1888
|
+
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
1889
|
+
const derivedKey = await deriveKeyFromPassword(
|
|
1890
|
+
initOptions.keyEncryptionPassword,
|
|
1891
|
+
salt.buffer,
|
|
1892
|
+
initOptions.pbkdf2Iterations
|
|
1893
|
+
);
|
|
1894
|
+
const { encrypted, iv } = await aesGCMEncrypt(privateKeyStr, derivedKey);
|
|
1895
|
+
const keyData = {
|
|
1896
|
+
encrypted: true,
|
|
1897
|
+
publicKey: publicKeyStr,
|
|
1898
|
+
privateKeyEncrypted: encrypted,
|
|
1899
|
+
iv,
|
|
1900
|
+
salt: base64Encode(salt.buffer),
|
|
1901
|
+
pbkdf2Iterations: initOptions.pbkdf2Iterations
|
|
1902
|
+
};
|
|
1903
|
+
window.localStorage.setItem(actualKeyStorageKey, JSON.stringify(keyData));
|
|
1904
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.info) {
|
|
1905
|
+
console.info("\u2705 Keys encrypted and stored securely");
|
|
1906
|
+
}
|
|
1907
|
+
} else {
|
|
1908
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
1909
|
+
console.warn(
|
|
1910
|
+
"\u26A0\uFE0F SECURITY WARNING: Storing private keys without encryption! Private keys will be stored in plain text (Base64 encoded). Provide keyEncryptionPassword for secure storage."
|
|
1911
|
+
);
|
|
1912
|
+
}
|
|
1913
|
+
const keyData = {
|
|
1914
|
+
publicKey: publicKeyStr,
|
|
1915
|
+
privateKey: await exportPrivateKey(globalKeyPair.privateKey)
|
|
1916
|
+
};
|
|
1917
|
+
window.localStorage.setItem(actualKeyStorageKey, JSON.stringify(keyData));
|
|
1918
|
+
}
|
|
1919
|
+
} catch (error) {
|
|
1920
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
1921
|
+
console.error("Failed to persist keys");
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
} catch (error) {
|
|
1926
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
1927
|
+
console.error("Failed to generate storage keys");
|
|
1928
|
+
}
|
|
1929
|
+
throw error;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
if (initOptions.enableHMAC && !globalHMACKey) {
|
|
1933
|
+
globalHMACKey = await generateHMACKey();
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
function setStorageKeyPair(keyPair) {
|
|
1937
|
+
globalKeyPair = keyPair;
|
|
1938
|
+
keyPairInitialized = true;
|
|
1939
|
+
}
|
|
1940
|
+
function getStorageKeyPair() {
|
|
1941
|
+
return globalKeyPair;
|
|
1942
|
+
}
|
|
1943
|
+
function clearPersistedKeys() {
|
|
1944
|
+
if (typeof window !== "undefined" && window.localStorage && actualKeyStorageKey) {
|
|
1945
|
+
try {
|
|
1946
|
+
window.localStorage.removeItem(actualKeyStorageKey);
|
|
1947
|
+
actualKeyStorageKey = null;
|
|
1948
|
+
keyPairInitialized = false;
|
|
1949
|
+
globalKeyPair = null;
|
|
1950
|
+
globalHMACKey = null;
|
|
1951
|
+
keyUsageCount = 0;
|
|
1952
|
+
} catch (error) {
|
|
1953
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
1954
|
+
console.error("Failed to clear persisted keys");
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
function getKeyUsageCount() {
|
|
1960
|
+
return keyUsageCount;
|
|
1961
|
+
}
|
|
1962
|
+
function resetKeyUsageCount() {
|
|
1963
|
+
keyUsageCount = 0;
|
|
1964
|
+
}
|
|
1965
|
+
async function ensureKeyPair() {
|
|
1966
|
+
if (globalKeyPair) {
|
|
1967
|
+
keyUsageCount++;
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
if (!initOptions.autoGenerateKeys) {
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
if (initializationPromise) {
|
|
1974
|
+
await initializationPromise;
|
|
1975
|
+
if (globalKeyPair) {
|
|
1976
|
+
keyUsageCount++;
|
|
1977
|
+
}
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
initializationPromise = initializeStorageKeys();
|
|
1981
|
+
try {
|
|
1982
|
+
await initializationPromise;
|
|
1983
|
+
} finally {
|
|
1984
|
+
initializationPromise = null;
|
|
1985
|
+
}
|
|
1986
|
+
if (globalKeyPair) {
|
|
1987
|
+
keyUsageCount++;
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
function safeParseJSON(jsonStr, expectedType) {
|
|
1991
|
+
try {
|
|
1992
|
+
const parsed = JSON.parse(jsonStr);
|
|
1993
|
+
if (expectedType === "object" && (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))) {
|
|
1994
|
+
throw new Error("Expected object but got different type");
|
|
1995
|
+
}
|
|
1996
|
+
if (expectedType === "array" && !Array.isArray(parsed)) {
|
|
1997
|
+
throw new Error("Expected array but got different type");
|
|
1998
|
+
}
|
|
1999
|
+
return parsed;
|
|
2000
|
+
} catch (error) {
|
|
2001
|
+
if (error instanceof SyntaxError) {
|
|
2002
|
+
throw new Error(`Invalid JSON format: ${error.message}`);
|
|
2003
|
+
}
|
|
2004
|
+
throw error;
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
function validateTimestamp(timestamp, maxClockSkew = 5 * 60 * 1e3) {
|
|
2008
|
+
if (!initOptions.enableTimestampValidation || !timestamp) {
|
|
2009
|
+
return true;
|
|
2010
|
+
}
|
|
2011
|
+
if (initOptions.timestampMaxAge === 0) {
|
|
2012
|
+
return true;
|
|
2013
|
+
}
|
|
2014
|
+
const now = Date.now();
|
|
2015
|
+
const age = now - timestamp;
|
|
2016
|
+
const maxAge = initOptions.timestampMaxAge || 7 * 24 * 60 * 60 * 1e3;
|
|
2017
|
+
if (age < -maxClockSkew) {
|
|
2018
|
+
return false;
|
|
2019
|
+
}
|
|
2020
|
+
return age >= -maxClockSkew && age <= maxAge;
|
|
2021
|
+
}
|
|
2022
|
+
async function encryptValue(value, publicKey, hmacKey) {
|
|
2023
|
+
const encryptedData = await rsaEncrypt(value, publicKey);
|
|
2024
|
+
if (hmacKey) {
|
|
2025
|
+
const hmac = await computeHMAC(encryptedData, hmacKey);
|
|
2026
|
+
const item = {
|
|
2027
|
+
data: encryptedData,
|
|
2028
|
+
hmac,
|
|
2029
|
+
encrypted: true,
|
|
2030
|
+
timestamp: Date.now()
|
|
2031
|
+
};
|
|
2032
|
+
return JSON.stringify(item);
|
|
2033
|
+
}
|
|
2034
|
+
return encryptedData;
|
|
2035
|
+
}
|
|
2036
|
+
async function handleDecryptError(error, encryptedStr, key) {
|
|
2037
|
+
if (error instanceof Error && error.message.includes("HMAC verification failed")) {
|
|
2038
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
2039
|
+
console.error(
|
|
2040
|
+
"\u26A0\uFE0F SECURITY ALERT: Data integrity check failed! Data may have been tampered with."
|
|
2041
|
+
);
|
|
2042
|
+
}
|
|
2043
|
+
throw error;
|
|
2044
|
+
}
|
|
2045
|
+
if (error instanceof Error && error.message.includes("Data timestamp validation failed")) {
|
|
2046
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
2047
|
+
console.warn("\u26A0\uFE0F SECURITY WARNING: Data timestamp validation failed");
|
|
2048
|
+
}
|
|
2049
|
+
throw error;
|
|
2050
|
+
}
|
|
2051
|
+
try {
|
|
2052
|
+
const decryptedStr = base64Decode(encryptedStr);
|
|
2053
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
2054
|
+
console.warn(
|
|
2055
|
+
`\u26A0\uFE0F SECURITY WARNING: Reading unencrypted data for key "${key}". This may be a security risk if sensitive data was stored without encryption.`
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2058
|
+
return decryptedStr;
|
|
2059
|
+
} catch {
|
|
2060
|
+
throw error;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
function handleNoKeyDecode(encryptedStr, key) {
|
|
2064
|
+
try {
|
|
2065
|
+
const decryptedStr = base64Decode(encryptedStr);
|
|
2066
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
2067
|
+
console.warn(
|
|
2068
|
+
`\u26A0\uFE0F SECURITY WARNING: Reading unencrypted data for key "${key}" without encryption keys.`
|
|
2069
|
+
);
|
|
2070
|
+
}
|
|
2071
|
+
return decryptedStr;
|
|
2072
|
+
} catch (error) {
|
|
2073
|
+
throw new Error(`Failed to decode storage value for key "${key}"`);
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
async function decryptValue(encryptedValue, privateKey, hmacKey) {
|
|
2077
|
+
try {
|
|
2078
|
+
const item = JSON.parse(encryptedValue);
|
|
2079
|
+
if (item.encrypted && item.data && item.hmac) {
|
|
2080
|
+
if (hmacKey) {
|
|
2081
|
+
const isValid = await verifyHMAC(item.data, item.hmac, hmacKey);
|
|
2082
|
+
if (!isValid) {
|
|
2083
|
+
throw new Error("HMAC verification failed: data may have been tampered with");
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
if (item.timestamp && !validateTimestamp(item.timestamp)) {
|
|
2087
|
+
throw new Error("Data timestamp validation failed: data may be expired or replayed");
|
|
2088
|
+
}
|
|
2089
|
+
return await rsaDecrypt(item.data, privateKey);
|
|
2090
|
+
}
|
|
2091
|
+
return await rsaDecrypt(encryptedValue, privateKey);
|
|
2092
|
+
} catch (error) {
|
|
2093
|
+
if (error instanceof Error && error.message.includes("HMAC verification failed")) {
|
|
2094
|
+
throw error;
|
|
2095
|
+
}
|
|
2096
|
+
throw new Error("Failed to decrypt storage value: invalid format or corrupted data");
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
var localStorage = {
|
|
2100
|
+
/**
|
|
2101
|
+
* 设置值
|
|
2102
|
+
* @param key - 键
|
|
2103
|
+
* @param value - 值
|
|
2104
|
+
* @param options - 选项(过期时间、密钥等)
|
|
2105
|
+
* @returns Promise<void>
|
|
2106
|
+
*/
|
|
2107
|
+
async set(key, value, options = {}) {
|
|
2108
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
2109
|
+
throw new Error("localStorage is not available");
|
|
2110
|
+
}
|
|
2111
|
+
const { expiry, publicKey } = options;
|
|
2112
|
+
const item = {
|
|
2113
|
+
value,
|
|
2114
|
+
expiry: expiry ? Date.now() + expiry : void 0
|
|
2115
|
+
};
|
|
2116
|
+
try {
|
|
2117
|
+
const jsonStr = JSON.stringify(item);
|
|
2118
|
+
await ensureKeyPair();
|
|
2119
|
+
const keyToUse = publicKey || globalKeyPair?.publicKey;
|
|
2120
|
+
if (keyToUse) {
|
|
2121
|
+
const encrypted = await encryptValue(jsonStr, keyToUse, globalHMACKey);
|
|
2122
|
+
window.localStorage.setItem(key, encrypted);
|
|
2123
|
+
} else {
|
|
2124
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
2125
|
+
console.warn(
|
|
2126
|
+
`\u26A0\uFE0F SECURITY WARNING: Storing data without encryption for key "${key}". Data is only Base64 encoded, not encrypted!`
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
2129
|
+
const encoded = base64Encode(jsonStr);
|
|
2130
|
+
window.localStorage.setItem(key, encoded);
|
|
2131
|
+
}
|
|
2132
|
+
} catch (error) {
|
|
2133
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
2134
|
+
console.error("localStorage.set error:", error);
|
|
2135
|
+
}
|
|
2136
|
+
throw error;
|
|
2137
|
+
}
|
|
2138
|
+
},
|
|
2139
|
+
/**
|
|
2140
|
+
* 获取值
|
|
2141
|
+
* @param key - 键
|
|
2142
|
+
* @param options - 选项(默认值、私钥等)
|
|
2143
|
+
* @returns Promise<T | undefined> 值或默认值
|
|
2144
|
+
*/
|
|
2145
|
+
async get(key, options = {}) {
|
|
2146
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
2147
|
+
return options.defaultValue;
|
|
2148
|
+
}
|
|
2149
|
+
const { defaultValue, privateKey } = options;
|
|
2150
|
+
try {
|
|
2151
|
+
const encryptedStr = window.localStorage.getItem(key);
|
|
2152
|
+
if (!encryptedStr) return defaultValue;
|
|
2153
|
+
await ensureKeyPair();
|
|
2154
|
+
let decryptedStr;
|
|
2155
|
+
const keyToUse = privateKey || globalKeyPair?.privateKey;
|
|
2156
|
+
if (keyToUse) {
|
|
2157
|
+
try {
|
|
2158
|
+
decryptedStr = await decryptValue(encryptedStr, keyToUse, globalHMACKey);
|
|
2159
|
+
} catch (error) {
|
|
2160
|
+
try {
|
|
2161
|
+
decryptedStr = await handleDecryptError(error, encryptedStr, key);
|
|
2162
|
+
} catch {
|
|
2163
|
+
return defaultValue;
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
} else {
|
|
2167
|
+
try {
|
|
2168
|
+
decryptedStr = handleNoKeyDecode(encryptedStr, key);
|
|
2169
|
+
} catch {
|
|
2170
|
+
return defaultValue;
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
const item = safeParseJSON(decryptedStr, "object");
|
|
2174
|
+
if (item.expiry && Date.now() > item.expiry) {
|
|
2175
|
+
window.localStorage.removeItem(key);
|
|
2176
|
+
return defaultValue;
|
|
2177
|
+
}
|
|
2178
|
+
return item.value;
|
|
2179
|
+
} catch (error) {
|
|
2180
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
2181
|
+
console.warn(`Failed to parse storage item for key "${key}"`);
|
|
2182
|
+
}
|
|
2183
|
+
return defaultValue;
|
|
2184
|
+
}
|
|
2185
|
+
},
|
|
2186
|
+
/**
|
|
2187
|
+
* 移除值
|
|
2188
|
+
* @param key - 键
|
|
2189
|
+
*/
|
|
2190
|
+
remove(key) {
|
|
2191
|
+
if (typeof window === "undefined" || !window.localStorage) return;
|
|
2192
|
+
try {
|
|
2193
|
+
window.localStorage.removeItem(key);
|
|
2194
|
+
} catch (error) {
|
|
2195
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
2196
|
+
console.error("localStorage.remove error");
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
},
|
|
2200
|
+
/**
|
|
2201
|
+
* 清空所有值
|
|
2202
|
+
*/
|
|
2203
|
+
clear() {
|
|
2204
|
+
if (typeof window === "undefined" || !window.localStorage) return;
|
|
2205
|
+
try {
|
|
2206
|
+
window.localStorage.clear();
|
|
2207
|
+
} catch (error) {
|
|
2208
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
2209
|
+
console.error("localStorage.clear error");
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
},
|
|
2213
|
+
/**
|
|
2214
|
+
* 获取所有键
|
|
2215
|
+
* @returns 键数组
|
|
2216
|
+
*/
|
|
2217
|
+
keys() {
|
|
2218
|
+
if (typeof window === "undefined" || !window.localStorage) return [];
|
|
2219
|
+
const keys2 = [];
|
|
2220
|
+
try {
|
|
2221
|
+
for (let i = 0; i < window.localStorage.length; i++) {
|
|
2222
|
+
const key = window.localStorage.key(i);
|
|
2223
|
+
if (key) keys2.push(key);
|
|
2224
|
+
}
|
|
2225
|
+
} catch (error) {
|
|
2226
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
2227
|
+
console.error("localStorage.keys error");
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
return keys2;
|
|
2231
|
+
}
|
|
2232
|
+
};
|
|
2233
|
+
var sessionStorage = {
|
|
2234
|
+
/**
|
|
2235
|
+
* 设置值
|
|
2236
|
+
* @param key - 键
|
|
2237
|
+
* @param value - 值
|
|
2238
|
+
* @param options - 选项(公钥等)
|
|
2239
|
+
* @returns Promise<void>
|
|
2240
|
+
*/
|
|
2241
|
+
async set(key, value, options = {}) {
|
|
2242
|
+
if (typeof window === "undefined" || !window.sessionStorage) {
|
|
2243
|
+
throw new Error("sessionStorage is not available");
|
|
2244
|
+
}
|
|
2245
|
+
const { publicKey } = options;
|
|
2246
|
+
try {
|
|
2247
|
+
const jsonStr = JSON.stringify(value);
|
|
2248
|
+
await ensureKeyPair();
|
|
2249
|
+
const keyToUse = publicKey || globalKeyPair?.publicKey;
|
|
2250
|
+
if (keyToUse) {
|
|
2251
|
+
const encrypted = await encryptValue(jsonStr, keyToUse, globalHMACKey);
|
|
2252
|
+
window.sessionStorage.setItem(key, encrypted);
|
|
2253
|
+
} else {
|
|
2254
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
2255
|
+
console.warn(
|
|
2256
|
+
`\u26A0\uFE0F SECURITY WARNING: Storing data without encryption for key "${key}". Data is only Base64 encoded, not encrypted!`
|
|
2257
|
+
);
|
|
2258
|
+
}
|
|
2259
|
+
const encoded = base64Encode(jsonStr);
|
|
2260
|
+
window.sessionStorage.setItem(key, encoded);
|
|
2261
|
+
}
|
|
2262
|
+
} catch (error) {
|
|
2263
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
2264
|
+
console.error("sessionStorage.set error:", error);
|
|
2265
|
+
}
|
|
2266
|
+
throw error;
|
|
2267
|
+
}
|
|
2268
|
+
},
|
|
2269
|
+
/**
|
|
2270
|
+
* 获取值
|
|
2271
|
+
* @param key - 键
|
|
2272
|
+
* @param options - 选项(默认值、私钥等)
|
|
2273
|
+
* @returns Promise<T | undefined> 值或默认值
|
|
2274
|
+
*/
|
|
2275
|
+
async get(key, options = {}) {
|
|
2276
|
+
if (typeof window === "undefined" || !window.sessionStorage) {
|
|
2277
|
+
return options.defaultValue;
|
|
2278
|
+
}
|
|
2279
|
+
const { defaultValue, privateKey } = options;
|
|
2280
|
+
try {
|
|
2281
|
+
const encryptedStr = window.sessionStorage.getItem(key);
|
|
2282
|
+
if (!encryptedStr) return defaultValue;
|
|
2283
|
+
await ensureKeyPair();
|
|
2284
|
+
let decryptedStr;
|
|
2285
|
+
const keyToUse = privateKey || globalKeyPair?.privateKey;
|
|
2286
|
+
if (keyToUse) {
|
|
2287
|
+
try {
|
|
2288
|
+
decryptedStr = await decryptValue(encryptedStr, keyToUse, globalHMACKey);
|
|
2289
|
+
} catch (error) {
|
|
2290
|
+
try {
|
|
2291
|
+
decryptedStr = await handleDecryptError(error, encryptedStr, key);
|
|
2292
|
+
} catch {
|
|
2293
|
+
return defaultValue;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
} else {
|
|
2297
|
+
try {
|
|
2298
|
+
decryptedStr = handleNoKeyDecode(encryptedStr, key);
|
|
2299
|
+
} catch {
|
|
2300
|
+
return defaultValue;
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
return safeParseJSON(decryptedStr);
|
|
2304
|
+
} catch {
|
|
2305
|
+
return defaultValue;
|
|
2306
|
+
}
|
|
2307
|
+
},
|
|
2308
|
+
/**
|
|
2309
|
+
* 移除值
|
|
2310
|
+
* @param key - 键
|
|
2311
|
+
*/
|
|
2312
|
+
remove(key) {
|
|
2313
|
+
if (typeof window === "undefined" || !window.sessionStorage) return;
|
|
2314
|
+
try {
|
|
2315
|
+
window.sessionStorage.removeItem(key);
|
|
2316
|
+
} catch (error) {
|
|
2317
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
2318
|
+
console.error("sessionStorage.remove error");
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
},
|
|
2322
|
+
/**
|
|
2323
|
+
* 清空所有值
|
|
2324
|
+
*/
|
|
2325
|
+
clear() {
|
|
2326
|
+
if (typeof window === "undefined" || !window.sessionStorage) return;
|
|
2327
|
+
try {
|
|
2328
|
+
window.sessionStorage.clear();
|
|
2329
|
+
} catch (error) {
|
|
2330
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
2331
|
+
console.error("sessionStorage.clear error");
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
};
|
|
2336
|
+
var cookie = {
|
|
2337
|
+
/**
|
|
2338
|
+
* 设置Cookie
|
|
2339
|
+
* @param key - 键
|
|
2340
|
+
* @param value - 值
|
|
2341
|
+
* @param options - Cookie选项
|
|
2342
|
+
*/
|
|
2343
|
+
set(key, value, options = {}) {
|
|
2344
|
+
if (typeof document === "undefined") {
|
|
2345
|
+
throw new Error("document is not available");
|
|
2346
|
+
}
|
|
2347
|
+
let cookieStr = `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
|
2348
|
+
if (options.expires) {
|
|
2349
|
+
const expiresDate = options.expires instanceof Date ? options.expires : new Date(Date.now() + options.expires * 24 * 60 * 60 * 1e3);
|
|
2350
|
+
cookieStr += `; expires=${expiresDate.toUTCString()}`;
|
|
2351
|
+
}
|
|
2352
|
+
if (options.path) cookieStr += `; path=${options.path}`;
|
|
2353
|
+
if (options.domain) cookieStr += `; domain=${options.domain}`;
|
|
2354
|
+
if (options.secure) cookieStr += "; secure";
|
|
2355
|
+
if (options.sameSite) cookieStr += `; sameSite=${options.sameSite}`;
|
|
2356
|
+
document.cookie = cookieStr;
|
|
2357
|
+
},
|
|
2358
|
+
/**
|
|
2359
|
+
* 获取Cookie
|
|
2360
|
+
* @param key - 键
|
|
2361
|
+
* @returns Cookie值
|
|
2362
|
+
*/
|
|
2363
|
+
get(key) {
|
|
2364
|
+
if (typeof document === "undefined") return void 0;
|
|
2365
|
+
const name = encodeURIComponent(key);
|
|
2366
|
+
const cookies = document.cookie.split(";");
|
|
2367
|
+
for (const cookieStr of cookies) {
|
|
2368
|
+
const trimmed = cookieStr.trim();
|
|
2369
|
+
const eqIndex = trimmed.indexOf("=");
|
|
2370
|
+
if (eqIndex === -1) continue;
|
|
2371
|
+
const cookieKey = trimmed.substring(0, eqIndex).trim();
|
|
2372
|
+
const cookieValue = trimmed.substring(eqIndex + 1).trim();
|
|
2373
|
+
if (cookieKey === name) {
|
|
2374
|
+
return decodeURIComponent(cookieValue);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
return void 0;
|
|
2378
|
+
},
|
|
2379
|
+
/**
|
|
2380
|
+
* 移除Cookie
|
|
2381
|
+
* @param key - 键
|
|
2382
|
+
* @param options - Cookie选项
|
|
2383
|
+
*/
|
|
2384
|
+
remove(key, options = {}) {
|
|
2385
|
+
cookie.set(key, "", {
|
|
2386
|
+
...options,
|
|
2387
|
+
expires: /* @__PURE__ */ new Date(0)
|
|
2388
|
+
});
|
|
2389
|
+
},
|
|
2390
|
+
/**
|
|
2391
|
+
* 获取所有Cookie
|
|
2392
|
+
* @returns Cookie对象
|
|
2393
|
+
*/
|
|
2394
|
+
getAll() {
|
|
2395
|
+
if (typeof document === "undefined") return {};
|
|
2396
|
+
const cookies = {};
|
|
2397
|
+
document.cookie.split(";").forEach((cookieStr) => {
|
|
2398
|
+
const trimmed = cookieStr.trim();
|
|
2399
|
+
const eqIndex = trimmed.indexOf("=");
|
|
2400
|
+
if (eqIndex === -1) return;
|
|
2401
|
+
const key = trimmed.substring(0, eqIndex).trim();
|
|
2402
|
+
const value = trimmed.substring(eqIndex + 1).trim();
|
|
2403
|
+
if (key && value) {
|
|
2404
|
+
cookies[decodeURIComponent(key)] = decodeURIComponent(value);
|
|
2405
|
+
}
|
|
2406
|
+
});
|
|
2407
|
+
return cookies;
|
|
2408
|
+
}
|
|
2409
|
+
};
|
|
2410
|
+
var storage = {
|
|
2411
|
+
/**
|
|
2412
|
+
* 设置值(优先使用localStorage,失败则使用sessionStorage)
|
|
2413
|
+
* @param key - 键
|
|
2414
|
+
* @param value - 值
|
|
2415
|
+
* @param options - 选项(过期时间、公钥等)
|
|
2416
|
+
* @returns Promise<void>
|
|
2417
|
+
*/
|
|
2418
|
+
async set(key, value, options = {}) {
|
|
2419
|
+
try {
|
|
2420
|
+
await localStorage.set(key, value, options);
|
|
2421
|
+
} catch {
|
|
2422
|
+
try {
|
|
2423
|
+
await sessionStorage.set(key, value, { publicKey: options.publicKey });
|
|
2424
|
+
} catch {
|
|
2425
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
2426
|
+
console.warn("Both localStorage and sessionStorage are not available");
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
},
|
|
2431
|
+
/**
|
|
2432
|
+
* 获取值
|
|
2433
|
+
* @param key - 键
|
|
2434
|
+
* @param options - 选项(默认值、私钥等)
|
|
2435
|
+
* @returns Promise<T | undefined> 值或默认值
|
|
2436
|
+
*/
|
|
2437
|
+
async get(key, options = {}) {
|
|
2438
|
+
const localValue = await localStorage.get(key, options);
|
|
2439
|
+
if (localValue !== void 0) return localValue;
|
|
2440
|
+
const sessionValue = await sessionStorage.get(key, options);
|
|
2441
|
+
return sessionValue !== void 0 ? sessionValue : options.defaultValue;
|
|
2442
|
+
},
|
|
2443
|
+
/**
|
|
2444
|
+
* 移除值
|
|
2445
|
+
* @param key - 键
|
|
2446
|
+
*/
|
|
2447
|
+
remove(key) {
|
|
2448
|
+
localStorage.remove(key);
|
|
2449
|
+
sessionStorage.remove(key);
|
|
2450
|
+
},
|
|
2451
|
+
/**
|
|
2452
|
+
* 清空所有值
|
|
2453
|
+
*/
|
|
2454
|
+
clear() {
|
|
2455
|
+
localStorage.clear();
|
|
2456
|
+
sessionStorage.clear();
|
|
2457
|
+
}
|
|
2458
|
+
};
|
|
2459
|
+
|
|
2460
|
+
// src/browser/network/index.ts
|
|
2461
|
+
async function fetchWithRetry(url, options = {}, retryCount = 3, retryDelay = 1e3) {
|
|
2462
|
+
let lastError = null;
|
|
2463
|
+
for (let i = 0; i <= retryCount; i++) {
|
|
2464
|
+
try {
|
|
2465
|
+
const response = await fetch(url, options);
|
|
2466
|
+
if (response.ok) {
|
|
2467
|
+
return response;
|
|
2468
|
+
}
|
|
2469
|
+
if (i < retryCount) {
|
|
2470
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
2471
|
+
continue;
|
|
2472
|
+
}
|
|
2473
|
+
return response;
|
|
2474
|
+
} catch (error) {
|
|
2475
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
2476
|
+
if (i < retryCount) {
|
|
2477
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
throw lastError || new Error("Fetch failed after retries");
|
|
2482
|
+
}
|
|
2483
|
+
async function fetchWithTimeout(url, options = {}, timeout2 = 5e3) {
|
|
2484
|
+
const controller = new AbortController();
|
|
2485
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout2);
|
|
2486
|
+
try {
|
|
2487
|
+
const response = await fetch(url, {
|
|
2488
|
+
...options,
|
|
2489
|
+
signal: controller.signal
|
|
2490
|
+
});
|
|
2491
|
+
clearTimeout(timeoutId);
|
|
2492
|
+
return response;
|
|
2493
|
+
} catch (error) {
|
|
2494
|
+
clearTimeout(timeoutId);
|
|
2495
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2496
|
+
throw new Error(`Request timeout after ${timeout2}ms`);
|
|
2497
|
+
}
|
|
2498
|
+
throw error;
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
async function downloadFile(url, filename) {
|
|
2502
|
+
if (typeof window === "undefined") {
|
|
2503
|
+
throw new Error("downloadFile is only available in browser environment");
|
|
2504
|
+
}
|
|
2505
|
+
try {
|
|
2506
|
+
const response = await fetch(url);
|
|
2507
|
+
if (!response.ok) {
|
|
2508
|
+
throw new Error(`Failed to download file: ${response.statusText}`);
|
|
2509
|
+
}
|
|
2510
|
+
const blob = await response.blob();
|
|
2511
|
+
const downloadUrl = window.URL.createObjectURL(blob);
|
|
2512
|
+
const link = document.createElement("a");
|
|
2513
|
+
link.href = downloadUrl;
|
|
2514
|
+
link.download = filename || url.split("/").pop() || "download";
|
|
2515
|
+
document.body.appendChild(link);
|
|
2516
|
+
link.click();
|
|
2517
|
+
document.body.removeChild(link);
|
|
2518
|
+
window.URL.revokeObjectURL(downloadUrl);
|
|
2519
|
+
} catch (error) {
|
|
2520
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
function checkOnline() {
|
|
2524
|
+
if (typeof navigator === "undefined") return true;
|
|
2525
|
+
return navigator.onLine;
|
|
2526
|
+
}
|
|
2527
|
+
async function request(url, options = {}) {
|
|
2528
|
+
const { method = "GET", headers = {}, body, timeout: timeout2, retry: retry2 = 0 } = options;
|
|
2529
|
+
const fetchOptions = {
|
|
2530
|
+
method,
|
|
2531
|
+
headers: {
|
|
2532
|
+
"Content-Type": "application/json",
|
|
2533
|
+
...headers
|
|
2534
|
+
}
|
|
2535
|
+
};
|
|
2536
|
+
if (body) {
|
|
2537
|
+
fetchOptions.body = typeof body === "string" ? body : JSON.stringify(body);
|
|
2538
|
+
}
|
|
2539
|
+
let fetchFn = fetch;
|
|
2540
|
+
if (timeout2) {
|
|
2541
|
+
fetchFn = (url2, opts) => fetchWithTimeout(url2, opts, timeout2);
|
|
2542
|
+
}
|
|
2543
|
+
if (retry2 > 0) {
|
|
2544
|
+
fetchFn = (url2, opts) => fetchWithRetry(url2, opts, retry2);
|
|
2545
|
+
}
|
|
2546
|
+
const response = await fetchFn(url, fetchOptions);
|
|
2547
|
+
if (!response.ok) {
|
|
2548
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
2549
|
+
}
|
|
2550
|
+
const contentType = response.headers.get("content-type");
|
|
2551
|
+
if (contentType && contentType.includes("application/json")) {
|
|
2552
|
+
return response.json();
|
|
2553
|
+
}
|
|
2554
|
+
return response.text();
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
// src/browser/dom/index.ts
|
|
2558
|
+
function $(selector, context = document) {
|
|
2559
|
+
return context.querySelector(selector);
|
|
2560
|
+
}
|
|
2561
|
+
function $$(selector, context = document) {
|
|
2562
|
+
return Array.from(context.querySelectorAll(selector));
|
|
2563
|
+
}
|
|
2564
|
+
function addClass(element, className) {
|
|
2565
|
+
element.classList.add(className);
|
|
2566
|
+
}
|
|
2567
|
+
function removeClass(element, className) {
|
|
2568
|
+
element.classList.remove(className);
|
|
2569
|
+
}
|
|
2570
|
+
function toggleClass(element, className) {
|
|
2571
|
+
return element.classList.toggle(className);
|
|
2572
|
+
}
|
|
2573
|
+
function getStyle(element, property) {
|
|
2574
|
+
return window.getComputedStyle(element).getPropertyValue(property);
|
|
2575
|
+
}
|
|
2576
|
+
function setStyle(element, property, value) {
|
|
2577
|
+
if (typeof property === "string" && value !== void 0) {
|
|
2578
|
+
element.style.setProperty(property, value);
|
|
2579
|
+
} else if (typeof property === "object") {
|
|
2580
|
+
Object.keys(property).forEach((key) => {
|
|
2581
|
+
element.style.setProperty(key, property[key]);
|
|
2582
|
+
});
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
function scrollTo(target, options = {}) {
|
|
2586
|
+
if (typeof window === "undefined") return;
|
|
2587
|
+
const { behavior = "smooth", block = "start", inline = "nearest" } = options;
|
|
2588
|
+
if (typeof target === "number") {
|
|
2589
|
+
window.scrollTo({ top: target, behavior });
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
2592
|
+
const element = typeof target === "string" ? $(target) : target;
|
|
2593
|
+
if (element) {
|
|
2594
|
+
element.scrollIntoView({ behavior, block, inline });
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
function getScrollPosition() {
|
|
2598
|
+
if (typeof window === "undefined") return { x: 0, y: 0 };
|
|
2599
|
+
return {
|
|
2600
|
+
x: window.pageXOffset || document.documentElement.scrollLeft || 0,
|
|
2601
|
+
y: window.pageYOffset || document.documentElement.scrollTop || 0
|
|
2602
|
+
};
|
|
2603
|
+
}
|
|
2604
|
+
function isInViewport(element, threshold = 0) {
|
|
2605
|
+
if (typeof window === "undefined") return false;
|
|
2606
|
+
const rect = element.getBoundingClientRect();
|
|
2607
|
+
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
|
|
2608
|
+
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
|
|
2609
|
+
return rect.top >= -threshold * rect.height && rect.left >= -threshold * rect.width && rect.bottom <= windowHeight + threshold * rect.height && rect.right <= windowWidth + threshold * rect.width;
|
|
2610
|
+
}
|
|
2611
|
+
function getElementOffset(element) {
|
|
2612
|
+
const rect = element.getBoundingClientRect();
|
|
2613
|
+
return {
|
|
2614
|
+
top: rect.top + (window.pageYOffset || document.documentElement.scrollTop),
|
|
2615
|
+
left: rect.left + (window.pageXOffset || document.documentElement.scrollLeft)
|
|
2616
|
+
};
|
|
2617
|
+
}
|
|
2618
|
+
async function copyToClipboard(text) {
|
|
2619
|
+
if (typeof navigator === "undefined" || !navigator.clipboard) {
|
|
2620
|
+
const textArea = document.createElement("textarea");
|
|
2621
|
+
textArea.value = text;
|
|
2622
|
+
textArea.style.position = "fixed";
|
|
2623
|
+
textArea.style.opacity = "0";
|
|
2624
|
+
document.body.appendChild(textArea);
|
|
2625
|
+
textArea.select();
|
|
2626
|
+
try {
|
|
2627
|
+
document.execCommand("copy");
|
|
2628
|
+
} catch (error) {
|
|
2629
|
+
throw new Error("Failed to copy text");
|
|
2630
|
+
}
|
|
2631
|
+
document.body.removeChild(textArea);
|
|
2632
|
+
return;
|
|
2633
|
+
}
|
|
2634
|
+
try {
|
|
2635
|
+
await navigator.clipboard.writeText(text);
|
|
2636
|
+
} catch (error) {
|
|
2637
|
+
throw new Error("Failed to copy text");
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
// src/data/transform/index.ts
|
|
2642
|
+
function csvToJson(csv, options = {}) {
|
|
2643
|
+
const { delimiter = ",", headers, skipEmptyLines = true } = options;
|
|
2644
|
+
const lines = csv.split(/\r?\n/).filter((line) => {
|
|
2645
|
+
if (skipEmptyLines) {
|
|
2646
|
+
return line.trim().length > 0;
|
|
2647
|
+
}
|
|
2648
|
+
return true;
|
|
2649
|
+
});
|
|
2650
|
+
if (lines.length === 0) return [];
|
|
2651
|
+
const headerRow = headers || lines[0].split(delimiter).map((h) => h.trim());
|
|
2652
|
+
const dataLines = headers ? lines : lines.slice(1);
|
|
2653
|
+
return dataLines.map((line) => {
|
|
2654
|
+
const values2 = parseCsvLine(line, delimiter);
|
|
2655
|
+
const obj = {};
|
|
2656
|
+
headerRow.forEach((header, index) => {
|
|
2657
|
+
const val = values2[index];
|
|
2658
|
+
obj[header] = val !== void 0 ? val : "";
|
|
2659
|
+
});
|
|
2660
|
+
return obj;
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
function parseCsvLine(line, delimiter) {
|
|
2664
|
+
const result = [];
|
|
2665
|
+
let current = "";
|
|
2666
|
+
let inQuotes = false;
|
|
2667
|
+
for (let i = 0; i < line.length; i++) {
|
|
2668
|
+
const char = line[i];
|
|
2669
|
+
const nextChar = line[i + 1];
|
|
2670
|
+
if (char === '"') {
|
|
2671
|
+
if (inQuotes && nextChar === '"') {
|
|
2672
|
+
current += '"';
|
|
2673
|
+
i++;
|
|
2674
|
+
} else {
|
|
2675
|
+
inQuotes = !inQuotes;
|
|
2676
|
+
}
|
|
2677
|
+
} else if (char === delimiter && !inQuotes) {
|
|
2678
|
+
result.push(current.trim());
|
|
2679
|
+
current = "";
|
|
2680
|
+
} else {
|
|
2681
|
+
current += char;
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
result.push(current.trim());
|
|
2685
|
+
return result;
|
|
2686
|
+
}
|
|
2687
|
+
function jsonToCsv(data, options = {}) {
|
|
2688
|
+
const { delimiter = ",", headers, includeHeaders = true } = options;
|
|
2689
|
+
if (data.length === 0) return "";
|
|
2690
|
+
const headerRow = headers || Object.keys(data[0]);
|
|
2691
|
+
const lines = [];
|
|
2692
|
+
if (includeHeaders) {
|
|
2693
|
+
lines.push(headerRow.map(escapeCsvValue).join(delimiter));
|
|
2694
|
+
}
|
|
2695
|
+
data.forEach((item) => {
|
|
2696
|
+
const values2 = headerRow.map((header) => {
|
|
2697
|
+
const value = item[header];
|
|
2698
|
+
return escapeCsvValue(value != null ? String(value) : "");
|
|
2699
|
+
});
|
|
2700
|
+
lines.push(values2.join(delimiter));
|
|
2701
|
+
});
|
|
2702
|
+
return lines.join("\n");
|
|
2703
|
+
}
|
|
2704
|
+
function escapeCsvValue(value) {
|
|
2705
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n")) {
|
|
2706
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
2707
|
+
}
|
|
2708
|
+
return value;
|
|
2709
|
+
}
|
|
2710
|
+
function xmlToJson(xml) {
|
|
2711
|
+
const parser = new DOMParser();
|
|
2712
|
+
const doc = parser.parseFromString(xml, "text/xml");
|
|
2713
|
+
const parseError = doc.querySelector("parsererror");
|
|
2714
|
+
if (parseError) {
|
|
2715
|
+
throw new Error("Invalid XML: " + parseError.textContent);
|
|
2716
|
+
}
|
|
2717
|
+
const result = xmlNodeToJson(doc.documentElement);
|
|
2718
|
+
if (typeof result === "object" && result !== null && !Array.isArray(result)) {
|
|
2719
|
+
return result;
|
|
2720
|
+
}
|
|
2721
|
+
return { value: result };
|
|
2722
|
+
}
|
|
2723
|
+
function xmlNodeToJson(node) {
|
|
2724
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
2725
|
+
const text = node.textContent?.trim();
|
|
2726
|
+
return text || null;
|
|
2727
|
+
}
|
|
2728
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
2729
|
+
const element = node;
|
|
2730
|
+
const result = {};
|
|
2731
|
+
if (element.attributes.length > 0) {
|
|
2732
|
+
const attrs = {};
|
|
2733
|
+
for (let i = 0; i < element.attributes.length; i++) {
|
|
2734
|
+
const attr = element.attributes[i];
|
|
2735
|
+
attrs[attr.name] = attr.value;
|
|
2736
|
+
}
|
|
2737
|
+
result["@attributes"] = attrs;
|
|
2738
|
+
}
|
|
2739
|
+
const children = {};
|
|
2740
|
+
let hasText = false;
|
|
2741
|
+
let textContent = "";
|
|
2742
|
+
for (let i = 0; i < element.childNodes.length; i++) {
|
|
2743
|
+
const child = element.childNodes[i];
|
|
2744
|
+
if (child.nodeType === Node.TEXT_NODE) {
|
|
2745
|
+
const text = child.textContent?.trim();
|
|
2746
|
+
if (text) {
|
|
2747
|
+
textContent += text;
|
|
2748
|
+
hasText = true;
|
|
2749
|
+
}
|
|
2750
|
+
} else if (child.nodeType === Node.ELEMENT_NODE) {
|
|
2751
|
+
const childElement = child;
|
|
2752
|
+
const childName = childElement.tagName;
|
|
2753
|
+
const childValue = xmlNodeToJson(childElement);
|
|
2754
|
+
if (!children[childName]) {
|
|
2755
|
+
children[childName] = [];
|
|
2756
|
+
}
|
|
2757
|
+
children[childName].push(childValue);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
if (hasText && Object.keys(children).length === 0) {
|
|
2761
|
+
return textContent;
|
|
2762
|
+
}
|
|
2763
|
+
Object.keys(children).forEach((key) => {
|
|
2764
|
+
result[key] = children[key].length === 1 ? children[key][0] : children[key];
|
|
2765
|
+
});
|
|
2766
|
+
if (hasText) {
|
|
2767
|
+
result["#text"] = textContent;
|
|
2768
|
+
}
|
|
2769
|
+
return result;
|
|
2770
|
+
}
|
|
2771
|
+
return null;
|
|
2772
|
+
}
|
|
2773
|
+
function jsonToXml(json, rootTag = "root") {
|
|
2774
|
+
return jsonToXmlElement(json, rootTag);
|
|
2775
|
+
}
|
|
2776
|
+
function jsonToXmlElement(obj, tagName) {
|
|
2777
|
+
if (obj === null || obj === void 0) {
|
|
2778
|
+
return `<${tagName}></${tagName}>`;
|
|
2779
|
+
}
|
|
2780
|
+
if (typeof obj === "string" || typeof obj === "number" || typeof obj === "boolean") {
|
|
2781
|
+
return `<${tagName}>${escapeXml(String(obj))}</${tagName}>`;
|
|
2782
|
+
}
|
|
2783
|
+
if (Array.isArray(obj)) {
|
|
2784
|
+
return obj.map((item) => jsonToXmlElement(item, tagName)).join("");
|
|
2785
|
+
}
|
|
2786
|
+
if (typeof obj === "object") {
|
|
2787
|
+
const record = obj;
|
|
2788
|
+
let xml = `<${tagName}`;
|
|
2789
|
+
if (record["@attributes"]) {
|
|
2790
|
+
const attrs = record["@attributes"];
|
|
2791
|
+
Object.keys(attrs).forEach((key) => {
|
|
2792
|
+
xml += ` ${key}="${escapeXml(String(attrs[key]))}"`;
|
|
2793
|
+
});
|
|
2794
|
+
}
|
|
2795
|
+
xml += ">";
|
|
2796
|
+
if (record["#text"]) {
|
|
2797
|
+
xml += escapeXml(String(record["#text"]));
|
|
2798
|
+
}
|
|
2799
|
+
Object.keys(record).forEach((key) => {
|
|
2800
|
+
if (key !== "@attributes" && key !== "#text") {
|
|
2801
|
+
const value = record[key];
|
|
2802
|
+
if (Array.isArray(value)) {
|
|
2803
|
+
value.forEach((item) => {
|
|
2804
|
+
xml += jsonToXmlElement(item, key);
|
|
2805
|
+
});
|
|
2806
|
+
} else {
|
|
2807
|
+
xml += jsonToXmlElement(value, key);
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
});
|
|
2811
|
+
xml += `</${tagName}>`;
|
|
2812
|
+
return xml;
|
|
2813
|
+
}
|
|
2814
|
+
return `<${tagName}></${tagName}>`;
|
|
2815
|
+
}
|
|
2816
|
+
function escapeXml(str) {
|
|
2817
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2818
|
+
}
|
|
2819
|
+
function yamlToJson(yaml) {
|
|
2820
|
+
const lines = yaml.split(/\r?\n/);
|
|
2821
|
+
const result = {};
|
|
2822
|
+
const stack = [
|
|
2823
|
+
{ obj: result, indent: -1 }
|
|
2824
|
+
];
|
|
2825
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2826
|
+
const line = lines[i];
|
|
2827
|
+
const trimmed = line.trim();
|
|
2828
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2829
|
+
const indent = line.length - line.trimStart().length;
|
|
2830
|
+
const current = stack[stack.length - 1];
|
|
2831
|
+
while (stack.length > 1 && indent <= current.indent) {
|
|
2832
|
+
stack.pop();
|
|
2833
|
+
}
|
|
2834
|
+
const match = trimmed.match(/^([^:]+):\s*(.*)$/);
|
|
2835
|
+
if (match) {
|
|
2836
|
+
const key = match[1].trim();
|
|
2837
|
+
const valueStr = match[2].trim();
|
|
2838
|
+
let parsedValue;
|
|
2839
|
+
if (valueStr === "") {
|
|
2840
|
+
parsedValue = {};
|
|
2841
|
+
} else if (valueStr === "[]") {
|
|
2842
|
+
parsedValue = [];
|
|
2843
|
+
} else if (valueStr.startsWith("[") && valueStr.endsWith("]")) {
|
|
2844
|
+
parsedValue = parseYamlArray(valueStr);
|
|
2845
|
+
} else if (valueStr === "true" || valueStr === "True") {
|
|
2846
|
+
parsedValue = true;
|
|
2847
|
+
} else if (valueStr === "false" || valueStr === "False") {
|
|
2848
|
+
parsedValue = false;
|
|
2849
|
+
} else if (valueStr === "null" || valueStr === "Null" || valueStr === "~") {
|
|
2850
|
+
parsedValue = null;
|
|
2851
|
+
} else if (/^-?\d+$/.test(valueStr)) {
|
|
2852
|
+
parsedValue = parseInt(valueStr, 10);
|
|
2853
|
+
} else if (/^-?\d+\.\d+$/.test(valueStr)) {
|
|
2854
|
+
parsedValue = parseFloat(valueStr);
|
|
2855
|
+
} else if (valueStr.startsWith('"') && valueStr.endsWith('"')) {
|
|
2856
|
+
parsedValue = valueStr.slice(1, -1).replace(/\\"/g, '"');
|
|
2857
|
+
} else if (valueStr.startsWith("'") && valueStr.endsWith("'")) {
|
|
2858
|
+
parsedValue = valueStr.slice(1, -1);
|
|
2859
|
+
} else {
|
|
2860
|
+
parsedValue = valueStr;
|
|
2861
|
+
}
|
|
2862
|
+
const currentObj = stack[stack.length - 1].obj;
|
|
2863
|
+
if (typeof parsedValue === "object" && parsedValue !== null && !Array.isArray(parsedValue)) {
|
|
2864
|
+
stack.push({ obj: parsedValue, indent });
|
|
2865
|
+
}
|
|
2866
|
+
currentObj[key] = parsedValue;
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
return result;
|
|
2870
|
+
}
|
|
2871
|
+
function parseYamlArray(str) {
|
|
2872
|
+
const content = str.slice(1, -1).trim();
|
|
2873
|
+
if (!content) return [];
|
|
2874
|
+
const items = [];
|
|
2875
|
+
const parts = content.split(",").map((s) => s.trim());
|
|
2876
|
+
parts.forEach((part) => {
|
|
2877
|
+
if (part.startsWith('"') && part.endsWith('"')) {
|
|
2878
|
+
items.push(part.slice(1, -1).replace(/\\"/g, '"'));
|
|
2879
|
+
} else if (part.startsWith("'") && part.endsWith("'")) {
|
|
2880
|
+
items.push(part.slice(1, -1));
|
|
2881
|
+
} else if (part === "true" || part === "True") {
|
|
2882
|
+
items.push(true);
|
|
2883
|
+
} else if (part === "false" || part === "False") {
|
|
2884
|
+
items.push(false);
|
|
2885
|
+
} else if (part === "null" || part === "Null") {
|
|
2886
|
+
items.push(null);
|
|
2887
|
+
} else if (/^-?\d+$/.test(part)) {
|
|
2888
|
+
items.push(parseInt(part, 10));
|
|
2889
|
+
} else if (/^-?\d+\.\d+$/.test(part)) {
|
|
2890
|
+
items.push(parseFloat(part));
|
|
2891
|
+
} else {
|
|
2892
|
+
items.push(part);
|
|
2893
|
+
}
|
|
2894
|
+
});
|
|
2895
|
+
return items;
|
|
2896
|
+
}
|
|
2897
|
+
function jsonToYaml(json, indent = 2) {
|
|
2898
|
+
return jsonToYamlValue(json, 0, indent);
|
|
2899
|
+
}
|
|
2900
|
+
function jsonToYamlValue(value, level, indent) {
|
|
2901
|
+
const spaces = " ".repeat(level * indent);
|
|
2902
|
+
if (value === null) {
|
|
2903
|
+
return "null";
|
|
2904
|
+
}
|
|
2905
|
+
if (typeof value === "string") {
|
|
2906
|
+
if (value.includes(":") || value.includes("\n") || value.includes('"')) {
|
|
2907
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
2908
|
+
}
|
|
2909
|
+
return value;
|
|
2910
|
+
}
|
|
2911
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
2912
|
+
return String(value);
|
|
2913
|
+
}
|
|
2914
|
+
if (Array.isArray(value)) {
|
|
2915
|
+
if (value.length === 0) {
|
|
2916
|
+
return "[]";
|
|
2917
|
+
}
|
|
2918
|
+
return value.map((item) => `${spaces}- ${jsonToYamlValue(item, level + 1, indent)}`).join("\n");
|
|
2919
|
+
}
|
|
2920
|
+
if (typeof value === "object") {
|
|
2921
|
+
const obj = value;
|
|
2922
|
+
const keys2 = Object.keys(obj);
|
|
2923
|
+
if (keys2.length === 0) {
|
|
2924
|
+
return "{}";
|
|
2925
|
+
}
|
|
2926
|
+
return keys2.map((key) => {
|
|
2927
|
+
const val = obj[key];
|
|
2928
|
+
const keyStr = /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key) ? key : `"${key}"`;
|
|
2929
|
+
const valStr = jsonToYamlValue(val, level + 1, indent);
|
|
2930
|
+
if (typeof val === "object" && val !== null) {
|
|
2931
|
+
return `${spaces}${keyStr}:
|
|
2932
|
+
${valStr}`;
|
|
2933
|
+
}
|
|
2934
|
+
return `${spaces}${keyStr}: ${valStr}`;
|
|
2935
|
+
}).join("\n");
|
|
2936
|
+
}
|
|
2937
|
+
return String(value);
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2940
|
+
// src/data/data-structure/index.ts
|
|
2941
|
+
var Stack = class {
|
|
2942
|
+
constructor() {
|
|
2943
|
+
this.items = [];
|
|
2944
|
+
}
|
|
2945
|
+
/**
|
|
2946
|
+
* 入栈
|
|
2947
|
+
*/
|
|
2948
|
+
push(item) {
|
|
2949
|
+
this.items.push(item);
|
|
2950
|
+
}
|
|
2951
|
+
/**
|
|
2952
|
+
* 出栈
|
|
2953
|
+
*/
|
|
2954
|
+
pop() {
|
|
2955
|
+
return this.items.pop();
|
|
2956
|
+
}
|
|
2957
|
+
/**
|
|
2958
|
+
* 查看栈顶元素
|
|
2959
|
+
*/
|
|
2960
|
+
peek() {
|
|
2961
|
+
return this.items[this.items.length - 1];
|
|
2962
|
+
}
|
|
2963
|
+
/**
|
|
2964
|
+
* 判断栈是否为空
|
|
2965
|
+
*/
|
|
2966
|
+
isEmpty() {
|
|
2967
|
+
return this.items.length === 0;
|
|
2968
|
+
}
|
|
2969
|
+
/**
|
|
2970
|
+
* 获取栈的大小
|
|
2971
|
+
*/
|
|
2972
|
+
size() {
|
|
2973
|
+
return this.items.length;
|
|
2974
|
+
}
|
|
2975
|
+
/**
|
|
2976
|
+
* 清空栈
|
|
2977
|
+
*/
|
|
2978
|
+
clear() {
|
|
2979
|
+
this.items = [];
|
|
2980
|
+
}
|
|
2981
|
+
/**
|
|
2982
|
+
* 转换为数组
|
|
2983
|
+
*/
|
|
2984
|
+
toArray() {
|
|
2985
|
+
return [...this.items];
|
|
2986
|
+
}
|
|
2987
|
+
};
|
|
2988
|
+
var DataQueue = class {
|
|
2989
|
+
constructor() {
|
|
2990
|
+
this.items = [];
|
|
2991
|
+
}
|
|
2992
|
+
/**
|
|
2993
|
+
* 入队
|
|
2994
|
+
*/
|
|
2995
|
+
enqueue(item) {
|
|
2996
|
+
this.items.push(item);
|
|
2997
|
+
}
|
|
2998
|
+
/**
|
|
2999
|
+
* 出队
|
|
3000
|
+
*/
|
|
3001
|
+
dequeue() {
|
|
3002
|
+
return this.items.shift();
|
|
3003
|
+
}
|
|
3004
|
+
/**
|
|
3005
|
+
* 查看队首元素
|
|
3006
|
+
*/
|
|
3007
|
+
front() {
|
|
3008
|
+
return this.items[0];
|
|
3009
|
+
}
|
|
3010
|
+
/**
|
|
3011
|
+
* 判断队列是否为空
|
|
3012
|
+
*/
|
|
3013
|
+
isEmpty() {
|
|
3014
|
+
return this.items.length === 0;
|
|
3015
|
+
}
|
|
3016
|
+
/**
|
|
3017
|
+
* 获取队列的大小
|
|
3018
|
+
*/
|
|
3019
|
+
size() {
|
|
3020
|
+
return this.items.length;
|
|
3021
|
+
}
|
|
3022
|
+
/**
|
|
3023
|
+
* 清空队列
|
|
3024
|
+
*/
|
|
3025
|
+
clear() {
|
|
3026
|
+
this.items = [];
|
|
3027
|
+
}
|
|
3028
|
+
/**
|
|
3029
|
+
* 转换为数组
|
|
3030
|
+
*/
|
|
3031
|
+
toArray() {
|
|
3032
|
+
return [...this.items];
|
|
3033
|
+
}
|
|
3034
|
+
};
|
|
3035
|
+
var ListNode = class {
|
|
3036
|
+
constructor(value) {
|
|
3037
|
+
this.next = null;
|
|
3038
|
+
this.value = value;
|
|
3039
|
+
}
|
|
3040
|
+
};
|
|
3041
|
+
var LinkedList = class {
|
|
3042
|
+
constructor() {
|
|
3043
|
+
this.head = null;
|
|
3044
|
+
this.length = 0;
|
|
3045
|
+
}
|
|
3046
|
+
/**
|
|
3047
|
+
* 在链表末尾添加元素
|
|
3048
|
+
*/
|
|
3049
|
+
append(value) {
|
|
3050
|
+
const newNode = new ListNode(value);
|
|
3051
|
+
if (!this.head) {
|
|
3052
|
+
this.head = newNode;
|
|
3053
|
+
} else {
|
|
3054
|
+
let current = this.head;
|
|
3055
|
+
while (current.next) {
|
|
3056
|
+
current = current.next;
|
|
3057
|
+
}
|
|
3058
|
+
current.next = newNode;
|
|
3059
|
+
}
|
|
3060
|
+
this.length++;
|
|
3061
|
+
}
|
|
3062
|
+
/**
|
|
3063
|
+
* 在指定位置插入元素
|
|
3064
|
+
*/
|
|
3065
|
+
insert(index, value) {
|
|
3066
|
+
if (index < 0 || index > this.length) {
|
|
3067
|
+
return false;
|
|
3068
|
+
}
|
|
3069
|
+
const newNode = new ListNode(value);
|
|
3070
|
+
if (index === 0) {
|
|
3071
|
+
newNode.next = this.head;
|
|
3072
|
+
this.head = newNode;
|
|
3073
|
+
} else {
|
|
3074
|
+
let current = this.head;
|
|
3075
|
+
for (let i = 0; i < index - 1; i++) {
|
|
3076
|
+
current = current.next;
|
|
3077
|
+
}
|
|
3078
|
+
newNode.next = current.next;
|
|
3079
|
+
current.next = newNode;
|
|
3080
|
+
}
|
|
3081
|
+
this.length++;
|
|
3082
|
+
return true;
|
|
3083
|
+
}
|
|
3084
|
+
/**
|
|
3085
|
+
* 删除指定位置的元素
|
|
3086
|
+
*/
|
|
3087
|
+
removeAt(index) {
|
|
3088
|
+
if (index < 0 || index >= this.length || !this.head) {
|
|
3089
|
+
return null;
|
|
3090
|
+
}
|
|
3091
|
+
let current = this.head;
|
|
3092
|
+
if (index === 0) {
|
|
3093
|
+
this.head = current.next;
|
|
3094
|
+
this.length--;
|
|
3095
|
+
return current.value;
|
|
3096
|
+
}
|
|
3097
|
+
for (let i = 0; i < index - 1; i++) {
|
|
3098
|
+
current = current.next;
|
|
3099
|
+
}
|
|
3100
|
+
const removed = current.next;
|
|
3101
|
+
current.next = removed.next;
|
|
3102
|
+
this.length--;
|
|
3103
|
+
return removed.value;
|
|
3104
|
+
}
|
|
3105
|
+
/**
|
|
3106
|
+
* 查找元素索引
|
|
3107
|
+
*/
|
|
3108
|
+
indexOf(value) {
|
|
3109
|
+
let current = this.head;
|
|
3110
|
+
let index = 0;
|
|
3111
|
+
while (current) {
|
|
3112
|
+
if (current.value === value) {
|
|
3113
|
+
return index;
|
|
3114
|
+
}
|
|
3115
|
+
current = current.next;
|
|
3116
|
+
index++;
|
|
3117
|
+
}
|
|
3118
|
+
return -1;
|
|
3119
|
+
}
|
|
3120
|
+
/**
|
|
3121
|
+
* 获取指定位置的元素
|
|
3122
|
+
*/
|
|
3123
|
+
get(index) {
|
|
3124
|
+
if (index < 0 || index >= this.length || !this.head) {
|
|
3125
|
+
return null;
|
|
3126
|
+
}
|
|
3127
|
+
let current = this.head;
|
|
3128
|
+
for (let i = 0; i < index; i++) {
|
|
3129
|
+
current = current.next;
|
|
3130
|
+
}
|
|
3131
|
+
return current.value;
|
|
3132
|
+
}
|
|
3133
|
+
/**
|
|
3134
|
+
* 判断链表是否为空
|
|
3135
|
+
*/
|
|
3136
|
+
isEmpty() {
|
|
3137
|
+
return this.length === 0;
|
|
3138
|
+
}
|
|
3139
|
+
/**
|
|
3140
|
+
* 获取链表长度
|
|
3141
|
+
*/
|
|
3142
|
+
size() {
|
|
3143
|
+
return this.length;
|
|
3144
|
+
}
|
|
3145
|
+
/**
|
|
3146
|
+
* 转换为数组
|
|
3147
|
+
*/
|
|
3148
|
+
toArray() {
|
|
3149
|
+
const result = [];
|
|
3150
|
+
let current = this.head;
|
|
3151
|
+
while (current) {
|
|
3152
|
+
result.push(current.value);
|
|
3153
|
+
current = current.next;
|
|
3154
|
+
}
|
|
3155
|
+
return result;
|
|
3156
|
+
}
|
|
3157
|
+
};
|
|
3158
|
+
var TreeNode = class {
|
|
3159
|
+
constructor(value) {
|
|
3160
|
+
this.left = null;
|
|
3161
|
+
this.right = null;
|
|
3162
|
+
this.value = value;
|
|
3163
|
+
}
|
|
3164
|
+
};
|
|
3165
|
+
var BinaryTree = class {
|
|
3166
|
+
constructor() {
|
|
3167
|
+
this.root = null;
|
|
3168
|
+
}
|
|
3169
|
+
/**
|
|
3170
|
+
* 插入节点
|
|
3171
|
+
*/
|
|
3172
|
+
insert(value) {
|
|
3173
|
+
const newNode = new TreeNode(value);
|
|
3174
|
+
if (!this.root) {
|
|
3175
|
+
this.root = newNode;
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
this.insertNode(this.root, newNode);
|
|
3179
|
+
}
|
|
3180
|
+
insertNode(node, newNode) {
|
|
3181
|
+
if (newNode.value <= node.value) {
|
|
3182
|
+
if (!node.left) {
|
|
3183
|
+
node.left = newNode;
|
|
3184
|
+
} else {
|
|
3185
|
+
this.insertNode(node.left, newNode);
|
|
3186
|
+
}
|
|
3187
|
+
} else {
|
|
3188
|
+
if (!node.right) {
|
|
3189
|
+
node.right = newNode;
|
|
3190
|
+
} else {
|
|
3191
|
+
this.insertNode(node.right, newNode);
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
/**
|
|
3196
|
+
* 查找节点
|
|
3197
|
+
*/
|
|
3198
|
+
search(value) {
|
|
3199
|
+
return this.searchNode(this.root, value);
|
|
3200
|
+
}
|
|
3201
|
+
searchNode(node, value) {
|
|
3202
|
+
if (!node) {
|
|
3203
|
+
return false;
|
|
3204
|
+
}
|
|
3205
|
+
if (value === node.value) {
|
|
3206
|
+
return true;
|
|
3207
|
+
}
|
|
3208
|
+
if (value < node.value) {
|
|
3209
|
+
return this.searchNode(node.left, value);
|
|
3210
|
+
}
|
|
3211
|
+
return this.searchNode(node.right, value);
|
|
3212
|
+
}
|
|
3213
|
+
/**
|
|
3214
|
+
* 中序遍历
|
|
3215
|
+
*/
|
|
3216
|
+
inOrder() {
|
|
3217
|
+
const result = [];
|
|
3218
|
+
this.inOrderTraverse(this.root, result);
|
|
3219
|
+
return result;
|
|
3220
|
+
}
|
|
3221
|
+
inOrderTraverse(node, result) {
|
|
3222
|
+
if (node) {
|
|
3223
|
+
this.inOrderTraverse(node.left, result);
|
|
3224
|
+
result.push(node.value);
|
|
3225
|
+
this.inOrderTraverse(node.right, result);
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
/**
|
|
3229
|
+
* 前序遍历
|
|
3230
|
+
*/
|
|
3231
|
+
preOrder() {
|
|
3232
|
+
const result = [];
|
|
3233
|
+
this.preOrderTraverse(this.root, result);
|
|
3234
|
+
return result;
|
|
3235
|
+
}
|
|
3236
|
+
preOrderTraverse(node, result) {
|
|
3237
|
+
if (node) {
|
|
3238
|
+
result.push(node.value);
|
|
3239
|
+
this.preOrderTraverse(node.left, result);
|
|
3240
|
+
this.preOrderTraverse(node.right, result);
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
/**
|
|
3244
|
+
* 后序遍历
|
|
3245
|
+
*/
|
|
3246
|
+
postOrder() {
|
|
3247
|
+
const result = [];
|
|
3248
|
+
this.postOrderTraverse(this.root, result);
|
|
3249
|
+
return result;
|
|
3250
|
+
}
|
|
3251
|
+
postOrderTraverse(node, result) {
|
|
3252
|
+
if (node) {
|
|
3253
|
+
this.postOrderTraverse(node.left, result);
|
|
3254
|
+
this.postOrderTraverse(node.right, result);
|
|
3255
|
+
result.push(node.value);
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
};
|
|
3259
|
+
var Graph = class {
|
|
3260
|
+
constructor() {
|
|
3261
|
+
this.vertices = /* @__PURE__ */ new Map();
|
|
3262
|
+
}
|
|
3263
|
+
/**
|
|
3264
|
+
* 添加顶点
|
|
3265
|
+
*/
|
|
3266
|
+
addVertex(vertex) {
|
|
3267
|
+
if (!this.vertices.has(vertex)) {
|
|
3268
|
+
this.vertices.set(vertex, []);
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
/**
|
|
3272
|
+
* 添加边(无向图)
|
|
3273
|
+
*/
|
|
3274
|
+
addEdge(vertex1, vertex2) {
|
|
3275
|
+
if (!this.vertices.has(vertex1)) {
|
|
3276
|
+
this.addVertex(vertex1);
|
|
3277
|
+
}
|
|
3278
|
+
if (!this.vertices.has(vertex2)) {
|
|
3279
|
+
this.addVertex(vertex2);
|
|
3280
|
+
}
|
|
3281
|
+
const neighbors1 = this.vertices.get(vertex1);
|
|
3282
|
+
const neighbors2 = this.vertices.get(vertex2);
|
|
3283
|
+
if (!neighbors1.includes(vertex2)) {
|
|
3284
|
+
neighbors1.push(vertex2);
|
|
3285
|
+
}
|
|
3286
|
+
if (!neighbors2.includes(vertex1)) {
|
|
3287
|
+
neighbors2.push(vertex1);
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
/**
|
|
3291
|
+
* 获取顶点的邻居
|
|
3292
|
+
*/
|
|
3293
|
+
getNeighbors(vertex) {
|
|
3294
|
+
return this.vertices.get(vertex) || [];
|
|
3295
|
+
}
|
|
3296
|
+
/**
|
|
3297
|
+
* 深度优先搜索
|
|
3298
|
+
*/
|
|
3299
|
+
dfs(start, callback) {
|
|
3300
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3301
|
+
const result = [];
|
|
3302
|
+
const dfsVisit = (vertex) => {
|
|
3303
|
+
visited.add(vertex);
|
|
3304
|
+
result.push(vertex);
|
|
3305
|
+
if (callback) {
|
|
3306
|
+
callback(vertex);
|
|
3307
|
+
}
|
|
3308
|
+
const neighbors = this.getNeighbors(vertex);
|
|
3309
|
+
for (const neighbor of neighbors) {
|
|
3310
|
+
if (!visited.has(neighbor)) {
|
|
3311
|
+
dfsVisit(neighbor);
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
};
|
|
3315
|
+
dfsVisit(start);
|
|
3316
|
+
return result;
|
|
3317
|
+
}
|
|
3318
|
+
/**
|
|
3319
|
+
* 广度优先搜索
|
|
3320
|
+
*/
|
|
3321
|
+
bfs(start, callback) {
|
|
3322
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3323
|
+
const queue = [start];
|
|
3324
|
+
const result = [];
|
|
3325
|
+
visited.add(start);
|
|
3326
|
+
while (queue.length > 0) {
|
|
3327
|
+
const vertex = queue.shift();
|
|
3328
|
+
result.push(vertex);
|
|
3329
|
+
if (callback) {
|
|
3330
|
+
callback(vertex);
|
|
3331
|
+
}
|
|
3332
|
+
const neighbors = this.getNeighbors(vertex);
|
|
3333
|
+
for (const neighbor of neighbors) {
|
|
3334
|
+
if (!visited.has(neighbor)) {
|
|
3335
|
+
visited.add(neighbor);
|
|
3336
|
+
queue.push(neighbor);
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
return result;
|
|
3341
|
+
}
|
|
3342
|
+
/**
|
|
3343
|
+
* 获取所有顶点
|
|
3344
|
+
*/
|
|
3345
|
+
getVertices() {
|
|
3346
|
+
return Array.from(this.vertices.keys());
|
|
3347
|
+
}
|
|
3348
|
+
};
|
|
3349
|
+
var LRUCache = class {
|
|
3350
|
+
constructor(capacity) {
|
|
3351
|
+
if (capacity <= 0) {
|
|
3352
|
+
throw new Error("Capacity must be greater than 0");
|
|
3353
|
+
}
|
|
3354
|
+
this.capacity = capacity;
|
|
3355
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
3356
|
+
}
|
|
3357
|
+
/**
|
|
3358
|
+
* 获取值
|
|
3359
|
+
*/
|
|
3360
|
+
get(key) {
|
|
3361
|
+
if (!this.cache.has(key)) {
|
|
3362
|
+
return void 0;
|
|
3363
|
+
}
|
|
3364
|
+
const value = this.cache.get(key);
|
|
3365
|
+
this.cache.delete(key);
|
|
3366
|
+
this.cache.set(key, value);
|
|
3367
|
+
return value;
|
|
3368
|
+
}
|
|
3369
|
+
/**
|
|
3370
|
+
* 设置值
|
|
3371
|
+
*/
|
|
3372
|
+
set(key, value) {
|
|
3373
|
+
if (this.cache.has(key)) {
|
|
3374
|
+
this.cache.delete(key);
|
|
3375
|
+
} else if (this.cache.size >= this.capacity) {
|
|
3376
|
+
const firstKey = this.cache.keys().next().value;
|
|
3377
|
+
if (firstKey !== void 0) {
|
|
3378
|
+
this.cache.delete(firstKey);
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
this.cache.set(key, value);
|
|
3382
|
+
}
|
|
3383
|
+
/**
|
|
3384
|
+
* 删除键
|
|
3385
|
+
*/
|
|
3386
|
+
delete(key) {
|
|
3387
|
+
return this.cache.delete(key);
|
|
3388
|
+
}
|
|
3389
|
+
/**
|
|
3390
|
+
* 清空缓存
|
|
3391
|
+
*/
|
|
3392
|
+
clear() {
|
|
3393
|
+
this.cache.clear();
|
|
3394
|
+
}
|
|
3395
|
+
/**
|
|
3396
|
+
* 判断是否包含键
|
|
3397
|
+
*/
|
|
3398
|
+
has(key) {
|
|
3399
|
+
return this.cache.has(key);
|
|
3400
|
+
}
|
|
3401
|
+
/**
|
|
3402
|
+
* 获取缓存大小
|
|
3403
|
+
*/
|
|
3404
|
+
size() {
|
|
3405
|
+
return this.cache.size;
|
|
3406
|
+
}
|
|
3407
|
+
/**
|
|
3408
|
+
* 获取所有键
|
|
3409
|
+
*/
|
|
3410
|
+
keys() {
|
|
3411
|
+
return Array.from(this.cache.keys()).filter((key) => key !== void 0);
|
|
3412
|
+
}
|
|
3413
|
+
/**
|
|
3414
|
+
* 获取所有值
|
|
3415
|
+
*/
|
|
3416
|
+
values() {
|
|
3417
|
+
return Array.from(this.cache.values());
|
|
3418
|
+
}
|
|
3419
|
+
};
|
|
3420
|
+
|
|
3421
|
+
// src/data/algorithm/index.ts
|
|
3422
|
+
function binarySearch(array, target, compareFn) {
|
|
3423
|
+
let left = 0;
|
|
3424
|
+
let right = array.length - 1;
|
|
3425
|
+
const compare = compareFn || ((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
3426
|
+
while (left <= right) {
|
|
3427
|
+
const mid = Math.floor((left + right) / 2);
|
|
3428
|
+
const comparison = compare(array[mid], target);
|
|
3429
|
+
if (comparison === 0) {
|
|
3430
|
+
return mid;
|
|
3431
|
+
} else if (comparison < 0) {
|
|
3432
|
+
left = mid + 1;
|
|
3433
|
+
} else {
|
|
3434
|
+
right = mid - 1;
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
return -1;
|
|
3438
|
+
}
|
|
3439
|
+
function quickSort(array, compareFn) {
|
|
3440
|
+
if (array.length <= 1) {
|
|
3441
|
+
return [...array];
|
|
3442
|
+
}
|
|
3443
|
+
const compare = compareFn || ((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
3444
|
+
const pivot = array[Math.floor(array.length / 2)];
|
|
3445
|
+
const left = [];
|
|
3446
|
+
const right = [];
|
|
3447
|
+
const equal = [];
|
|
3448
|
+
for (const item of array) {
|
|
3449
|
+
const comparison = compare(item, pivot);
|
|
3450
|
+
if (comparison < 0) {
|
|
3451
|
+
left.push(item);
|
|
3452
|
+
} else if (comparison > 0) {
|
|
3453
|
+
right.push(item);
|
|
3454
|
+
} else {
|
|
3455
|
+
equal.push(item);
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
return [...quickSort(left, compareFn), ...equal, ...quickSort(right, compareFn)];
|
|
3459
|
+
}
|
|
3460
|
+
function mergeSort(array, compareFn) {
|
|
3461
|
+
if (array.length <= 1) {
|
|
3462
|
+
return [...array];
|
|
3463
|
+
}
|
|
3464
|
+
const compare = compareFn || ((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
3465
|
+
const mid = Math.floor(array.length / 2);
|
|
3466
|
+
const left = mergeSort(array.slice(0, mid), compareFn);
|
|
3467
|
+
const right = mergeSort(array.slice(mid), compareFn);
|
|
3468
|
+
return merge2(left, right, compare);
|
|
3469
|
+
}
|
|
3470
|
+
function merge2(left, right, compareFn) {
|
|
3471
|
+
const result = [];
|
|
3472
|
+
let i = 0;
|
|
3473
|
+
let j = 0;
|
|
3474
|
+
while (i < left.length && j < right.length) {
|
|
3475
|
+
if (compareFn(left[i], right[j]) <= 0) {
|
|
3476
|
+
result.push(left[i]);
|
|
3477
|
+
i++;
|
|
3478
|
+
} else {
|
|
3479
|
+
result.push(right[j]);
|
|
3480
|
+
j++;
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
return result.concat(left.slice(i)).concat(right.slice(j));
|
|
3484
|
+
}
|
|
3485
|
+
function bubbleSort(array, compareFn) {
|
|
3486
|
+
const result = [...array];
|
|
3487
|
+
const compare = compareFn || ((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
3488
|
+
const n = result.length;
|
|
3489
|
+
for (let i = 0; i < n - 1; i++) {
|
|
3490
|
+
let swapped = false;
|
|
3491
|
+
for (let j = 0; j < n - i - 1; j++) {
|
|
3492
|
+
if (compare(result[j], result[j + 1]) > 0) {
|
|
3493
|
+
[result[j], result[j + 1]] = [result[j + 1], result[j]];
|
|
3494
|
+
swapped = true;
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
if (!swapped) {
|
|
3498
|
+
break;
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
return result;
|
|
3502
|
+
}
|
|
3503
|
+
function fibonacci(n) {
|
|
3504
|
+
if (n < 0) {
|
|
3505
|
+
throw new Error("n must be non-negative");
|
|
3506
|
+
}
|
|
3507
|
+
if (n === 0) return 0;
|
|
3508
|
+
if (n === 1) return 1;
|
|
3509
|
+
let a = 0;
|
|
3510
|
+
let b = 1;
|
|
3511
|
+
for (let i = 2; i <= n; i++) {
|
|
3512
|
+
[a, b] = [b, a + b];
|
|
3513
|
+
}
|
|
3514
|
+
return b;
|
|
3515
|
+
}
|
|
3516
|
+
function fibonacciSequence(n) {
|
|
3517
|
+
if (n <= 0) return [];
|
|
3518
|
+
if (n === 1) return [0];
|
|
3519
|
+
if (n === 2) return [0, 1];
|
|
3520
|
+
const sequence = [0, 1];
|
|
3521
|
+
for (let i = 2; i < n; i++) {
|
|
3522
|
+
sequence.push(sequence[i - 1] + sequence[i - 2]);
|
|
3523
|
+
}
|
|
3524
|
+
return sequence;
|
|
3525
|
+
}
|
|
3526
|
+
function factorial(n) {
|
|
3527
|
+
if (n < 0) {
|
|
3528
|
+
throw new Error("n must be non-negative");
|
|
3529
|
+
}
|
|
3530
|
+
if (n === 0 || n === 1) {
|
|
3531
|
+
return 1;
|
|
3532
|
+
}
|
|
3533
|
+
let result = 1;
|
|
3534
|
+
for (let i = 2; i <= n; i++) {
|
|
3535
|
+
result *= i;
|
|
3536
|
+
}
|
|
3537
|
+
return result;
|
|
3538
|
+
}
|
|
3539
|
+
function gcd(a, b) {
|
|
3540
|
+
a = Math.abs(a);
|
|
3541
|
+
b = Math.abs(b);
|
|
3542
|
+
while (b !== 0) {
|
|
3543
|
+
[a, b] = [b, a % b];
|
|
3544
|
+
}
|
|
3545
|
+
return a;
|
|
3546
|
+
}
|
|
3547
|
+
function lcm(a, b) {
|
|
3548
|
+
return Math.abs(a * b) / gcd(a, b);
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
// src/helper/performance/index.ts
|
|
3552
|
+
function debounce(fn, delay) {
|
|
3553
|
+
let timeoutId = null;
|
|
3554
|
+
return function(...args) {
|
|
3555
|
+
if (timeoutId) {
|
|
3556
|
+
clearTimeout(timeoutId);
|
|
3557
|
+
}
|
|
3558
|
+
timeoutId = setTimeout(() => {
|
|
3559
|
+
fn.apply(this, args);
|
|
3560
|
+
timeoutId = null;
|
|
3561
|
+
}, delay);
|
|
3562
|
+
};
|
|
3563
|
+
}
|
|
3564
|
+
function throttle(fn, delay) {
|
|
3565
|
+
let lastCall = 0;
|
|
3566
|
+
let timeoutId = null;
|
|
3567
|
+
return function(...args) {
|
|
3568
|
+
const now = Date.now();
|
|
3569
|
+
const timeSinceLastCall = now - lastCall;
|
|
3570
|
+
if (timeSinceLastCall >= delay) {
|
|
3571
|
+
lastCall = now;
|
|
3572
|
+
fn.apply(this, args);
|
|
3573
|
+
} else {
|
|
3574
|
+
if (timeoutId) {
|
|
3575
|
+
clearTimeout(timeoutId);
|
|
3576
|
+
}
|
|
3577
|
+
timeoutId = setTimeout(() => {
|
|
3578
|
+
lastCall = Date.now();
|
|
3579
|
+
fn.apply(this, args);
|
|
3580
|
+
timeoutId = null;
|
|
3581
|
+
}, delay - timeSinceLastCall);
|
|
3582
|
+
}
|
|
3583
|
+
};
|
|
3584
|
+
}
|
|
3585
|
+
function memoize(fn, keyFn) {
|
|
3586
|
+
const cache = /* @__PURE__ */ new Map();
|
|
3587
|
+
return ((...args) => {
|
|
3588
|
+
const key = keyFn ? keyFn(...args) : JSON.stringify(args);
|
|
3589
|
+
if (cache.has(key)) {
|
|
3590
|
+
return cache.get(key);
|
|
3591
|
+
}
|
|
3592
|
+
const result = fn(...args);
|
|
3593
|
+
cache.set(key, result);
|
|
3594
|
+
return result;
|
|
3595
|
+
});
|
|
3596
|
+
}
|
|
3597
|
+
function once(fn) {
|
|
3598
|
+
let called = false;
|
|
3599
|
+
let result;
|
|
3600
|
+
return ((...args) => {
|
|
3601
|
+
if (!called) {
|
|
3602
|
+
called = true;
|
|
3603
|
+
result = fn(...args);
|
|
3604
|
+
}
|
|
3605
|
+
return result;
|
|
3606
|
+
});
|
|
3607
|
+
}
|
|
3608
|
+
async function retry(fn, retryCount = 3, retryDelay = 1e3) {
|
|
3609
|
+
let lastError = null;
|
|
3610
|
+
for (let i = 0; i <= retryCount; i++) {
|
|
3611
|
+
try {
|
|
3612
|
+
return await fn();
|
|
3613
|
+
} catch (error) {
|
|
3614
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3615
|
+
if (i < retryCount) {
|
|
3616
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
throw lastError || new Error("Retry failed");
|
|
3621
|
+
}
|
|
3622
|
+
async function timeout(fn, timeout2) {
|
|
3623
|
+
return Promise.race([
|
|
3624
|
+
fn(),
|
|
3625
|
+
new Promise((_, reject) => {
|
|
3626
|
+
setTimeout(() => reject(new Error(`Operation timeout after ${timeout2}ms`)), timeout2);
|
|
3627
|
+
})
|
|
3628
|
+
]);
|
|
3629
|
+
}
|
|
3630
|
+
async function batch(items, fn, batchSize = 10) {
|
|
3631
|
+
const results = [];
|
|
3632
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
3633
|
+
const batch2 = items.slice(i, i + batchSize);
|
|
3634
|
+
const batchResults = await Promise.all(batch2.map(fn));
|
|
3635
|
+
results.push(...batchResults);
|
|
3636
|
+
}
|
|
3637
|
+
return results;
|
|
3638
|
+
}
|
|
3639
|
+
var Queue = class {
|
|
3640
|
+
constructor() {
|
|
3641
|
+
this.items = [];
|
|
3642
|
+
this.processing = false;
|
|
3643
|
+
}
|
|
3644
|
+
/**
|
|
3645
|
+
* 添加项到队列
|
|
3646
|
+
* @param item - 要添加的项
|
|
3647
|
+
*/
|
|
3648
|
+
enqueue(item) {
|
|
3649
|
+
this.items.push(item);
|
|
3650
|
+
if (!this.processing && this.processor) {
|
|
3651
|
+
this.processItems();
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
/**
|
|
3655
|
+
* 设置处理函数并开始处理
|
|
3656
|
+
* @param processor - 处理函数
|
|
3657
|
+
*/
|
|
3658
|
+
async start(processor) {
|
|
3659
|
+
this.processor = processor;
|
|
3660
|
+
if (!this.processing) {
|
|
3661
|
+
await this.processItems();
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
/**
|
|
3665
|
+
* 处理队列
|
|
3666
|
+
*/
|
|
3667
|
+
async processItems() {
|
|
3668
|
+
if (!this.processor || this.processing) return;
|
|
3669
|
+
this.processing = true;
|
|
3670
|
+
while (this.items.length > 0) {
|
|
3671
|
+
const item = this.items.shift();
|
|
3672
|
+
if (item && this.processor) {
|
|
3673
|
+
try {
|
|
3674
|
+
await this.processor(item);
|
|
3675
|
+
} catch (error) {
|
|
3676
|
+
console.error("Queue processing error:", error);
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
this.processing = false;
|
|
3681
|
+
}
|
|
3682
|
+
/**
|
|
3683
|
+
* 清空队列
|
|
3684
|
+
*/
|
|
3685
|
+
clear() {
|
|
3686
|
+
this.items = [];
|
|
3687
|
+
}
|
|
3688
|
+
/**
|
|
3689
|
+
* 获取队列长度
|
|
3690
|
+
*/
|
|
3691
|
+
get length() {
|
|
3692
|
+
return this.items.length;
|
|
3693
|
+
}
|
|
3694
|
+
};
|
|
3695
|
+
|
|
3696
|
+
// src/helper/tracking/index.ts
|
|
3697
|
+
var Tracker = class {
|
|
3698
|
+
constructor(options) {
|
|
3699
|
+
this.eventQueue = [];
|
|
3700
|
+
this.batchTimer = null;
|
|
3701
|
+
this.userInfo = {};
|
|
3702
|
+
this.exposureObservers = /* @__PURE__ */ new Map();
|
|
3703
|
+
this.exposureTimers = /* @__PURE__ */ new Map();
|
|
3704
|
+
this.exposedElements = /* @__PURE__ */ new Set();
|
|
3705
|
+
if (!options.endpoint && !options.customSend) {
|
|
3706
|
+
throw new Error("Either endpoint or customSend must be provided");
|
|
3707
|
+
}
|
|
3708
|
+
this.options = {
|
|
3709
|
+
endpoint: options.endpoint || "",
|
|
3710
|
+
batchSize: options.batchSize ?? 10,
|
|
3711
|
+
batchDelay: options.batchDelay ?? 3e3,
|
|
3712
|
+
autoTrackPageView: options.autoTrackPageView ?? true,
|
|
3713
|
+
autoTrackClick: options.autoTrackClick ?? false,
|
|
3714
|
+
commonParams: options.commonParams || {},
|
|
3715
|
+
customSend: options.customSend,
|
|
3716
|
+
debug: options.debug ?? false
|
|
3717
|
+
};
|
|
3718
|
+
this.sessionId = this.generateSessionId();
|
|
3719
|
+
if (this.options.autoTrackPageView && typeof window !== "undefined") {
|
|
3720
|
+
this.trackPageView();
|
|
3721
|
+
this.setupPageViewListener();
|
|
3722
|
+
}
|
|
3723
|
+
if (this.options.autoTrackClick && typeof window !== "undefined") {
|
|
3724
|
+
this.setupClickListener();
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
/**
|
|
3728
|
+
* 生成会话ID
|
|
3729
|
+
*/
|
|
3730
|
+
generateSessionId() {
|
|
3731
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
3732
|
+
}
|
|
3733
|
+
/**
|
|
3734
|
+
* 获取公共数据
|
|
3735
|
+
*/
|
|
3736
|
+
getCommonData() {
|
|
3737
|
+
const data = {
|
|
3738
|
+
timestamp: Date.now(),
|
|
3739
|
+
sessionId: this.sessionId,
|
|
3740
|
+
...this.options.commonParams,
|
|
3741
|
+
...this.userInfo
|
|
3742
|
+
};
|
|
3743
|
+
if (typeof window !== "undefined") {
|
|
3744
|
+
data.url = window.location.href;
|
|
3745
|
+
data.userAgent = navigator.userAgent;
|
|
3746
|
+
}
|
|
3747
|
+
return data;
|
|
3748
|
+
}
|
|
3749
|
+
/**
|
|
3750
|
+
* 添加事件到队列
|
|
3751
|
+
*/
|
|
3752
|
+
enqueue(event) {
|
|
3753
|
+
this.eventQueue.push(event);
|
|
3754
|
+
if (this.options.debug) {
|
|
3755
|
+
console.log("[Tracker] Event enqueued:", event);
|
|
3756
|
+
}
|
|
3757
|
+
if (this.eventQueue.length >= this.options.batchSize) {
|
|
3758
|
+
this.flush();
|
|
3759
|
+
} else {
|
|
3760
|
+
this.scheduleBatch();
|
|
3761
|
+
}
|
|
3762
|
+
}
|
|
3763
|
+
/**
|
|
3764
|
+
* 安排批量上报
|
|
3765
|
+
*/
|
|
3766
|
+
scheduleBatch() {
|
|
3767
|
+
if (this.batchTimer) {
|
|
3768
|
+
clearTimeout(this.batchTimer);
|
|
3769
|
+
}
|
|
3770
|
+
this.batchTimer = setTimeout(() => {
|
|
3771
|
+
this.flush();
|
|
3772
|
+
}, this.options.batchDelay);
|
|
3773
|
+
}
|
|
3774
|
+
/**
|
|
3775
|
+
* 上报事件
|
|
3776
|
+
*/
|
|
3777
|
+
async sendEvents(events) {
|
|
3778
|
+
if (events.length === 0) return;
|
|
3779
|
+
try {
|
|
3780
|
+
if (this.options.customSend) {
|
|
3781
|
+
await this.options.customSend(events);
|
|
3782
|
+
} else if (this.options.endpoint) {
|
|
3783
|
+
await this.sendToEndpoint(events);
|
|
3784
|
+
}
|
|
3785
|
+
if (this.options.debug) {
|
|
3786
|
+
console.log("[Tracker] Events sent:", events);
|
|
3787
|
+
}
|
|
3788
|
+
} catch (error) {
|
|
3789
|
+
console.error("[Tracker] Failed to send events:", error);
|
|
3790
|
+
this.eventQueue.unshift(...events);
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3793
|
+
/**
|
|
3794
|
+
* 发送到上报接口
|
|
3795
|
+
*/
|
|
3796
|
+
async sendToEndpoint(events) {
|
|
3797
|
+
const response = await fetch(this.options.endpoint, {
|
|
3798
|
+
method: "POST",
|
|
3799
|
+
headers: {
|
|
3800
|
+
"Content-Type": "application/json"
|
|
3801
|
+
},
|
|
3802
|
+
body: JSON.stringify({ events })
|
|
3803
|
+
});
|
|
3804
|
+
if (!response.ok) {
|
|
3805
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
/**
|
|
3809
|
+
* 立即上报所有待上报事件
|
|
3810
|
+
*/
|
|
3811
|
+
async flush() {
|
|
3812
|
+
if (this.batchTimer) {
|
|
3813
|
+
clearTimeout(this.batchTimer);
|
|
3814
|
+
this.batchTimer = null;
|
|
3815
|
+
}
|
|
3816
|
+
if (this.eventQueue.length === 0) return;
|
|
3817
|
+
const events = [...this.eventQueue];
|
|
3818
|
+
this.eventQueue = [];
|
|
3819
|
+
await this.sendEvents(events);
|
|
3820
|
+
}
|
|
3821
|
+
/**
|
|
3822
|
+
* 追踪事件
|
|
3823
|
+
*/
|
|
3824
|
+
trackEvent(name, params) {
|
|
3825
|
+
const event = {
|
|
3826
|
+
type: "event",
|
|
3827
|
+
name,
|
|
3828
|
+
params,
|
|
3829
|
+
...this.getCommonData()
|
|
3830
|
+
};
|
|
3831
|
+
this.enqueue(event);
|
|
3832
|
+
}
|
|
3833
|
+
/**
|
|
3834
|
+
* 追踪页面浏览
|
|
3835
|
+
*/
|
|
3836
|
+
trackPageView(params) {
|
|
3837
|
+
const event = {
|
|
3838
|
+
type: "pageview",
|
|
3839
|
+
name: "pageview",
|
|
3840
|
+
params: {
|
|
3841
|
+
path: typeof window !== "undefined" ? window.location.pathname : void 0,
|
|
3842
|
+
search: typeof window !== "undefined" ? window.location.search : void 0,
|
|
3843
|
+
hash: typeof window !== "undefined" ? window.location.hash : void 0,
|
|
3844
|
+
...params
|
|
3845
|
+
},
|
|
3846
|
+
...this.getCommonData()
|
|
3847
|
+
};
|
|
3848
|
+
this.enqueue(event);
|
|
3849
|
+
}
|
|
3850
|
+
/**
|
|
3851
|
+
* 追踪点击事件
|
|
3852
|
+
*/
|
|
3853
|
+
trackClick(element, params) {
|
|
3854
|
+
const el = typeof element === "string" ? document.querySelector(element) : element;
|
|
3855
|
+
if (!el) {
|
|
3856
|
+
if (this.options.debug) {
|
|
3857
|
+
console.warn("[Tracker] Element not found:", element);
|
|
3858
|
+
}
|
|
3859
|
+
return;
|
|
3860
|
+
}
|
|
3861
|
+
const event = {
|
|
3862
|
+
type: "click",
|
|
3863
|
+
name: "click",
|
|
3864
|
+
params: {
|
|
3865
|
+
element: el.tagName.toLowerCase(),
|
|
3866
|
+
id: el.id || void 0,
|
|
3867
|
+
className: el.className || void 0,
|
|
3868
|
+
text: el.textContent?.trim().substring(0, 100) || void 0,
|
|
3869
|
+
...params
|
|
3870
|
+
},
|
|
3871
|
+
...this.getCommonData()
|
|
3872
|
+
};
|
|
3873
|
+
this.enqueue(event);
|
|
3874
|
+
}
|
|
3875
|
+
/**
|
|
3876
|
+
* 追踪曝光事件
|
|
3877
|
+
*/
|
|
3878
|
+
trackExposure(element, options, params) {
|
|
3879
|
+
if (typeof window === "undefined" || !window.IntersectionObserver) {
|
|
3880
|
+
if (this.options.debug) {
|
|
3881
|
+
console.warn("[Tracker] IntersectionObserver is not supported");
|
|
3882
|
+
}
|
|
3883
|
+
return;
|
|
3884
|
+
}
|
|
3885
|
+
const el = typeof element === "string" ? document.querySelector(element) : element;
|
|
3886
|
+
if (!el) {
|
|
3887
|
+
if (this.options.debug) {
|
|
3888
|
+
console.warn("[Tracker] Element not found:", element);
|
|
3889
|
+
}
|
|
3890
|
+
return;
|
|
3891
|
+
}
|
|
3892
|
+
const { threshold = 0.5, duration = 1e3, once: once2 = true } = options || {};
|
|
3893
|
+
const elementId = `${Date.now()}-${Math.random()}`;
|
|
3894
|
+
if (once2 && this.exposedElements.has(el)) {
|
|
3895
|
+
return;
|
|
3896
|
+
}
|
|
3897
|
+
let exposureTimer = null;
|
|
3898
|
+
let startTime = null;
|
|
3899
|
+
const observer = new IntersectionObserver(
|
|
3900
|
+
(entries) => {
|
|
3901
|
+
entries.forEach((entry) => {
|
|
3902
|
+
if (entry.isIntersecting && entry.intersectionRatio >= threshold) {
|
|
3903
|
+
if (!startTime) {
|
|
3904
|
+
startTime = Date.now();
|
|
3905
|
+
const currentStartTime = startTime;
|
|
3906
|
+
exposureTimer = setTimeout(() => {
|
|
3907
|
+
const event = {
|
|
3908
|
+
type: "exposure",
|
|
3909
|
+
name: "exposure",
|
|
3910
|
+
params: {
|
|
3911
|
+
element: el.tagName.toLowerCase(),
|
|
3912
|
+
id: el.id || void 0,
|
|
3913
|
+
className: el.className || void 0,
|
|
3914
|
+
duration: currentStartTime ? Date.now() - currentStartTime : duration,
|
|
3915
|
+
...params
|
|
3916
|
+
},
|
|
3917
|
+
...this.getCommonData()
|
|
3918
|
+
};
|
|
3919
|
+
this.enqueue(event);
|
|
3920
|
+
this.exposedElements.add(el);
|
|
3921
|
+
observer.disconnect();
|
|
3922
|
+
this.exposureObservers.delete(elementId);
|
|
3923
|
+
if (exposureTimer) {
|
|
3924
|
+
clearTimeout(exposureTimer);
|
|
3925
|
+
this.exposureTimers.delete(elementId);
|
|
3926
|
+
}
|
|
3927
|
+
}, duration);
|
|
3928
|
+
this.exposureTimers.set(elementId, exposureTimer);
|
|
3929
|
+
}
|
|
3930
|
+
} else {
|
|
3931
|
+
if (exposureTimer) {
|
|
3932
|
+
clearTimeout(exposureTimer);
|
|
3933
|
+
this.exposureTimers.delete(elementId);
|
|
3934
|
+
}
|
|
3935
|
+
startTime = null;
|
|
3936
|
+
}
|
|
3937
|
+
});
|
|
3938
|
+
},
|
|
3939
|
+
{
|
|
3940
|
+
threshold: [threshold]
|
|
3941
|
+
}
|
|
3942
|
+
);
|
|
3943
|
+
observer.observe(el);
|
|
3944
|
+
this.exposureObservers.set(elementId, observer);
|
|
3945
|
+
}
|
|
3946
|
+
/**
|
|
3947
|
+
* 设置用户信息
|
|
3948
|
+
*/
|
|
3949
|
+
setUserInfo(userInfo) {
|
|
3950
|
+
this.userInfo = { ...this.userInfo, ...userInfo };
|
|
3951
|
+
}
|
|
3952
|
+
/**
|
|
3953
|
+
* 设置公共参数
|
|
3954
|
+
*/
|
|
3955
|
+
setCommonParams(params) {
|
|
3956
|
+
this.options.commonParams = { ...this.options.commonParams, ...params };
|
|
3957
|
+
}
|
|
3958
|
+
/**
|
|
3959
|
+
* 设置页面浏览监听器(SPA应用)
|
|
3960
|
+
*/
|
|
3961
|
+
setupPageViewListener() {
|
|
3962
|
+
if (typeof window === "undefined") return;
|
|
3963
|
+
window.addEventListener("popstate", () => {
|
|
3964
|
+
this.trackPageView();
|
|
3965
|
+
});
|
|
3966
|
+
const originalPushState = history.pushState;
|
|
3967
|
+
const originalReplaceState = history.replaceState;
|
|
3968
|
+
history.pushState = function(...args) {
|
|
3969
|
+
originalPushState.apply(history, args);
|
|
3970
|
+
window.dispatchEvent(new Event("pushstate"));
|
|
3971
|
+
};
|
|
3972
|
+
history.replaceState = function(...args) {
|
|
3973
|
+
originalReplaceState.apply(history, args);
|
|
3974
|
+
window.dispatchEvent(new Event("replacestate"));
|
|
3975
|
+
};
|
|
3976
|
+
window.addEventListener("pushstate", () => {
|
|
3977
|
+
this.trackPageView();
|
|
3978
|
+
});
|
|
3979
|
+
window.addEventListener("replacestate", () => {
|
|
3980
|
+
this.trackPageView();
|
|
3981
|
+
});
|
|
3982
|
+
}
|
|
3983
|
+
/**
|
|
3984
|
+
* 设置点击事件监听器
|
|
3985
|
+
*/
|
|
3986
|
+
setupClickListener() {
|
|
3987
|
+
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
3988
|
+
document.addEventListener(
|
|
3989
|
+
"click",
|
|
3990
|
+
(event) => {
|
|
3991
|
+
const target = event.target;
|
|
3992
|
+
if (target) {
|
|
3993
|
+
this.trackClick(target);
|
|
3994
|
+
}
|
|
3995
|
+
},
|
|
3996
|
+
true
|
|
3997
|
+
// 使用捕获阶段
|
|
3998
|
+
);
|
|
3999
|
+
}
|
|
4000
|
+
/**
|
|
4001
|
+
* 清理资源
|
|
4002
|
+
*/
|
|
4003
|
+
destroy() {
|
|
4004
|
+
if (this.batchTimer) {
|
|
4005
|
+
clearTimeout(this.batchTimer);
|
|
4006
|
+
this.batchTimer = null;
|
|
4007
|
+
}
|
|
4008
|
+
this.exposureObservers.forEach((observer) => observer.disconnect());
|
|
4009
|
+
this.exposureObservers.clear();
|
|
4010
|
+
this.exposureTimers.forEach((timer) => clearTimeout(timer));
|
|
4011
|
+
this.exposureTimers.clear();
|
|
4012
|
+
this.flush().catch(console.error);
|
|
4013
|
+
}
|
|
4014
|
+
};
|
|
4015
|
+
function createTracker(options) {
|
|
4016
|
+
return new Tracker(options);
|
|
4017
|
+
}
|
|
4018
|
+
var defaultTracker = null;
|
|
4019
|
+
function initTracker(options) {
|
|
4020
|
+
if (defaultTracker) {
|
|
4021
|
+
console.warn("[Tracker] Default tracker already initialized");
|
|
4022
|
+
return defaultTracker;
|
|
4023
|
+
}
|
|
4024
|
+
defaultTracker = new Tracker(options);
|
|
4025
|
+
return defaultTracker;
|
|
4026
|
+
}
|
|
4027
|
+
function getTracker() {
|
|
4028
|
+
if (!defaultTracker) {
|
|
4029
|
+
throw new Error("Tracker not initialized. Call initTracker() first.");
|
|
4030
|
+
}
|
|
4031
|
+
return defaultTracker;
|
|
4032
|
+
}
|
|
4033
|
+
function trackEvent(name, params) {
|
|
4034
|
+
getTracker().trackEvent(name, params);
|
|
4035
|
+
}
|
|
4036
|
+
function trackPageView(params) {
|
|
4037
|
+
getTracker().trackPageView(params);
|
|
4038
|
+
}
|
|
4039
|
+
function trackClick(element, params) {
|
|
4040
|
+
getTracker().trackClick(element, params);
|
|
4041
|
+
}
|
|
4042
|
+
function trackExposure(element, options, params) {
|
|
4043
|
+
getTracker().trackExposure(element, options, params);
|
|
4044
|
+
}
|
|
4045
|
+
function setUserInfo(userInfo) {
|
|
4046
|
+
getTracker().setUserInfo(userInfo);
|
|
4047
|
+
}
|
|
4048
|
+
function setCommonParams(params) {
|
|
4049
|
+
getTracker().setCommonParams(params);
|
|
4050
|
+
}
|
|
4051
|
+
async function flush() {
|
|
4052
|
+
await getTracker().flush();
|
|
4053
|
+
}
|
|
4054
|
+
var serviceEventHandlers = {
|
|
4055
|
+
message: null
|
|
4056
|
+
};
|
|
4057
|
+
function setServiceEventHandlers(handlers = {}) {
|
|
4058
|
+
Object.assign(serviceEventHandlers, handlers);
|
|
4059
|
+
}
|
|
4060
|
+
function emitMessageEvent(payload) {
|
|
4061
|
+
const handler = serviceEventHandlers.message;
|
|
4062
|
+
if (typeof handler === "function") {
|
|
4063
|
+
handler(payload);
|
|
4064
|
+
return;
|
|
4065
|
+
}
|
|
4066
|
+
const { type = "error", message, code, status } = payload;
|
|
4067
|
+
const prefix = `[service][${type}]`;
|
|
4068
|
+
const extra = [];
|
|
4069
|
+
if (typeof code !== "undefined") extra.push(`code=${code}`);
|
|
4070
|
+
if (typeof status !== "undefined") extra.push(`status=${status}`);
|
|
4071
|
+
const suffix = extra.length ? ` (${extra.join(", ")})` : "";
|
|
4072
|
+
if (type === "info" || type === "success") {
|
|
4073
|
+
console.info(`${prefix} ${message}${suffix}`);
|
|
4074
|
+
} else if (type === "warn" || type === "warning") {
|
|
4075
|
+
console.warn(`${prefix} ${message}${suffix}`);
|
|
4076
|
+
} else {
|
|
4077
|
+
console.error(`${prefix} ${message}${suffix}`);
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
function filterEmptyKey(params, emptyString) {
|
|
4081
|
+
Object.keys(params).forEach((key) => {
|
|
4082
|
+
if (params[key] === null || emptyString && params[key] === "" || emptyString && Array.isArray(params[key]) && params[key].length <= 0) {
|
|
4083
|
+
delete params[key];
|
|
4084
|
+
}
|
|
4085
|
+
});
|
|
4086
|
+
}
|
|
4087
|
+
function getTransformRequest(contentType) {
|
|
4088
|
+
if (!contentType) {
|
|
4089
|
+
return (data) => Qs__default.default.stringify(data);
|
|
4090
|
+
}
|
|
4091
|
+
const normalizedType = contentType.toLowerCase().trim();
|
|
4092
|
+
if (normalizedType.startsWith("application/json")) {
|
|
4093
|
+
return (data) => JSON.stringify(data);
|
|
4094
|
+
}
|
|
4095
|
+
if (normalizedType.startsWith("application/x-www-form-urlencoded")) {
|
|
4096
|
+
return (data) => Qs__default.default.stringify(data);
|
|
4097
|
+
}
|
|
4098
|
+
return void 0;
|
|
4099
|
+
}
|
|
4100
|
+
var service = axios__default.default.create({
|
|
4101
|
+
baseURL: "",
|
|
4102
|
+
timeout: 6e4,
|
|
4103
|
+
// 不设置默认 transformRequest,根据 Content-Type 自动选择
|
|
4104
|
+
headers: {
|
|
4105
|
+
"Cache-Control": "no-cache",
|
|
4106
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
4107
|
+
"X-Requested-With": "XMLHttpRequest"
|
|
4108
|
+
}
|
|
4109
|
+
});
|
|
4110
|
+
service.interceptors.request.use(
|
|
4111
|
+
async (config) => {
|
|
4112
|
+
const contentType = config.headers?.["Content-Type"] || config.headers?.["content-type"];
|
|
4113
|
+
if (!config.transformRequest && contentType) {
|
|
4114
|
+
const transformRequest = getTransformRequest(
|
|
4115
|
+
typeof contentType === "string" ? contentType : void 0
|
|
4116
|
+
);
|
|
4117
|
+
if (transformRequest) {
|
|
4118
|
+
config.transformRequest = transformRequest;
|
|
4119
|
+
}
|
|
4120
|
+
} else if (!config.transformRequest) {
|
|
4121
|
+
config.transformRequest = (data) => Qs__default.default.stringify(data);
|
|
4122
|
+
}
|
|
4123
|
+
if (config.method === "post" || config.method === "put" || config.method === "patch") {
|
|
4124
|
+
const params = { ...config.data || {} };
|
|
4125
|
+
filterEmptyKey(params, config.emptyParams ?? false);
|
|
4126
|
+
config.data = params;
|
|
4127
|
+
} else if (config.method === "get" || config.method === "delete") {
|
|
4128
|
+
if (!config.disableTimestamp) {
|
|
4129
|
+
config.params = {
|
|
4130
|
+
_t: Math.floor(Date.now() / 1e3),
|
|
4131
|
+
...config.params || {}
|
|
4132
|
+
};
|
|
4133
|
+
}
|
|
4134
|
+
filterEmptyKey(config.params, true);
|
|
4135
|
+
}
|
|
4136
|
+
return config;
|
|
4137
|
+
},
|
|
4138
|
+
(error) => Promise.reject(error)
|
|
4139
|
+
);
|
|
4140
|
+
function extractErrorMessage(responseData) {
|
|
4141
|
+
return responseData?.errorMessage || responseData?.message || responseData?.msg || "Unknown server error";
|
|
4142
|
+
}
|
|
4143
|
+
function handle401Error(config, error) {
|
|
4144
|
+
emitMessageEvent({
|
|
4145
|
+
type: "info",
|
|
4146
|
+
message: "Authentication required",
|
|
4147
|
+
status: 401,
|
|
4148
|
+
config,
|
|
4149
|
+
error
|
|
4150
|
+
});
|
|
4151
|
+
}
|
|
4152
|
+
function isServiceResponseData(data) {
|
|
4153
|
+
return typeof data === "object" && data !== null && !(data instanceof Blob) && ("success" in data || "code" in data || "data" in data || "message" in data || "msg" in data);
|
|
4154
|
+
}
|
|
4155
|
+
function hasDataField(responseData) {
|
|
4156
|
+
return "data" in responseData && responseData.data !== void 0;
|
|
4157
|
+
}
|
|
4158
|
+
function extractResponseData(response) {
|
|
4159
|
+
const { data } = response;
|
|
4160
|
+
if (data instanceof Blob) {
|
|
4161
|
+
return data;
|
|
4162
|
+
}
|
|
4163
|
+
if (!isServiceResponseData(data)) {
|
|
4164
|
+
return data;
|
|
4165
|
+
}
|
|
4166
|
+
if (hasDataField(data)) {
|
|
4167
|
+
return data.data;
|
|
4168
|
+
}
|
|
4169
|
+
return data;
|
|
4170
|
+
}
|
|
4171
|
+
service.interceptors.response.use(
|
|
4172
|
+
(res) => {
|
|
4173
|
+
const { data, config } = res;
|
|
4174
|
+
const extendedConfig = config;
|
|
4175
|
+
if (extendedConfig.file && res.status === 200 && data instanceof Blob) {
|
|
4176
|
+
convertRes2Blob(res);
|
|
4177
|
+
return void 0;
|
|
4178
|
+
}
|
|
4179
|
+
if (!isServiceResponseData(data)) {
|
|
4180
|
+
return data;
|
|
4181
|
+
}
|
|
4182
|
+
const responseData = data;
|
|
4183
|
+
if ((!responseData.success || responseData.success === "false") && responseData.code !== 200 && responseData.code !== 0) {
|
|
4184
|
+
const msg = extractErrorMessage(responseData);
|
|
4185
|
+
const code = responseData.errorCode || responseData.code || -1e3;
|
|
4186
|
+
if (!extendedConfig.hidden) {
|
|
4187
|
+
emitMessageEvent({
|
|
4188
|
+
type: code === 401 ? "info" : "error",
|
|
4189
|
+
message: `${extendedConfig.action || "Request"} failed: ${msg}`,
|
|
4190
|
+
code,
|
|
4191
|
+
status: res.status,
|
|
4192
|
+
config: extendedConfig
|
|
4193
|
+
});
|
|
4194
|
+
}
|
|
4195
|
+
return Promise.reject({ code, message: msg });
|
|
4196
|
+
}
|
|
4197
|
+
const extractedData = extractResponseData(res);
|
|
4198
|
+
return extractedData;
|
|
4199
|
+
},
|
|
4200
|
+
(error) => {
|
|
4201
|
+
try {
|
|
4202
|
+
const axiosError = error;
|
|
4203
|
+
const config = axiosError.config || {};
|
|
4204
|
+
const status = axiosError.response?.status;
|
|
4205
|
+
const messageText = String(axiosError?.message || "");
|
|
4206
|
+
const isAborted = axiosError.code === "ECONNABORTED";
|
|
4207
|
+
if (status === 401) {
|
|
4208
|
+
handle401Error(config, error);
|
|
4209
|
+
}
|
|
4210
|
+
if (isAborted && config && !config._noAutoRetry) {
|
|
4211
|
+
config._retryCount = config._retryCount || 0;
|
|
4212
|
+
if (config._retryCount < 2) {
|
|
4213
|
+
config._retryCount++;
|
|
4214
|
+
console.debug(
|
|
4215
|
+
`[request] aborted, retry attempt #${config._retryCount} -> ${config.url || ""}`
|
|
4216
|
+
);
|
|
4217
|
+
return new Promise((resolve) => setTimeout(resolve, 300)).then(
|
|
4218
|
+
() => service.request(config)
|
|
4219
|
+
);
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4222
|
+
if (!config?.hidden && !isAborted) {
|
|
4223
|
+
let friendly = messageText;
|
|
4224
|
+
if (status === 403) friendly = "No permission";
|
|
4225
|
+
else if (status === 502) friendly = "System is upgrading or unavailable";
|
|
4226
|
+
else if (status === 504) friendly = "System timeout";
|
|
4227
|
+
emitMessageEvent({
|
|
4228
|
+
type: status === 401 ? "info" : "error",
|
|
4229
|
+
message: `${config.action || "Request"} failed: ${friendly}`,
|
|
4230
|
+
status,
|
|
4231
|
+
config,
|
|
4232
|
+
error
|
|
4233
|
+
});
|
|
4234
|
+
}
|
|
4235
|
+
} catch (e) {
|
|
4236
|
+
console.error("error in response error handler", e);
|
|
4237
|
+
}
|
|
4238
|
+
return Promise.reject(error);
|
|
4239
|
+
}
|
|
4240
|
+
);
|
|
4241
|
+
service.json = function(url, params, config) {
|
|
4242
|
+
const newConfig = {
|
|
4243
|
+
headers: { "Content-Type": "application/json", ...config?.headers },
|
|
4244
|
+
...config
|
|
4245
|
+
};
|
|
4246
|
+
return service.post(
|
|
4247
|
+
url,
|
|
4248
|
+
Array.isArray(params) ? params : params,
|
|
4249
|
+
newConfig
|
|
4250
|
+
);
|
|
4251
|
+
};
|
|
4252
|
+
service.put = function(url, params, config) {
|
|
4253
|
+
const newConfig = {
|
|
4254
|
+
headers: { "Content-Type": "application/json", ...config?.headers },
|
|
4255
|
+
...config
|
|
4256
|
+
};
|
|
4257
|
+
return service.put(url, Array.isArray(params) ? params : params, newConfig).then((res) => res);
|
|
4258
|
+
};
|
|
4259
|
+
service.arrayGet = function(url, config) {
|
|
4260
|
+
if (config) {
|
|
4261
|
+
config.paramsSerializer = (params) => Qs__default.default.stringify(params, { arrayFormat: "repeat" });
|
|
4262
|
+
}
|
|
4263
|
+
return service.get(url, config);
|
|
4264
|
+
};
|
|
4265
|
+
service.download = function(url, config = {}) {
|
|
4266
|
+
const { type = "get", params } = config;
|
|
4267
|
+
const newConfig = {
|
|
4268
|
+
headers: {
|
|
4269
|
+
Accept: "application/json",
|
|
4270
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
4271
|
+
...config.headers
|
|
4272
|
+
},
|
|
4273
|
+
withCredentials: true,
|
|
4274
|
+
file: true,
|
|
4275
|
+
responseType: "blob",
|
|
4276
|
+
...config
|
|
4277
|
+
};
|
|
4278
|
+
if (type === "get") {
|
|
4279
|
+
newConfig.params = params;
|
|
4280
|
+
return service.get(url, newConfig);
|
|
4281
|
+
} else {
|
|
4282
|
+
newConfig.params = {};
|
|
4283
|
+
return service.post(url, params, newConfig).then(() => void 0);
|
|
4284
|
+
}
|
|
4285
|
+
};
|
|
4286
|
+
function convertRes2Blob(response) {
|
|
4287
|
+
const contentDisposition = response.headers["content-disposition"];
|
|
4288
|
+
let fileName = "file.xlsx";
|
|
4289
|
+
if (contentDisposition) {
|
|
4290
|
+
const rfc5987Match = contentDisposition.match(/filename\*=UTF-8''([^;]+)/i);
|
|
4291
|
+
if (rfc5987Match) {
|
|
4292
|
+
fileName = decodeURIComponent(rfc5987Match[1]);
|
|
4293
|
+
} else {
|
|
4294
|
+
const standardMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/i);
|
|
4295
|
+
if (standardMatch) {
|
|
4296
|
+
fileName = standardMatch[1];
|
|
4297
|
+
fileName = fileName.replace(/^['"]|['"]$/g, "");
|
|
4298
|
+
try {
|
|
4299
|
+
fileName = decodeURIComponent(fileName);
|
|
4300
|
+
} catch (e) {
|
|
4301
|
+
try {
|
|
4302
|
+
fileName = decodeURI(fileName);
|
|
4303
|
+
} catch (e2) {
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4308
|
+
fileName = fileName.trim().replace(/^_+|_+$/g, "");
|
|
4309
|
+
}
|
|
4310
|
+
const blob = new Blob([response.data], { type: "application/vnd.ms-excel;charset=utf-8" });
|
|
4311
|
+
if (typeof window.navigator.msSaveBlob !== "undefined") {
|
|
4312
|
+
window.navigator.msSaveBlob(blob, fileName);
|
|
4313
|
+
} else {
|
|
4314
|
+
const blobURL = window.URL.createObjectURL(blob);
|
|
4315
|
+
const tempLink = document.createElement("a");
|
|
4316
|
+
tempLink.style.display = "none";
|
|
4317
|
+
tempLink.href = blobURL;
|
|
4318
|
+
tempLink.setAttribute("download", fileName);
|
|
4319
|
+
if (typeof tempLink.download === "undefined") {
|
|
4320
|
+
tempLink.setAttribute("target", "_blank");
|
|
4321
|
+
}
|
|
4322
|
+
document.body.appendChild(tempLink);
|
|
4323
|
+
tempLink.click();
|
|
4324
|
+
document.body.removeChild(tempLink);
|
|
4325
|
+
window.URL.revokeObjectURL(blobURL);
|
|
4326
|
+
}
|
|
4327
|
+
}
|
|
4328
|
+
|
|
4329
|
+
exports.$ = $;
|
|
4330
|
+
exports.$$ = $$;
|
|
4331
|
+
exports.BinaryTree = BinaryTree;
|
|
4332
|
+
exports.ChunkUploader = ChunkUploader;
|
|
4333
|
+
exports.DataQueue = DataQueue;
|
|
4334
|
+
exports.Graph = Graph;
|
|
4335
|
+
exports.LRUCache = LRUCache;
|
|
4336
|
+
exports.LinkedList = LinkedList;
|
|
4337
|
+
exports.Queue = Queue;
|
|
4338
|
+
exports.Stack = Stack;
|
|
4339
|
+
exports.Tracker = Tracker;
|
|
4340
|
+
exports.UploadStatus = UploadStatus;
|
|
4341
|
+
exports.addClass = addClass;
|
|
4342
|
+
exports.addDays = addDays;
|
|
4343
|
+
exports.addMonths = addMonths;
|
|
4344
|
+
exports.addYears = addYears;
|
|
4345
|
+
exports.aesGCMDecrypt = aesGCMDecrypt;
|
|
4346
|
+
exports.aesGCMEncrypt = aesGCMEncrypt;
|
|
4347
|
+
exports.base64Decode = base64Decode;
|
|
4348
|
+
exports.base64Encode = base64Encode;
|
|
4349
|
+
exports.batch = batch;
|
|
4350
|
+
exports.binarySearch = binarySearch;
|
|
4351
|
+
exports.bubbleSort = bubbleSort;
|
|
4352
|
+
exports.buildUrl = buildUrl;
|
|
4353
|
+
exports.calculateBlobMD5 = calculateBlobMD5;
|
|
4354
|
+
exports.calculateFileMD5 = calculateFileMD5;
|
|
4355
|
+
exports.camelCase = camelCase;
|
|
4356
|
+
exports.capitalize = capitalize;
|
|
4357
|
+
exports.ceil = ceil;
|
|
4358
|
+
exports.checkOnline = checkOnline;
|
|
4359
|
+
exports.chunk = chunk;
|
|
4360
|
+
exports.clamp = clamp;
|
|
4361
|
+
exports.clearPersistedKeys = clearPersistedKeys;
|
|
4362
|
+
exports.compact = compact;
|
|
4363
|
+
exports.computeHMAC = computeHMAC;
|
|
4364
|
+
exports.contrast = contrast;
|
|
4365
|
+
exports.cookie = cookie;
|
|
4366
|
+
exports.copyToClipboard = copyToClipboard;
|
|
4367
|
+
exports.createTracker = createTracker;
|
|
4368
|
+
exports.createTranslator = createTranslator;
|
|
4369
|
+
exports.createUploader = createUploader;
|
|
4370
|
+
exports.csvToJson = csvToJson;
|
|
4371
|
+
exports.darken = darken;
|
|
4372
|
+
exports.debounce = debounce;
|
|
4373
|
+
exports.deepClone = deepClone;
|
|
4374
|
+
exports.deepMerge = deepMerge;
|
|
4375
|
+
exports.defaults = defaults;
|
|
4376
|
+
exports.deriveKeyFromPassword = deriveKeyFromPassword;
|
|
4377
|
+
exports.diffDays = diffDays;
|
|
4378
|
+
exports.difference = difference;
|
|
4379
|
+
exports.downloadFile = downloadFile;
|
|
4380
|
+
exports.drop = drop;
|
|
4381
|
+
exports.dropWhile = dropWhile;
|
|
4382
|
+
exports.endOfDay = endOfDay;
|
|
4383
|
+
exports.escapeHtml = escapeHtml;
|
|
4384
|
+
exports.exportPrivateKey = exportPrivateKey;
|
|
4385
|
+
exports.exportPublicKey = exportPublicKey;
|
|
4386
|
+
exports.factorial = factorial;
|
|
4387
|
+
exports.fetchWithRetry = fetchWithRetry;
|
|
4388
|
+
exports.fetchWithTimeout = fetchWithTimeout;
|
|
4389
|
+
exports.fibonacci = fibonacci;
|
|
4390
|
+
exports.fibonacciSequence = fibonacciSequence;
|
|
4391
|
+
exports.findIndexBy = findIndexBy;
|
|
4392
|
+
exports.flatten = flatten;
|
|
4393
|
+
exports.floor = floor;
|
|
4394
|
+
exports.flush = flush;
|
|
4395
|
+
exports.formatBytes = formatBytes;
|
|
4396
|
+
exports.formatCurrency = formatCurrency;
|
|
4397
|
+
exports.formatCurrencyI18n = formatCurrencyI18n;
|
|
4398
|
+
exports.formatDate = formatDate;
|
|
4399
|
+
exports.formatDateI18n = formatDateI18n;
|
|
4400
|
+
exports.formatFileSize = formatFileSize;
|
|
4401
|
+
exports.formatNumber = formatNumber;
|
|
4402
|
+
exports.formatNumberI18n = formatNumberI18n;
|
|
4403
|
+
exports.formatRelativeTime = formatRelativeTime;
|
|
4404
|
+
exports.gcd = gcd;
|
|
4405
|
+
exports.generateHMACKey = generateHMACKey;
|
|
4406
|
+
exports.generateRSAKeyPair = generateRSAKeyPair;
|
|
4407
|
+
exports.generateRandomString = generateRandomString;
|
|
4408
|
+
exports.generateUUID = generateUUID;
|
|
4409
|
+
exports.get = get;
|
|
4410
|
+
exports.getDateFormatByGMT = getDateFormatByGMT;
|
|
4411
|
+
exports.getElementOffset = getElementOffset;
|
|
4412
|
+
exports.getFileExtension = getFileExtension;
|
|
4413
|
+
exports.getFileNameWithoutExtension = getFileNameWithoutExtension;
|
|
4414
|
+
exports.getKeyUsageCount = getKeyUsageCount;
|
|
4415
|
+
exports.getLocale = getLocale;
|
|
4416
|
+
exports.getQuarter = getQuarter;
|
|
4417
|
+
exports.getQueryParams = getQueryParams;
|
|
4418
|
+
exports.getRelativeTime = getRelativeTime;
|
|
4419
|
+
exports.getScrollPosition = getScrollPosition;
|
|
4420
|
+
exports.getStorageKeyPair = getStorageKeyPair;
|
|
4421
|
+
exports.getStyle = getStyle;
|
|
4422
|
+
exports.getTimeFromGMT = getTimeFromGMT;
|
|
4423
|
+
exports.getTracker = getTracker;
|
|
4424
|
+
exports.getWeekNumber = getWeekNumber;
|
|
4425
|
+
exports.groupBy = groupBy;
|
|
4426
|
+
exports.hash = hash;
|
|
4427
|
+
exports.hexToRgb = hexToRgb;
|
|
4428
|
+
exports.highlight = highlight;
|
|
4429
|
+
exports.hslToRgb = hslToRgb;
|
|
4430
|
+
exports.importPrivateKey = importPrivateKey;
|
|
4431
|
+
exports.importPublicKey = importPublicKey;
|
|
4432
|
+
exports.initTracker = initTracker;
|
|
4433
|
+
exports.initializeStorageKeys = initializeStorageKeys;
|
|
4434
|
+
exports.intersection = intersection;
|
|
4435
|
+
exports.invert = invert;
|
|
4436
|
+
exports.isAbsoluteUrl = isAbsoluteUrl;
|
|
4437
|
+
exports.isBetween = isBetween;
|
|
4438
|
+
exports.isEmpty = isEmpty;
|
|
4439
|
+
exports.isEmptyObject = isEmptyObject;
|
|
4440
|
+
exports.isEqual = isEqual;
|
|
4441
|
+
exports.isFloat = isFloat;
|
|
4442
|
+
exports.isInViewport = isInViewport;
|
|
4443
|
+
exports.isInteger = isInteger;
|
|
4444
|
+
exports.isNumeric = isNumeric;
|
|
4445
|
+
exports.isToday = isToday;
|
|
4446
|
+
exports.isValidDomain = isValidDomain;
|
|
4447
|
+
exports.isValidEmail = isValidEmail;
|
|
4448
|
+
exports.isValidHexColor = isValidHexColor;
|
|
4449
|
+
exports.isValidIP = isValidIP;
|
|
4450
|
+
exports.isValidIdCard = isValidIdCard;
|
|
4451
|
+
exports.isValidJSON = isValidJSON;
|
|
4452
|
+
exports.isValidPhone = isValidPhone;
|
|
4453
|
+
exports.isValidUUID = isValidUUID;
|
|
4454
|
+
exports.isValidUrl = isValidUrl;
|
|
4455
|
+
exports.isWeekday = isWeekday;
|
|
4456
|
+
exports.isWeekend = isWeekend;
|
|
4457
|
+
exports.isYesterday = isYesterday;
|
|
4458
|
+
exports.joinUrl = joinUrl;
|
|
4459
|
+
exports.jsonToCsv = jsonToCsv;
|
|
4460
|
+
exports.jsonToXml = jsonToXml;
|
|
4461
|
+
exports.jsonToYaml = jsonToYaml;
|
|
4462
|
+
exports.kebabCase = kebabCase;
|
|
4463
|
+
exports.keys = keys;
|
|
4464
|
+
exports.lcm = lcm;
|
|
4465
|
+
exports.lighten = lighten;
|
|
4466
|
+
exports.localStorage = localStorage;
|
|
4467
|
+
exports.mapKeys = mapKeys;
|
|
4468
|
+
exports.mapValues = mapValues;
|
|
4469
|
+
exports.mask = mask;
|
|
4470
|
+
exports.maskEmail = maskEmail;
|
|
4471
|
+
exports.maskPhone = maskPhone;
|
|
4472
|
+
exports.memoize = memoize;
|
|
4473
|
+
exports.merge = merge;
|
|
4474
|
+
exports.mergeSort = mergeSort;
|
|
4475
|
+
exports.mix = mix;
|
|
4476
|
+
exports.normalizeUrl = normalizeUrl;
|
|
4477
|
+
exports.omit = omit;
|
|
4478
|
+
exports.omitBy = omitBy;
|
|
4479
|
+
exports.once = once;
|
|
4480
|
+
exports.parseNumber = parseNumber;
|
|
4481
|
+
exports.parseUrl = parseUrl;
|
|
4482
|
+
exports.partition = partition;
|
|
4483
|
+
exports.pascalCase = pascalCase;
|
|
4484
|
+
exports.percent = percent;
|
|
4485
|
+
exports.pick = pick;
|
|
4486
|
+
exports.pickBy = pickBy;
|
|
4487
|
+
exports.pluralize = pluralize;
|
|
4488
|
+
exports.quickSort = quickSort;
|
|
4489
|
+
exports.random = random;
|
|
4490
|
+
exports.removeAccents = removeAccents;
|
|
4491
|
+
exports.removeClass = removeClass;
|
|
4492
|
+
exports.removeQueryParams = removeQueryParams;
|
|
4493
|
+
exports.request = request;
|
|
4494
|
+
exports.resetKeyUsageCount = resetKeyUsageCount;
|
|
4495
|
+
exports.retry = retry;
|
|
4496
|
+
exports.rgbToHex = rgbToHex;
|
|
4497
|
+
exports.rgbToHsl = rgbToHsl;
|
|
4498
|
+
exports.round = round;
|
|
4499
|
+
exports.rsaDecrypt = rsaDecrypt;
|
|
4500
|
+
exports.rsaEncrypt = rsaEncrypt;
|
|
4501
|
+
exports.sample = sample;
|
|
4502
|
+
exports.scrollTo = scrollTo;
|
|
4503
|
+
exports.sessionStorage = sessionStorage;
|
|
4504
|
+
exports.set = set;
|
|
4505
|
+
exports.setCommonParams = setCommonParams;
|
|
4506
|
+
exports.setQueryParams = setQueryParams;
|
|
4507
|
+
exports.setServiceEventHandlers = setServiceEventHandlers;
|
|
4508
|
+
exports.setStorageKeyPair = setStorageKeyPair;
|
|
4509
|
+
exports.setStyle = setStyle;
|
|
4510
|
+
exports.setUserInfo = setUserInfo;
|
|
4511
|
+
exports.sha256 = sha256;
|
|
4512
|
+
exports.shuffle = shuffle;
|
|
4513
|
+
exports.slugify = slugify;
|
|
4514
|
+
exports.snakeCase = snakeCase;
|
|
4515
|
+
exports.sortBy = sortBy;
|
|
4516
|
+
exports.splitFileIntoChunks = splitFileIntoChunks;
|
|
4517
|
+
exports.startOfDay = startOfDay;
|
|
4518
|
+
exports.storage = storage;
|
|
4519
|
+
exports.take = take;
|
|
4520
|
+
exports.takeWhile = takeWhile;
|
|
4521
|
+
exports.template = template;
|
|
4522
|
+
exports.throttle = throttle;
|
|
4523
|
+
exports.timeout = timeout;
|
|
4524
|
+
exports.toFixed = toFixed;
|
|
4525
|
+
exports.toggleClass = toggleClass;
|
|
4526
|
+
exports.trackClick = trackClick;
|
|
4527
|
+
exports.trackEvent = trackEvent;
|
|
4528
|
+
exports.trackExposure = trackExposure;
|
|
4529
|
+
exports.trackPageView = trackPageView;
|
|
4530
|
+
exports.transform = transform;
|
|
4531
|
+
exports.translate = translate;
|
|
4532
|
+
exports.truncate = truncate;
|
|
4533
|
+
exports.unescapeHtml = unescapeHtml;
|
|
4534
|
+
exports.union = union;
|
|
4535
|
+
exports.unique = unique;
|
|
4536
|
+
exports.unzip = unzip;
|
|
4537
|
+
exports.updateQueryParams = updateQueryParams;
|
|
4538
|
+
exports.uploadFile = uploadFile;
|
|
4539
|
+
exports.values = values;
|
|
4540
|
+
exports.verifyHMAC = verifyHMAC;
|
|
4541
|
+
exports.xmlToJson = xmlToJson;
|
|
4542
|
+
exports.yamlToJson = yamlToJson;
|
|
4543
|
+
exports.zip = zip;
|