@alibarbar/common 1.0.10 → 1.1.1
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/algorithm.cjs +1 -1
- package/dist/algorithm.js +1 -1
- package/dist/array.cjs +1 -1
- package/dist/array.js +1 -1
- package/dist/color.cjs +1 -1
- package/dist/color.js +1 -1
- package/dist/crypto.cjs +109 -1
- package/dist/crypto.d.mts +48 -1
- package/dist/crypto.d.ts +48 -1
- package/dist/crypto.js +104 -2
- package/dist/data-structure.cjs +1 -1
- package/dist/data-structure.js +1 -1
- package/dist/date.cjs +1 -1
- package/dist/date.js +1 -1
- package/dist/dom.cjs +1 -1
- package/dist/dom.js +1 -1
- package/dist/file.cjs +1 -1
- package/dist/file.js +1 -1
- package/dist/i18n.cjs +1 -1
- package/dist/i18n.js +1 -1
- package/dist/index.cjs +848 -415
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +838 -413
- package/dist/network.cjs +1 -1
- package/dist/network.js +1 -1
- package/dist/number.cjs +1 -1
- package/dist/number.js +1 -1
- package/dist/object.cjs +1 -1
- package/dist/object.js +1 -1
- package/dist/performance.cjs +1 -1
- package/dist/performance.js +1 -1
- package/dist/storage.cjs +509 -104
- package/dist/storage.d.mts +50 -73
- package/dist/storage.d.ts +50 -73
- package/dist/storage.js +505 -102
- package/dist/string.cjs +1 -1
- package/dist/string.js +1 -1
- package/dist/tracking.cjs +1 -1
- package/dist/tracking.js +1 -1
- package/dist/transform.cjs +1 -1
- package/dist/transform.js +1 -1
- package/dist/upload.cjs +2 -2
- package/dist/upload.d.mts +1 -1
- package/dist/upload.d.ts +1 -1
- package/dist/upload.js +2 -2
- package/dist/url.cjs +1 -1
- package/dist/url.js +1 -1
- package/dist/validation.cjs +1 -1
- package/dist/validation.js +1 -1
- package/package.json +3 -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.js
CHANGED
|
@@ -1,209 +1,7 @@
|
|
|
1
1
|
import Qs from 'qs';
|
|
2
2
|
import axios from 'axios';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __esm = (fn, res) => function __init() {
|
|
7
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
-
};
|
|
9
|
-
var __export = (target, all) => {
|
|
10
|
-
for (var name in all)
|
|
11
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
// src/helper/crypto.ts
|
|
15
|
-
var crypto_exports = {};
|
|
16
|
-
__export(crypto_exports, {
|
|
17
|
-
base64Decode: () => base64Decode,
|
|
18
|
-
base64Encode: () => base64Encode,
|
|
19
|
-
exportPrivateKey: () => exportPrivateKey,
|
|
20
|
-
exportPublicKey: () => exportPublicKey,
|
|
21
|
-
generateRSAKeyPair: () => generateRSAKeyPair,
|
|
22
|
-
generateRandomString: () => generateRandomString,
|
|
23
|
-
generateUUID: () => generateUUID,
|
|
24
|
-
hash: () => hash,
|
|
25
|
-
importPrivateKey: () => importPrivateKey,
|
|
26
|
-
importPublicKey: () => importPublicKey,
|
|
27
|
-
rsaDecrypt: () => rsaDecrypt,
|
|
28
|
-
rsaEncrypt: () => rsaEncrypt,
|
|
29
|
-
sha256: () => sha256
|
|
30
|
-
});
|
|
31
|
-
async function sha256(data) {
|
|
32
|
-
const buffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
33
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
|
|
34
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
35
|
-
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
36
|
-
}
|
|
37
|
-
function base64Encode(data) {
|
|
38
|
-
if (typeof data === "string") {
|
|
39
|
-
return btoa(unescape(encodeURIComponent(data)));
|
|
40
|
-
}
|
|
41
|
-
const bytes = new Uint8Array(data);
|
|
42
|
-
let binary = "";
|
|
43
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
44
|
-
binary += String.fromCharCode(bytes[i]);
|
|
45
|
-
}
|
|
46
|
-
return btoa(binary);
|
|
47
|
-
}
|
|
48
|
-
function base64Decode(data) {
|
|
49
|
-
try {
|
|
50
|
-
return decodeURIComponent(escape(atob(data)));
|
|
51
|
-
} catch {
|
|
52
|
-
throw new Error("Invalid Base64 string");
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
function generateUUID() {
|
|
56
|
-
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
57
|
-
return crypto.randomUUID();
|
|
58
|
-
}
|
|
59
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
60
|
-
const r = Math.random() * 16 | 0;
|
|
61
|
-
const v = c === "x" ? r : r & 3 | 8;
|
|
62
|
-
return v.toString(16);
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
function generateRandomString(length, charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") {
|
|
66
|
-
let result = "";
|
|
67
|
-
for (let i = 0; i < length; i++) {
|
|
68
|
-
result += charset.charAt(Math.floor(Math.random() * charset.length));
|
|
69
|
-
}
|
|
70
|
-
return result;
|
|
71
|
-
}
|
|
72
|
-
function hash(data) {
|
|
73
|
-
let hashValue = 0;
|
|
74
|
-
for (let i = 0; i < data.length; i++) {
|
|
75
|
-
const char = data.charCodeAt(i);
|
|
76
|
-
hashValue = (hashValue << 5) - hashValue + char;
|
|
77
|
-
hashValue = hashValue & hashValue;
|
|
78
|
-
}
|
|
79
|
-
return Math.abs(hashValue);
|
|
80
|
-
}
|
|
81
|
-
async function generateRSAKeyPair(modulusLength = 2048) {
|
|
82
|
-
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
83
|
-
throw new Error("Web Crypto API is not available");
|
|
84
|
-
}
|
|
85
|
-
const keyPair = await crypto.subtle.generateKey(
|
|
86
|
-
{
|
|
87
|
-
name: "RSA-OAEP",
|
|
88
|
-
modulusLength,
|
|
89
|
-
publicExponent: new Uint8Array([1, 0, 1]),
|
|
90
|
-
hash: "SHA-256"
|
|
91
|
-
},
|
|
92
|
-
true,
|
|
93
|
-
["encrypt", "decrypt"]
|
|
94
|
-
);
|
|
95
|
-
return {
|
|
96
|
-
publicKey: keyPair.publicKey,
|
|
97
|
-
privateKey: keyPair.privateKey
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
async function rsaEncrypt(data, publicKey) {
|
|
101
|
-
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
102
|
-
throw new Error("Web Crypto API is not available");
|
|
103
|
-
}
|
|
104
|
-
const encoder = new TextEncoder();
|
|
105
|
-
const dataBuffer = encoder.encode(data);
|
|
106
|
-
const maxChunkSize = 245;
|
|
107
|
-
const chunks = [];
|
|
108
|
-
for (let i = 0; i < dataBuffer.length; i += maxChunkSize) {
|
|
109
|
-
const chunk2 = dataBuffer.slice(i, i + maxChunkSize);
|
|
110
|
-
const encrypted = await crypto.subtle.encrypt(
|
|
111
|
-
{
|
|
112
|
-
name: "RSA-OAEP"
|
|
113
|
-
},
|
|
114
|
-
publicKey,
|
|
115
|
-
chunk2
|
|
116
|
-
);
|
|
117
|
-
chunks.push(encrypted);
|
|
118
|
-
}
|
|
119
|
-
const totalLength = chunks.reduce((sum, chunk2) => sum + chunk2.byteLength, 0);
|
|
120
|
-
const merged = new Uint8Array(totalLength);
|
|
121
|
-
let offset = 0;
|
|
122
|
-
for (const chunk2 of chunks) {
|
|
123
|
-
merged.set(new Uint8Array(chunk2), offset);
|
|
124
|
-
offset += chunk2.byteLength;
|
|
125
|
-
}
|
|
126
|
-
return base64Encode(merged.buffer);
|
|
127
|
-
}
|
|
128
|
-
async function rsaDecrypt(encryptedData, privateKey) {
|
|
129
|
-
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
130
|
-
throw new Error("Web Crypto API is not available");
|
|
131
|
-
}
|
|
132
|
-
const binaryString = atob(encryptedData);
|
|
133
|
-
const encryptedArray = new Uint8Array(binaryString.length);
|
|
134
|
-
for (let i = 0; i < binaryString.length; i++) {
|
|
135
|
-
encryptedArray[i] = binaryString.charCodeAt(i);
|
|
136
|
-
}
|
|
137
|
-
const chunkSize = 256;
|
|
138
|
-
const chunks = [];
|
|
139
|
-
for (let i = 0; i < encryptedArray.length; i += chunkSize) {
|
|
140
|
-
const chunk2 = encryptedArray.slice(i, i + chunkSize);
|
|
141
|
-
const decrypted = await crypto.subtle.decrypt(
|
|
142
|
-
{
|
|
143
|
-
name: "RSA-OAEP"
|
|
144
|
-
},
|
|
145
|
-
privateKey,
|
|
146
|
-
chunk2
|
|
147
|
-
);
|
|
148
|
-
const decoder = new TextDecoder();
|
|
149
|
-
chunks.push(decoder.decode(decrypted));
|
|
150
|
-
}
|
|
151
|
-
return chunks.join("");
|
|
152
|
-
}
|
|
153
|
-
async function exportPublicKey(publicKey) {
|
|
154
|
-
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
155
|
-
throw new Error("Web Crypto API is not available");
|
|
156
|
-
}
|
|
157
|
-
const exported = await crypto.subtle.exportKey("spki", publicKey);
|
|
158
|
-
return base64Encode(exported);
|
|
159
|
-
}
|
|
160
|
-
async function exportPrivateKey(privateKey) {
|
|
161
|
-
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
162
|
-
throw new Error("Web Crypto API is not available");
|
|
163
|
-
}
|
|
164
|
-
const exported = await crypto.subtle.exportKey("pkcs8", privateKey);
|
|
165
|
-
return base64Encode(exported);
|
|
166
|
-
}
|
|
167
|
-
async function importPublicKey(keyData) {
|
|
168
|
-
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
169
|
-
throw new Error("Web Crypto API is not available");
|
|
170
|
-
}
|
|
171
|
-
const keyBuffer = base64Decode(keyData);
|
|
172
|
-
const keyArray = new Uint8Array(keyBuffer.split("").map((char) => char.charCodeAt(0)));
|
|
173
|
-
return crypto.subtle.importKey(
|
|
174
|
-
"spki",
|
|
175
|
-
keyArray.buffer,
|
|
176
|
-
{
|
|
177
|
-
name: "RSA-OAEP",
|
|
178
|
-
hash: "SHA-256"
|
|
179
|
-
},
|
|
180
|
-
true,
|
|
181
|
-
["encrypt"]
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
async function importPrivateKey(keyData) {
|
|
185
|
-
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
186
|
-
throw new Error("Web Crypto API is not available");
|
|
187
|
-
}
|
|
188
|
-
const keyBuffer = base64Decode(keyData);
|
|
189
|
-
const keyArray = new Uint8Array(keyBuffer.split("").map((char) => char.charCodeAt(0)));
|
|
190
|
-
return crypto.subtle.importKey(
|
|
191
|
-
"pkcs8",
|
|
192
|
-
keyArray.buffer,
|
|
193
|
-
{
|
|
194
|
-
name: "RSA-OAEP",
|
|
195
|
-
hash: "SHA-256"
|
|
196
|
-
},
|
|
197
|
-
true,
|
|
198
|
-
["decrypt"]
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
var init_crypto = __esm({
|
|
202
|
-
"src/helper/crypto.ts"() {
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
// src/core/string.ts
|
|
4
|
+
// src/core/string/index.ts
|
|
207
5
|
function capitalize(str) {
|
|
208
6
|
if (!str) return str;
|
|
209
7
|
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
@@ -294,7 +92,7 @@ function escapeRegex(str) {
|
|
|
294
92
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
295
93
|
}
|
|
296
94
|
|
|
297
|
-
// src/core/array.ts
|
|
95
|
+
// src/core/array/index.ts
|
|
298
96
|
function unique(arr) {
|
|
299
97
|
return Array.from(new Set(arr));
|
|
300
98
|
}
|
|
@@ -429,7 +227,7 @@ function dropWhile(arr, predicate) {
|
|
|
429
227
|
return arr.slice(index);
|
|
430
228
|
}
|
|
431
229
|
|
|
432
|
-
// src/core/object.ts
|
|
230
|
+
// src/core/object/index.ts
|
|
433
231
|
function deepClone(obj) {
|
|
434
232
|
if (obj === null || typeof obj !== "object") {
|
|
435
233
|
return obj;
|
|
@@ -666,7 +464,7 @@ function omitBy(obj, predicate) {
|
|
|
666
464
|
return result;
|
|
667
465
|
}
|
|
668
466
|
|
|
669
|
-
// src/core/date.ts
|
|
467
|
+
// src/core/date/index.ts
|
|
670
468
|
function formatDate(date, format = "YYYY-MM-DD HH:mm:ss") {
|
|
671
469
|
const d = typeof date === "number" ? new Date(date) : date;
|
|
672
470
|
const year = d.getFullYear();
|
|
@@ -791,7 +589,7 @@ function getQuarter(date) {
|
|
|
791
589
|
return Math.floor(d.getMonth() / 3) + 1;
|
|
792
590
|
}
|
|
793
591
|
|
|
794
|
-
// src/core/validation.ts
|
|
592
|
+
// src/core/validation/index.ts
|
|
795
593
|
function isValidEmail(email) {
|
|
796
594
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
797
595
|
return emailRegex.test(email);
|
|
@@ -868,7 +666,7 @@ function isFloat(value) {
|
|
|
868
666
|
return false;
|
|
869
667
|
}
|
|
870
668
|
|
|
871
|
-
// src/format/number.ts
|
|
669
|
+
// src/format/number/index.ts
|
|
872
670
|
function formatNumber(num, options = {}) {
|
|
873
671
|
const { decimals, separator = ",", decimalPoint = ".", minimumFractionDigits } = options;
|
|
874
672
|
let numStr;
|
|
@@ -945,7 +743,7 @@ function formatBytes(bytes, decimals = 2) {
|
|
|
945
743
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`;
|
|
946
744
|
}
|
|
947
745
|
|
|
948
|
-
// src/format/url.ts
|
|
746
|
+
// src/format/url/index.ts
|
|
949
747
|
function parseUrl(url) {
|
|
950
748
|
try {
|
|
951
749
|
const urlObj = new URL(url);
|
|
@@ -1036,7 +834,7 @@ function normalizeUrl(url) {
|
|
|
1036
834
|
}
|
|
1037
835
|
}
|
|
1038
836
|
|
|
1039
|
-
// src/format/color.ts
|
|
837
|
+
// src/format/color/index.ts
|
|
1040
838
|
function hexToRgb(hex) {
|
|
1041
839
|
const cleanHex = hex.replace("#", "");
|
|
1042
840
|
if (!/^[0-9A-Fa-f]{6}$/.test(cleanHex)) {
|
|
@@ -1163,7 +961,7 @@ function contrast(color1, color2) {
|
|
|
1163
961
|
return (lighter + 0.05) / (darker + 0.05);
|
|
1164
962
|
}
|
|
1165
963
|
|
|
1166
|
-
// src/format/i18n.ts
|
|
964
|
+
// src/format/i18n/index.ts
|
|
1167
965
|
function getLocale() {
|
|
1168
966
|
if (typeof navigator !== "undefined" && navigator.language) {
|
|
1169
967
|
return navigator.language;
|
|
@@ -1241,7 +1039,7 @@ function pluralize(count, singular, plural, locale) {
|
|
|
1241
1039
|
return `${count} ${pluralForm}`;
|
|
1242
1040
|
}
|
|
1243
1041
|
|
|
1244
|
-
// src/browser/file.ts
|
|
1042
|
+
// src/browser/file/index.ts
|
|
1245
1043
|
async function calculateFileMD5(file) {
|
|
1246
1044
|
return new Promise((resolve, reject) => {
|
|
1247
1045
|
if (typeof FileReader === "undefined") {
|
|
@@ -1331,7 +1129,7 @@ var UploadStatus = /* @__PURE__ */ ((UploadStatus2) => {
|
|
|
1331
1129
|
return UploadStatus2;
|
|
1332
1130
|
})(UploadStatus || {});
|
|
1333
1131
|
|
|
1334
|
-
// src/browser/upload.ts
|
|
1132
|
+
// src/browser/upload/index.ts
|
|
1335
1133
|
var ChunkUploader = class {
|
|
1336
1134
|
constructor(file, options = {}) {
|
|
1337
1135
|
this.taskId = null;
|
|
@@ -1538,186 +1336,757 @@ var ChunkUploader = class {
|
|
|
1538
1336
|
method: "POST",
|
|
1539
1337
|
headers: this.options.headers
|
|
1540
1338
|
}
|
|
1541
|
-
);
|
|
1542
|
-
console.log("[\u5B8C\u6210\u4E0A\u4F20] \u63A5\u53E3\u54CD\u5E94:", response);
|
|
1543
|
-
if (response.code !== 200) {
|
|
1544
|
-
throw new Error(response.message || "\u5B8C\u6210\u4E0A\u4F20\u5931\u8D25");
|
|
1339
|
+
);
|
|
1340
|
+
console.log("[\u5B8C\u6210\u4E0A\u4F20] \u63A5\u53E3\u54CD\u5E94:", response);
|
|
1341
|
+
if (response.code !== 200) {
|
|
1342
|
+
throw new Error(response.message || "\u5B8C\u6210\u4E0A\u4F20\u5931\u8D25");
|
|
1343
|
+
}
|
|
1344
|
+
return response.data;
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* 开始上传
|
|
1348
|
+
*/
|
|
1349
|
+
async upload() {
|
|
1350
|
+
try {
|
|
1351
|
+
this.status = "uploading" /* UPLOADING */;
|
|
1352
|
+
this.abortController = new AbortController();
|
|
1353
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 1. \u521D\u59CB\u5316\u4E0A\u4F20");
|
|
1354
|
+
const initResponse = await this.initUpload();
|
|
1355
|
+
this.taskId = initResponse.taskId;
|
|
1356
|
+
if (initResponse.instantUpload && initResponse.fileUrl) {
|
|
1357
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] \u79D2\u4F20\u6210\u529F");
|
|
1358
|
+
this.status = "completed" /* COMPLETED */;
|
|
1359
|
+
const result = {
|
|
1360
|
+
taskId: this.taskId,
|
|
1361
|
+
fileUrl: initResponse.fileUrl,
|
|
1362
|
+
fileName: this.file.name,
|
|
1363
|
+
fileSize: this.file.size,
|
|
1364
|
+
fileMd5: "",
|
|
1365
|
+
success: true,
|
|
1366
|
+
message: "\u6587\u4EF6\u5DF2\u5B58\u5728\uFF0C\u79D2\u4F20\u6210\u529F"
|
|
1367
|
+
};
|
|
1368
|
+
this.options.onComplete(result);
|
|
1369
|
+
return result;
|
|
1370
|
+
}
|
|
1371
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 2. \u51C6\u5907\u5206\u7247");
|
|
1372
|
+
this.prepareChunks();
|
|
1373
|
+
const totalChunks = this.chunks.length;
|
|
1374
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] \u5171 ${totalChunks} \u4E2A\u5206\u7247`);
|
|
1375
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 3. \u68C0\u67E5\u5DF2\u4E0A\u4F20\u5206\u7247");
|
|
1376
|
+
const existingChunks = await this.getUploadedChunks();
|
|
1377
|
+
existingChunks.forEach((index) => this.uploadedChunks.add(index));
|
|
1378
|
+
console.log(
|
|
1379
|
+
`[\u4E0A\u4F20\u6D41\u7A0B] \u5DF2\u4E0A\u4F20 ${existingChunks.length} \u4E2A\u5206\u7247\uFF0C\u8FD8\u9700\u4E0A\u4F20 ${totalChunks - existingChunks.length} \u4E2A`
|
|
1380
|
+
);
|
|
1381
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 4. \u5F00\u59CB\u4E0A\u4F20\u5206\u7247");
|
|
1382
|
+
await this.uploadChunksConcurrently();
|
|
1383
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 5. \u6240\u6709\u5206\u7247\u4E0A\u4F20\u5B8C\u6210");
|
|
1384
|
+
const uploadedCount = this.uploadedChunks.size;
|
|
1385
|
+
if (uploadedCount < totalChunks) {
|
|
1386
|
+
throw new Error(`\u4E0A\u4F20\u9A8C\u8BC1\u5931\u8D25\uFF1A\u5DF2\u4E0A\u4F20 ${uploadedCount}/${totalChunks} \u4E2A\u5206\u7247`);
|
|
1387
|
+
}
|
|
1388
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] 6. \u9A8C\u8BC1\u901A\u8FC7\uFF1A${uploadedCount}/${totalChunks} \u4E2A\u5206\u7247\u5DF2\u4E0A\u4F20`);
|
|
1389
|
+
const localPercentage = Math.round(uploadedCount / totalChunks * 100);
|
|
1390
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] 7. \u672C\u5730\u8FDB\u5EA6\uFF1A${localPercentage}%`);
|
|
1391
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 8. \u83B7\u53D6\u670D\u52A1\u7AEF\u8FDB\u5EA6");
|
|
1392
|
+
const serverProgress = await this.getUploadProgress();
|
|
1393
|
+
const serverPercentage = serverProgress?.percentage ?? 0;
|
|
1394
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] \u670D\u52A1\u7AEF\u8FDB\u5EA6\uFF1A${serverPercentage}%`);
|
|
1395
|
+
const finalPercentage = serverProgress?.percentage ?? localPercentage;
|
|
1396
|
+
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] 9. \u6700\u7EC8\u8FDB\u5EA6\uFF1A${finalPercentage}%`);
|
|
1397
|
+
if (finalPercentage >= 100) {
|
|
1398
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 10. \u2705 \u8FDB\u5EA6\u8FBE\u5230100%\uFF0C\u8C03\u7528\u5B8C\u6210\u63A5\u53E3");
|
|
1399
|
+
const result = await this.completeUpload();
|
|
1400
|
+
this.status = "completed" /* COMPLETED */;
|
|
1401
|
+
this.options.onComplete(result);
|
|
1402
|
+
console.log("[\u4E0A\u4F20\u6D41\u7A0B] \u2705 \u4E0A\u4F20\u5B8C\u6210");
|
|
1403
|
+
return result;
|
|
1404
|
+
} else {
|
|
1405
|
+
console.error(`[\u4E0A\u4F20\u6D41\u7A0B] \u274C \u8FDB\u5EA6\u4E0D\u8DB3100%\uFF1A${finalPercentage}%`);
|
|
1406
|
+
throw new Error(`\u4E0A\u4F20\u672A\u5B8C\u6210\uFF1A\u5F53\u524D\u8FDB\u5EA6 ${finalPercentage.toFixed(2)}%`);
|
|
1407
|
+
}
|
|
1408
|
+
} catch (error) {
|
|
1409
|
+
this.status = "failed" /* FAILED */;
|
|
1410
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1411
|
+
this.options.onError(err);
|
|
1412
|
+
console.error("[\u4E0A\u4F20\u6D41\u7A0B] \u274C \u4E0A\u4F20\u5931\u8D25:", err);
|
|
1413
|
+
throw err;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* 暂停上传
|
|
1418
|
+
*/
|
|
1419
|
+
pause() {
|
|
1420
|
+
if (this.status === "uploading" /* UPLOADING */) {
|
|
1421
|
+
this.status = "paused" /* PAUSED */;
|
|
1422
|
+
if (this.abortController) {
|
|
1423
|
+
this.abortController.abort();
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* 恢复上传
|
|
1429
|
+
*/
|
|
1430
|
+
async resume() {
|
|
1431
|
+
if (this.status === "paused" /* PAUSED */) {
|
|
1432
|
+
return this.upload();
|
|
1433
|
+
}
|
|
1434
|
+
throw new Error("\u5F53\u524D\u72B6\u6001\u65E0\u6CD5\u6062\u590D\u4E0A\u4F20");
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* 取消上传
|
|
1438
|
+
*/
|
|
1439
|
+
async cancel() {
|
|
1440
|
+
if (this.taskId && this.status === "uploading" /* UPLOADING */) {
|
|
1441
|
+
try {
|
|
1442
|
+
await this.request(`/api/files/common/cancel/${this.taskId}`, {
|
|
1443
|
+
method: "POST",
|
|
1444
|
+
headers: this.options.headers
|
|
1445
|
+
});
|
|
1446
|
+
} catch (error) {
|
|
1447
|
+
console.warn("\u53D6\u6D88\u4E0A\u4F20\u5931\u8D25:", error);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
this.status = "cancelled" /* CANCELLED */;
|
|
1451
|
+
if (this.abortController) {
|
|
1452
|
+
this.abortController.abort();
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
/**
|
|
1456
|
+
* 获取当前状态
|
|
1457
|
+
*/
|
|
1458
|
+
getStatus() {
|
|
1459
|
+
return this.status;
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* 获取任务ID
|
|
1463
|
+
*/
|
|
1464
|
+
getTaskId() {
|
|
1465
|
+
return this.taskId;
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* HTTP请求封装
|
|
1469
|
+
*/
|
|
1470
|
+
async request(url, options = {}) {
|
|
1471
|
+
const fullUrl = `${this.options.baseURL}${url}`;
|
|
1472
|
+
const signal = this.abortController?.signal;
|
|
1473
|
+
const response = await fetch(fullUrl, {
|
|
1474
|
+
...options,
|
|
1475
|
+
signal
|
|
1476
|
+
});
|
|
1477
|
+
if (!response.ok) {
|
|
1478
|
+
throw new Error(`HTTP\u9519\u8BEF: ${response.status} ${response.statusText}`);
|
|
1479
|
+
}
|
|
1480
|
+
return response.json();
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* 延迟函数
|
|
1484
|
+
*/
|
|
1485
|
+
delay(ms) {
|
|
1486
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1487
|
+
}
|
|
1488
|
+
};
|
|
1489
|
+
function createUploader(file, options) {
|
|
1490
|
+
return new ChunkUploader(file, options);
|
|
1491
|
+
}
|
|
1492
|
+
async function uploadFile(file, options) {
|
|
1493
|
+
const uploader = createUploader(file, options);
|
|
1494
|
+
return uploader.upload();
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
// src/helper/crypto/index.ts
|
|
1498
|
+
async function sha256(data) {
|
|
1499
|
+
const buffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
1500
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
|
|
1501
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1502
|
+
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1503
|
+
}
|
|
1504
|
+
function base64Encode(data) {
|
|
1505
|
+
if (typeof data === "string") {
|
|
1506
|
+
return btoa(unescape(encodeURIComponent(data)));
|
|
1507
|
+
}
|
|
1508
|
+
const bytes = new Uint8Array(data);
|
|
1509
|
+
let binary = "";
|
|
1510
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1511
|
+
binary += String.fromCharCode(bytes[i]);
|
|
1512
|
+
}
|
|
1513
|
+
return btoa(binary);
|
|
1514
|
+
}
|
|
1515
|
+
function base64Decode(data) {
|
|
1516
|
+
try {
|
|
1517
|
+
return decodeURIComponent(escape(atob(data)));
|
|
1518
|
+
} catch {
|
|
1519
|
+
throw new Error("Invalid Base64 string");
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
function generateUUID() {
|
|
1523
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
1524
|
+
return crypto.randomUUID();
|
|
1525
|
+
}
|
|
1526
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
1527
|
+
const r = Math.random() * 16 | 0;
|
|
1528
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
1529
|
+
return v.toString(16);
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
function generateRandomString(length, charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") {
|
|
1533
|
+
let result = "";
|
|
1534
|
+
for (let i = 0; i < length; i++) {
|
|
1535
|
+
result += charset.charAt(Math.floor(Math.random() * charset.length));
|
|
1536
|
+
}
|
|
1537
|
+
return result;
|
|
1538
|
+
}
|
|
1539
|
+
function hash(data) {
|
|
1540
|
+
let hashValue = 0;
|
|
1541
|
+
for (let i = 0; i < data.length; i++) {
|
|
1542
|
+
const char = data.charCodeAt(i);
|
|
1543
|
+
hashValue = (hashValue << 5) - hashValue + char;
|
|
1544
|
+
hashValue = hashValue & hashValue;
|
|
1545
|
+
}
|
|
1546
|
+
return Math.abs(hashValue);
|
|
1547
|
+
}
|
|
1548
|
+
async function generateRSAKeyPair(modulusLength = 2048) {
|
|
1549
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1550
|
+
throw new Error("Web Crypto API is not available");
|
|
1551
|
+
}
|
|
1552
|
+
const keyPair = await crypto.subtle.generateKey(
|
|
1553
|
+
{
|
|
1554
|
+
name: "RSA-OAEP",
|
|
1555
|
+
modulusLength,
|
|
1556
|
+
publicExponent: new Uint8Array([1, 0, 1]),
|
|
1557
|
+
hash: "SHA-256"
|
|
1558
|
+
},
|
|
1559
|
+
true,
|
|
1560
|
+
["encrypt", "decrypt"]
|
|
1561
|
+
);
|
|
1562
|
+
return {
|
|
1563
|
+
publicKey: keyPair.publicKey,
|
|
1564
|
+
privateKey: keyPair.privateKey
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
async function rsaEncrypt(data, publicKey) {
|
|
1568
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1569
|
+
throw new Error("Web Crypto API is not available");
|
|
1570
|
+
}
|
|
1571
|
+
const encoder = new TextEncoder();
|
|
1572
|
+
const dataBuffer = encoder.encode(data);
|
|
1573
|
+
const maxChunkSize = 245;
|
|
1574
|
+
const chunks = [];
|
|
1575
|
+
for (let i = 0; i < dataBuffer.length; i += maxChunkSize) {
|
|
1576
|
+
const chunk2 = dataBuffer.slice(i, i + maxChunkSize);
|
|
1577
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
1578
|
+
{
|
|
1579
|
+
name: "RSA-OAEP"
|
|
1580
|
+
},
|
|
1581
|
+
publicKey,
|
|
1582
|
+
chunk2
|
|
1583
|
+
);
|
|
1584
|
+
chunks.push(encrypted);
|
|
1585
|
+
}
|
|
1586
|
+
const totalLength = chunks.reduce((sum, chunk2) => sum + chunk2.byteLength, 0);
|
|
1587
|
+
const merged = new Uint8Array(totalLength);
|
|
1588
|
+
let offset = 0;
|
|
1589
|
+
for (const chunk2 of chunks) {
|
|
1590
|
+
merged.set(new Uint8Array(chunk2), offset);
|
|
1591
|
+
offset += chunk2.byteLength;
|
|
1592
|
+
}
|
|
1593
|
+
return base64Encode(merged.buffer);
|
|
1594
|
+
}
|
|
1595
|
+
async function rsaDecrypt(encryptedData, privateKey) {
|
|
1596
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1597
|
+
throw new Error("Web Crypto API is not available");
|
|
1598
|
+
}
|
|
1599
|
+
const binaryString = atob(encryptedData);
|
|
1600
|
+
const encryptedArray = new Uint8Array(binaryString.length);
|
|
1601
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
1602
|
+
encryptedArray[i] = binaryString.charCodeAt(i);
|
|
1603
|
+
}
|
|
1604
|
+
const chunkSize = 256;
|
|
1605
|
+
const chunks = [];
|
|
1606
|
+
for (let i = 0; i < encryptedArray.length; i += chunkSize) {
|
|
1607
|
+
const chunk2 = encryptedArray.slice(i, i + chunkSize);
|
|
1608
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
1609
|
+
{
|
|
1610
|
+
name: "RSA-OAEP"
|
|
1611
|
+
},
|
|
1612
|
+
privateKey,
|
|
1613
|
+
chunk2
|
|
1614
|
+
);
|
|
1615
|
+
const decoder = new TextDecoder();
|
|
1616
|
+
chunks.push(decoder.decode(decrypted));
|
|
1617
|
+
}
|
|
1618
|
+
return chunks.join("");
|
|
1619
|
+
}
|
|
1620
|
+
async function exportPublicKey(publicKey) {
|
|
1621
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1622
|
+
throw new Error("Web Crypto API is not available");
|
|
1623
|
+
}
|
|
1624
|
+
const exported = await crypto.subtle.exportKey("spki", publicKey);
|
|
1625
|
+
return base64Encode(exported);
|
|
1626
|
+
}
|
|
1627
|
+
async function exportPrivateKey(privateKey) {
|
|
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("pkcs8", privateKey);
|
|
1632
|
+
return base64Encode(exported);
|
|
1633
|
+
}
|
|
1634
|
+
async function importPublicKey(keyData) {
|
|
1635
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1636
|
+
throw new Error("Web Crypto API is not available");
|
|
1637
|
+
}
|
|
1638
|
+
const keyBuffer = base64Decode(keyData);
|
|
1639
|
+
const keyArray = new Uint8Array(keyBuffer.split("").map((char) => char.charCodeAt(0)));
|
|
1640
|
+
return crypto.subtle.importKey(
|
|
1641
|
+
"spki",
|
|
1642
|
+
keyArray.buffer,
|
|
1643
|
+
{
|
|
1644
|
+
name: "RSA-OAEP",
|
|
1645
|
+
hash: "SHA-256"
|
|
1646
|
+
},
|
|
1647
|
+
true,
|
|
1648
|
+
["encrypt"]
|
|
1649
|
+
);
|
|
1650
|
+
}
|
|
1651
|
+
async function importPrivateKey(keyData) {
|
|
1652
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1653
|
+
throw new Error("Web Crypto API is not available");
|
|
1654
|
+
}
|
|
1655
|
+
const keyBuffer = base64Decode(keyData);
|
|
1656
|
+
const keyArray = new Uint8Array(keyBuffer.split("").map((char) => char.charCodeAt(0)));
|
|
1657
|
+
return crypto.subtle.importKey(
|
|
1658
|
+
"pkcs8",
|
|
1659
|
+
keyArray.buffer,
|
|
1660
|
+
{
|
|
1661
|
+
name: "RSA-OAEP",
|
|
1662
|
+
hash: "SHA-256"
|
|
1663
|
+
},
|
|
1664
|
+
true,
|
|
1665
|
+
["decrypt"]
|
|
1666
|
+
);
|
|
1667
|
+
}
|
|
1668
|
+
async function generateHMACKey() {
|
|
1669
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1670
|
+
throw new Error("Web Crypto API is not available");
|
|
1671
|
+
}
|
|
1672
|
+
return crypto.subtle.generateKey(
|
|
1673
|
+
{
|
|
1674
|
+
name: "HMAC",
|
|
1675
|
+
hash: "SHA-256"
|
|
1676
|
+
},
|
|
1677
|
+
true,
|
|
1678
|
+
["sign", "verify"]
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1681
|
+
async function computeHMAC(data, key) {
|
|
1682
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1683
|
+
throw new Error("Web Crypto API is not available");
|
|
1684
|
+
}
|
|
1685
|
+
const buffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
1686
|
+
const signature = await crypto.subtle.sign("HMAC", key, buffer);
|
|
1687
|
+
return base64Encode(signature);
|
|
1688
|
+
}
|
|
1689
|
+
async function verifyHMAC(data, signature, key) {
|
|
1690
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1691
|
+
throw new Error("Web Crypto API is not available");
|
|
1692
|
+
}
|
|
1693
|
+
try {
|
|
1694
|
+
const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
1695
|
+
const signatureBuffer = new Uint8Array(
|
|
1696
|
+
atob(signature).split("").map((char) => char.charCodeAt(0))
|
|
1697
|
+
);
|
|
1698
|
+
return await crypto.subtle.verify("HMAC", key, signatureBuffer, dataBuffer);
|
|
1699
|
+
} catch {
|
|
1700
|
+
return false;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
async function deriveKeyFromPassword(password, salt, iterations = 1e5, keyLength = 256) {
|
|
1704
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1705
|
+
throw new Error("Web Crypto API is not available");
|
|
1706
|
+
}
|
|
1707
|
+
const saltBuffer = typeof salt === "string" ? new TextEncoder().encode(salt) : salt;
|
|
1708
|
+
const passwordKey = await crypto.subtle.importKey(
|
|
1709
|
+
"raw",
|
|
1710
|
+
new TextEncoder().encode(password),
|
|
1711
|
+
"PBKDF2",
|
|
1712
|
+
false,
|
|
1713
|
+
["deriveBits", "deriveKey"]
|
|
1714
|
+
);
|
|
1715
|
+
return crypto.subtle.deriveKey(
|
|
1716
|
+
{
|
|
1717
|
+
name: "PBKDF2",
|
|
1718
|
+
salt: saltBuffer,
|
|
1719
|
+
iterations,
|
|
1720
|
+
hash: "SHA-256"
|
|
1721
|
+
},
|
|
1722
|
+
passwordKey,
|
|
1723
|
+
{
|
|
1724
|
+
name: "AES-GCM",
|
|
1725
|
+
length: keyLength
|
|
1726
|
+
},
|
|
1727
|
+
false,
|
|
1728
|
+
["encrypt", "decrypt"]
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1731
|
+
async function aesGCMEncrypt(data, key) {
|
|
1732
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1733
|
+
throw new Error("Web Crypto API is not available");
|
|
1734
|
+
}
|
|
1735
|
+
const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
1736
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
1737
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
1738
|
+
{
|
|
1739
|
+
name: "AES-GCM",
|
|
1740
|
+
iv
|
|
1741
|
+
},
|
|
1742
|
+
key,
|
|
1743
|
+
dataBuffer
|
|
1744
|
+
);
|
|
1745
|
+
return {
|
|
1746
|
+
encrypted: base64Encode(encrypted),
|
|
1747
|
+
iv: base64Encode(iv.buffer)
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
async function aesGCMDecrypt(encryptedData, iv, key) {
|
|
1751
|
+
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
1752
|
+
throw new Error("Web Crypto API is not available");
|
|
1753
|
+
}
|
|
1754
|
+
const encryptedBuffer = new Uint8Array(
|
|
1755
|
+
atob(encryptedData).split("").map((char) => char.charCodeAt(0))
|
|
1756
|
+
);
|
|
1757
|
+
const ivBuffer = new Uint8Array(
|
|
1758
|
+
atob(iv).split("").map((char) => char.charCodeAt(0))
|
|
1759
|
+
);
|
|
1760
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
1761
|
+
{
|
|
1762
|
+
name: "AES-GCM",
|
|
1763
|
+
iv: ivBuffer
|
|
1764
|
+
},
|
|
1765
|
+
key,
|
|
1766
|
+
encryptedBuffer
|
|
1767
|
+
);
|
|
1768
|
+
return new TextDecoder().decode(decrypted);
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
// src/browser/SecureStorage/index.ts
|
|
1772
|
+
var globalKeyPair = null;
|
|
1773
|
+
var globalHMACKey = null;
|
|
1774
|
+
var keyPairInitialized = false;
|
|
1775
|
+
var initOptions = {
|
|
1776
|
+
autoGenerateKeys: true,
|
|
1777
|
+
persistKeys: false,
|
|
1778
|
+
keyStorageKey: void 0,
|
|
1779
|
+
// 将自动生成随机键名
|
|
1780
|
+
keyEncryptionPassword: void 0,
|
|
1781
|
+
pbkdf2Iterations: 1e5,
|
|
1782
|
+
keyModulusLength: 2048,
|
|
1783
|
+
enableHMAC: true,
|
|
1784
|
+
enableTimestampValidation: true,
|
|
1785
|
+
timestampMaxAge: 7 * 24 * 60 * 60 * 1e3,
|
|
1786
|
+
// 7 天
|
|
1787
|
+
isProduction: false
|
|
1788
|
+
};
|
|
1789
|
+
var actualKeyStorageKey = null;
|
|
1790
|
+
var keyUsageCount = 0;
|
|
1791
|
+
var initializationPromise = null;
|
|
1792
|
+
function generateKeyStorageKey() {
|
|
1793
|
+
return `_sk_${generateRandomString(32)}_${Date.now()}`;
|
|
1794
|
+
}
|
|
1795
|
+
async function initializeStorageKeys(options = {}) {
|
|
1796
|
+
if (options.forceReinitialize) {
|
|
1797
|
+
globalKeyPair = null;
|
|
1798
|
+
globalHMACKey = null;
|
|
1799
|
+
keyPairInitialized = false;
|
|
1800
|
+
actualKeyStorageKey = null;
|
|
1801
|
+
keyUsageCount = 0;
|
|
1802
|
+
initializationPromise = null;
|
|
1803
|
+
} else if (keyPairInitialized && globalKeyPair) {
|
|
1804
|
+
initOptions = { ...initOptions, ...options };
|
|
1805
|
+
return;
|
|
1806
|
+
}
|
|
1807
|
+
initOptions = { ...initOptions, ...options };
|
|
1808
|
+
if (initOptions.keyStorageKey) {
|
|
1809
|
+
actualKeyStorageKey = initOptions.keyStorageKey;
|
|
1810
|
+
} else {
|
|
1811
|
+
actualKeyStorageKey = generateKeyStorageKey();
|
|
1812
|
+
}
|
|
1813
|
+
if (initOptions.persistKeys && typeof window !== "undefined" && window.localStorage) {
|
|
1814
|
+
try {
|
|
1815
|
+
const storedKeys = window.localStorage.getItem(actualKeyStorageKey);
|
|
1816
|
+
if (storedKeys) {
|
|
1817
|
+
const keyData = JSON.parse(storedKeys);
|
|
1818
|
+
if (keyData.encrypted && keyData.privateKeyEncrypted && keyData.salt) {
|
|
1819
|
+
if (!initOptions.keyEncryptionPassword) {
|
|
1820
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
1821
|
+
console.error("Encrypted keys found but no password provided");
|
|
1822
|
+
}
|
|
1823
|
+
throw new Error("Password required to decrypt stored keys");
|
|
1824
|
+
}
|
|
1825
|
+
const saltArray = new Uint8Array(
|
|
1826
|
+
atob(keyData.salt).split("").map((char) => char.charCodeAt(0))
|
|
1827
|
+
);
|
|
1828
|
+
const iterations = keyData.pbkdf2Iterations || initOptions.pbkdf2Iterations;
|
|
1829
|
+
const derivedKey = await deriveKeyFromPassword(
|
|
1830
|
+
initOptions.keyEncryptionPassword,
|
|
1831
|
+
saltArray.buffer,
|
|
1832
|
+
iterations
|
|
1833
|
+
);
|
|
1834
|
+
const decryptedPrivateKey = await aesGCMDecrypt(
|
|
1835
|
+
keyData.privateKeyEncrypted,
|
|
1836
|
+
keyData.iv,
|
|
1837
|
+
derivedKey
|
|
1838
|
+
);
|
|
1839
|
+
globalKeyPair = {
|
|
1840
|
+
publicKey: await importPublicKey(keyData.publicKey),
|
|
1841
|
+
privateKey: await importPrivateKey(decryptedPrivateKey)
|
|
1842
|
+
};
|
|
1843
|
+
} else if (keyData.publicKey && keyData.privateKey) {
|
|
1844
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
1845
|
+
console.warn(
|
|
1846
|
+
"\u26A0\uFE0F SECURITY WARNING: Loading unencrypted keys from storage. Consider re-initializing with password encryption."
|
|
1847
|
+
);
|
|
1848
|
+
}
|
|
1849
|
+
globalKeyPair = {
|
|
1850
|
+
publicKey: await importPublicKey(keyData.publicKey),
|
|
1851
|
+
privateKey: await importPrivateKey(keyData.privateKey)
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
if (globalKeyPair) {
|
|
1855
|
+
keyPairInitialized = true;
|
|
1856
|
+
if (initOptions.enableHMAC && !globalHMACKey) {
|
|
1857
|
+
globalHMACKey = await generateHMACKey();
|
|
1858
|
+
}
|
|
1859
|
+
return;
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
} catch (error) {
|
|
1863
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
1864
|
+
console.warn("Failed to load persisted keys, will generate new ones");
|
|
1865
|
+
}
|
|
1545
1866
|
}
|
|
1546
|
-
return response.data;
|
|
1547
1867
|
}
|
|
1548
|
-
|
|
1549
|
-
* 开始上传
|
|
1550
|
-
*/
|
|
1551
|
-
async upload() {
|
|
1868
|
+
if (initOptions.autoGenerateKeys && !globalKeyPair) {
|
|
1552
1869
|
try {
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
const initResponse = await this.initUpload();
|
|
1557
|
-
this.taskId = initResponse.taskId;
|
|
1558
|
-
if (initResponse.instantUpload && initResponse.fileUrl) {
|
|
1559
|
-
console.log("[\u4E0A\u4F20\u6D41\u7A0B] \u79D2\u4F20\u6210\u529F");
|
|
1560
|
-
this.status = "completed" /* COMPLETED */;
|
|
1561
|
-
const result = {
|
|
1562
|
-
taskId: this.taskId,
|
|
1563
|
-
fileUrl: initResponse.fileUrl,
|
|
1564
|
-
fileName: this.file.name,
|
|
1565
|
-
fileSize: this.file.size,
|
|
1566
|
-
fileMd5: "",
|
|
1567
|
-
success: true,
|
|
1568
|
-
message: "\u6587\u4EF6\u5DF2\u5B58\u5728\uFF0C\u79D2\u4F20\u6210\u529F"
|
|
1569
|
-
};
|
|
1570
|
-
this.options.onComplete(result);
|
|
1571
|
-
return result;
|
|
1572
|
-
}
|
|
1573
|
-
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 2. \u51C6\u5907\u5206\u7247");
|
|
1574
|
-
this.prepareChunks();
|
|
1575
|
-
const totalChunks = this.chunks.length;
|
|
1576
|
-
console.log(`[\u4E0A\u4F20\u6D41\u7A0B] \u5171 ${totalChunks} \u4E2A\u5206\u7247`);
|
|
1577
|
-
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 3. \u68C0\u67E5\u5DF2\u4E0A\u4F20\u5206\u7247");
|
|
1578
|
-
const existingChunks = await this.getUploadedChunks();
|
|
1579
|
-
existingChunks.forEach((index) => this.uploadedChunks.add(index));
|
|
1580
|
-
console.log(
|
|
1581
|
-
`[\u4E0A\u4F20\u6D41\u7A0B] \u5DF2\u4E0A\u4F20 ${existingChunks.length} \u4E2A\u5206\u7247\uFF0C\u8FD8\u9700\u4E0A\u4F20 ${totalChunks - existingChunks.length} \u4E2A`
|
|
1582
|
-
);
|
|
1583
|
-
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 4. \u5F00\u59CB\u4E0A\u4F20\u5206\u7247");
|
|
1584
|
-
await this.uploadChunksConcurrently();
|
|
1585
|
-
console.log("[\u4E0A\u4F20\u6D41\u7A0B] 5. \u6240\u6709\u5206\u7247\u4E0A\u4F20\u5B8C\u6210");
|
|
1586
|
-
const uploadedCount = this.uploadedChunks.size;
|
|
1587
|
-
if (uploadedCount < totalChunks) {
|
|
1588
|
-
throw new Error(`\u4E0A\u4F20\u9A8C\u8BC1\u5931\u8D25\uFF1A\u5DF2\u4E0A\u4F20 ${uploadedCount}/${totalChunks} \u4E2A\u5206\u7247`);
|
|
1870
|
+
globalKeyPair = await generateRSAKeyPair(initOptions.keyModulusLength);
|
|
1871
|
+
if (initOptions.enableHMAC && !globalHMACKey) {
|
|
1872
|
+
globalHMACKey = await generateHMACKey();
|
|
1589
1873
|
}
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1874
|
+
keyPairInitialized = true;
|
|
1875
|
+
keyUsageCount = 0;
|
|
1876
|
+
if (initOptions.persistKeys && typeof window !== "undefined" && window.localStorage) {
|
|
1877
|
+
try {
|
|
1878
|
+
const publicKeyStr = await exportPublicKey(globalKeyPair.publicKey);
|
|
1879
|
+
if (initOptions.keyEncryptionPassword) {
|
|
1880
|
+
const privateKeyStr = await exportPrivateKey(globalKeyPair.privateKey);
|
|
1881
|
+
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
1882
|
+
const derivedKey = await deriveKeyFromPassword(
|
|
1883
|
+
initOptions.keyEncryptionPassword,
|
|
1884
|
+
salt.buffer,
|
|
1885
|
+
initOptions.pbkdf2Iterations
|
|
1886
|
+
);
|
|
1887
|
+
const { encrypted, iv } = await aesGCMEncrypt(privateKeyStr, derivedKey);
|
|
1888
|
+
const keyData = {
|
|
1889
|
+
encrypted: true,
|
|
1890
|
+
publicKey: publicKeyStr,
|
|
1891
|
+
privateKeyEncrypted: encrypted,
|
|
1892
|
+
iv,
|
|
1893
|
+
salt: base64Encode(salt.buffer),
|
|
1894
|
+
pbkdf2Iterations: initOptions.pbkdf2Iterations
|
|
1895
|
+
};
|
|
1896
|
+
window.localStorage.setItem(actualKeyStorageKey, JSON.stringify(keyData));
|
|
1897
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.info) {
|
|
1898
|
+
console.info("\u2705 Keys encrypted and stored securely");
|
|
1899
|
+
}
|
|
1900
|
+
} else {
|
|
1901
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
1902
|
+
console.warn(
|
|
1903
|
+
"\u26A0\uFE0F SECURITY WARNING: Storing private keys without encryption! Private keys will be stored in plain text (Base64 encoded). Provide keyEncryptionPassword for secure storage."
|
|
1904
|
+
);
|
|
1905
|
+
}
|
|
1906
|
+
const keyData = {
|
|
1907
|
+
publicKey: publicKeyStr,
|
|
1908
|
+
privateKey: await exportPrivateKey(globalKeyPair.privateKey)
|
|
1909
|
+
};
|
|
1910
|
+
window.localStorage.setItem(actualKeyStorageKey, JSON.stringify(keyData));
|
|
1911
|
+
}
|
|
1912
|
+
} catch (error) {
|
|
1913
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
1914
|
+
console.error("Failed to persist keys");
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1609
1917
|
}
|
|
1610
1918
|
} catch (error) {
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
this.options.onError(err);
|
|
1614
|
-
console.error("[\u4E0A\u4F20\u6D41\u7A0B] \u274C \u4E0A\u4F20\u5931\u8D25:", err);
|
|
1615
|
-
throw err;
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
/**
|
|
1619
|
-
* 暂停上传
|
|
1620
|
-
*/
|
|
1621
|
-
pause() {
|
|
1622
|
-
if (this.status === "uploading" /* UPLOADING */) {
|
|
1623
|
-
this.status = "paused" /* PAUSED */;
|
|
1624
|
-
if (this.abortController) {
|
|
1625
|
-
this.abortController.abort();
|
|
1919
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
1920
|
+
console.error("Failed to generate storage keys");
|
|
1626
1921
|
}
|
|
1922
|
+
throw error;
|
|
1627
1923
|
}
|
|
1628
1924
|
}
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
*/
|
|
1632
|
-
async resume() {
|
|
1633
|
-
if (this.status === "paused" /* PAUSED */) {
|
|
1634
|
-
return this.upload();
|
|
1635
|
-
}
|
|
1636
|
-
throw new Error("\u5F53\u524D\u72B6\u6001\u65E0\u6CD5\u6062\u590D\u4E0A\u4F20");
|
|
1925
|
+
if (initOptions.enableHMAC && !globalHMACKey) {
|
|
1926
|
+
globalHMACKey = await generateHMACKey();
|
|
1637
1927
|
}
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1928
|
+
}
|
|
1929
|
+
function setStorageKeyPair(keyPair) {
|
|
1930
|
+
globalKeyPair = keyPair;
|
|
1931
|
+
keyPairInitialized = true;
|
|
1932
|
+
}
|
|
1933
|
+
function getStorageKeyPair() {
|
|
1934
|
+
return globalKeyPair;
|
|
1935
|
+
}
|
|
1936
|
+
function clearPersistedKeys() {
|
|
1937
|
+
if (typeof window !== "undefined" && window.localStorage && actualKeyStorageKey) {
|
|
1938
|
+
try {
|
|
1939
|
+
window.localStorage.removeItem(actualKeyStorageKey);
|
|
1940
|
+
actualKeyStorageKey = null;
|
|
1941
|
+
keyPairInitialized = false;
|
|
1942
|
+
globalKeyPair = null;
|
|
1943
|
+
globalHMACKey = null;
|
|
1944
|
+
keyUsageCount = 0;
|
|
1945
|
+
} catch (error) {
|
|
1946
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
1947
|
+
console.error("Failed to clear persisted keys");
|
|
1650
1948
|
}
|
|
1651
1949
|
}
|
|
1652
|
-
this.status = "cancelled" /* CANCELLED */;
|
|
1653
|
-
if (this.abortController) {
|
|
1654
|
-
this.abortController.abort();
|
|
1655
|
-
}
|
|
1656
1950
|
}
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1951
|
+
}
|
|
1952
|
+
function getKeyUsageCount() {
|
|
1953
|
+
return keyUsageCount;
|
|
1954
|
+
}
|
|
1955
|
+
function resetKeyUsageCount() {
|
|
1956
|
+
keyUsageCount = 0;
|
|
1957
|
+
}
|
|
1958
|
+
async function ensureKeyPair() {
|
|
1959
|
+
if (globalKeyPair) {
|
|
1960
|
+
keyUsageCount++;
|
|
1961
|
+
return;
|
|
1662
1962
|
}
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
*/
|
|
1666
|
-
getTaskId() {
|
|
1667
|
-
return this.taskId;
|
|
1963
|
+
if (!initOptions.autoGenerateKeys) {
|
|
1964
|
+
return;
|
|
1668
1965
|
}
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
const fullUrl = `${this.options.baseURL}${url}`;
|
|
1674
|
-
const signal = this.abortController?.signal;
|
|
1675
|
-
const response = await fetch(fullUrl, {
|
|
1676
|
-
...options,
|
|
1677
|
-
signal
|
|
1678
|
-
});
|
|
1679
|
-
if (!response.ok) {
|
|
1680
|
-
throw new Error(`HTTP\u9519\u8BEF: ${response.status} ${response.statusText}`);
|
|
1966
|
+
if (initializationPromise) {
|
|
1967
|
+
await initializationPromise;
|
|
1968
|
+
if (globalKeyPair) {
|
|
1969
|
+
keyUsageCount++;
|
|
1681
1970
|
}
|
|
1682
|
-
return
|
|
1971
|
+
return;
|
|
1683
1972
|
}
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1973
|
+
initializationPromise = initializeStorageKeys();
|
|
1974
|
+
try {
|
|
1975
|
+
await initializationPromise;
|
|
1976
|
+
} finally {
|
|
1977
|
+
initializationPromise = null;
|
|
1978
|
+
}
|
|
1979
|
+
if (globalKeyPair) {
|
|
1980
|
+
keyUsageCount++;
|
|
1689
1981
|
}
|
|
1690
|
-
};
|
|
1691
|
-
function createUploader(file, options) {
|
|
1692
|
-
return new ChunkUploader(file, options);
|
|
1693
1982
|
}
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1983
|
+
function safeParseJSON(jsonStr, expectedType) {
|
|
1984
|
+
try {
|
|
1985
|
+
const parsed = JSON.parse(jsonStr);
|
|
1986
|
+
if (expectedType === "object" && (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))) {
|
|
1987
|
+
throw new Error("Expected object but got different type");
|
|
1988
|
+
}
|
|
1989
|
+
if (expectedType === "array" && !Array.isArray(parsed)) {
|
|
1990
|
+
throw new Error("Expected array but got different type");
|
|
1991
|
+
}
|
|
1992
|
+
return parsed;
|
|
1993
|
+
} catch (error) {
|
|
1994
|
+
if (error instanceof SyntaxError) {
|
|
1995
|
+
throw new Error(`Invalid JSON format: ${error.message}`);
|
|
1996
|
+
}
|
|
1997
|
+
throw error;
|
|
1998
|
+
}
|
|
1697
1999
|
}
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
2000
|
+
function validateTimestamp(timestamp, maxClockSkew = 5 * 60 * 1e3) {
|
|
2001
|
+
if (!initOptions.enableTimestampValidation || !timestamp) {
|
|
2002
|
+
return true;
|
|
2003
|
+
}
|
|
2004
|
+
if (initOptions.timestampMaxAge === 0) {
|
|
2005
|
+
return true;
|
|
2006
|
+
}
|
|
2007
|
+
const now = Date.now();
|
|
2008
|
+
const age = now - timestamp;
|
|
2009
|
+
const maxAge = initOptions.timestampMaxAge || 7 * 24 * 60 * 60 * 1e3;
|
|
2010
|
+
if (age < -maxClockSkew) {
|
|
2011
|
+
return false;
|
|
2012
|
+
}
|
|
2013
|
+
return age >= -maxClockSkew && age <= maxAge;
|
|
1704
2014
|
}
|
|
1705
|
-
function
|
|
1706
|
-
|
|
2015
|
+
async function encryptValue(value, publicKey, hmacKey) {
|
|
2016
|
+
const encryptedData = await rsaEncrypt(value, publicKey);
|
|
2017
|
+
if (hmacKey) {
|
|
2018
|
+
const hmac = await computeHMAC(encryptedData, hmacKey);
|
|
2019
|
+
const item = {
|
|
2020
|
+
data: encryptedData,
|
|
2021
|
+
hmac,
|
|
2022
|
+
encrypted: true,
|
|
2023
|
+
timestamp: Date.now()
|
|
2024
|
+
};
|
|
2025
|
+
return JSON.stringify(item);
|
|
2026
|
+
}
|
|
2027
|
+
return encryptedData;
|
|
2028
|
+
}
|
|
2029
|
+
async function handleDecryptError(error, encryptedStr, key) {
|
|
2030
|
+
if (error instanceof Error && error.message.includes("HMAC verification failed")) {
|
|
2031
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
2032
|
+
console.error(
|
|
2033
|
+
"\u26A0\uFE0F SECURITY ALERT: Data integrity check failed! Data may have been tampered with."
|
|
2034
|
+
);
|
|
2035
|
+
}
|
|
2036
|
+
throw error;
|
|
2037
|
+
}
|
|
2038
|
+
if (error instanceof Error && error.message.includes("Data timestamp validation failed")) {
|
|
2039
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
2040
|
+
console.warn("\u26A0\uFE0F SECURITY WARNING: Data timestamp validation failed");
|
|
2041
|
+
}
|
|
2042
|
+
throw error;
|
|
2043
|
+
}
|
|
2044
|
+
try {
|
|
2045
|
+
const decryptedStr = base64Decode(encryptedStr);
|
|
2046
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
2047
|
+
console.warn(
|
|
2048
|
+
`\u26A0\uFE0F SECURITY WARNING: Reading unencrypted data for key "${key}". This may be a security risk if sensitive data was stored without encryption.`
|
|
2049
|
+
);
|
|
2050
|
+
}
|
|
2051
|
+
return decryptedStr;
|
|
2052
|
+
} catch {
|
|
2053
|
+
throw error;
|
|
2054
|
+
}
|
|
1707
2055
|
}
|
|
1708
|
-
|
|
1709
|
-
|
|
2056
|
+
function handleNoKeyDecode(encryptedStr, key) {
|
|
2057
|
+
try {
|
|
2058
|
+
const decryptedStr = base64Decode(encryptedStr);
|
|
2059
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
2060
|
+
console.warn(
|
|
2061
|
+
`\u26A0\uFE0F SECURITY WARNING: Reading unencrypted data for key "${key}" without encryption keys.`
|
|
2062
|
+
);
|
|
2063
|
+
}
|
|
2064
|
+
return decryptedStr;
|
|
2065
|
+
} catch (error) {
|
|
2066
|
+
throw new Error(`Failed to decode storage value for key "${key}"`);
|
|
2067
|
+
}
|
|
1710
2068
|
}
|
|
1711
|
-
async function decryptValue(encryptedValue, privateKey) {
|
|
2069
|
+
async function decryptValue(encryptedValue, privateKey, hmacKey) {
|
|
1712
2070
|
try {
|
|
2071
|
+
const item = JSON.parse(encryptedValue);
|
|
2072
|
+
if (item.encrypted && item.data && item.hmac) {
|
|
2073
|
+
if (hmacKey) {
|
|
2074
|
+
const isValid = await verifyHMAC(item.data, item.hmac, hmacKey);
|
|
2075
|
+
if (!isValid) {
|
|
2076
|
+
throw new Error("HMAC verification failed: data may have been tampered with");
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
if (item.timestamp && !validateTimestamp(item.timestamp)) {
|
|
2080
|
+
throw new Error("Data timestamp validation failed: data may be expired or replayed");
|
|
2081
|
+
}
|
|
2082
|
+
return await rsaDecrypt(item.data, privateKey);
|
|
2083
|
+
}
|
|
1713
2084
|
return await rsaDecrypt(encryptedValue, privateKey);
|
|
1714
|
-
} catch {
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
return encryptedValue;
|
|
1718
|
-
} catch {
|
|
1719
|
-
throw new Error("Failed to decrypt storage value");
|
|
2085
|
+
} catch (error) {
|
|
2086
|
+
if (error instanceof Error && error.message.includes("HMAC verification failed")) {
|
|
2087
|
+
throw error;
|
|
1720
2088
|
}
|
|
2089
|
+
throw new Error("Failed to decrypt storage value: invalid format or corrupted data");
|
|
1721
2090
|
}
|
|
1722
2091
|
}
|
|
1723
2092
|
var localStorage = {
|
|
@@ -1739,17 +2108,24 @@ var localStorage = {
|
|
|
1739
2108
|
};
|
|
1740
2109
|
try {
|
|
1741
2110
|
const jsonStr = JSON.stringify(item);
|
|
2111
|
+
await ensureKeyPair();
|
|
1742
2112
|
const keyToUse = publicKey || globalKeyPair?.publicKey;
|
|
1743
2113
|
if (keyToUse) {
|
|
1744
|
-
const encrypted = await encryptValue(jsonStr, keyToUse);
|
|
2114
|
+
const encrypted = await encryptValue(jsonStr, keyToUse, globalHMACKey);
|
|
1745
2115
|
window.localStorage.setItem(key, encrypted);
|
|
1746
2116
|
} else {
|
|
1747
|
-
|
|
1748
|
-
|
|
2117
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
2118
|
+
console.warn(
|
|
2119
|
+
`\u26A0\uFE0F SECURITY WARNING: Storing data without encryption for key "${key}". Data is only Base64 encoded, not encrypted!`
|
|
2120
|
+
);
|
|
2121
|
+
}
|
|
2122
|
+
const encoded = base64Encode(jsonStr);
|
|
1749
2123
|
window.localStorage.setItem(key, encoded);
|
|
1750
2124
|
}
|
|
1751
2125
|
} catch (error) {
|
|
1752
|
-
|
|
2126
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
2127
|
+
console.error("localStorage.set error:", error);
|
|
2128
|
+
}
|
|
1753
2129
|
throw error;
|
|
1754
2130
|
}
|
|
1755
2131
|
},
|
|
@@ -1767,34 +2143,36 @@ var localStorage = {
|
|
|
1767
2143
|
try {
|
|
1768
2144
|
const encryptedStr = window.localStorage.getItem(key);
|
|
1769
2145
|
if (!encryptedStr) return defaultValue;
|
|
2146
|
+
await ensureKeyPair();
|
|
1770
2147
|
let decryptedStr;
|
|
1771
2148
|
const keyToUse = privateKey || globalKeyPair?.privateKey;
|
|
1772
2149
|
if (keyToUse) {
|
|
1773
2150
|
try {
|
|
1774
|
-
decryptedStr = await decryptValue(encryptedStr, keyToUse);
|
|
1775
|
-
} catch {
|
|
1776
|
-
const { base64Decode: base64Decode2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
|
|
2151
|
+
decryptedStr = await decryptValue(encryptedStr, keyToUse, globalHMACKey);
|
|
2152
|
+
} catch (error) {
|
|
1777
2153
|
try {
|
|
1778
|
-
decryptedStr =
|
|
2154
|
+
decryptedStr = await handleDecryptError(error, encryptedStr, key);
|
|
1779
2155
|
} catch {
|
|
1780
2156
|
return defaultValue;
|
|
1781
2157
|
}
|
|
1782
2158
|
}
|
|
1783
2159
|
} else {
|
|
1784
|
-
const { base64Decode: base64Decode2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
|
|
1785
2160
|
try {
|
|
1786
|
-
decryptedStr =
|
|
2161
|
+
decryptedStr = handleNoKeyDecode(encryptedStr, key);
|
|
1787
2162
|
} catch {
|
|
1788
2163
|
return defaultValue;
|
|
1789
2164
|
}
|
|
1790
2165
|
}
|
|
1791
|
-
const item =
|
|
2166
|
+
const item = safeParseJSON(decryptedStr, "object");
|
|
1792
2167
|
if (item.expiry && Date.now() > item.expiry) {
|
|
1793
2168
|
window.localStorage.removeItem(key);
|
|
1794
2169
|
return defaultValue;
|
|
1795
2170
|
}
|
|
1796
2171
|
return item.value;
|
|
1797
|
-
} catch {
|
|
2172
|
+
} catch (error) {
|
|
2173
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
2174
|
+
console.warn(`Failed to parse storage item for key "${key}"`);
|
|
2175
|
+
}
|
|
1798
2176
|
return defaultValue;
|
|
1799
2177
|
}
|
|
1800
2178
|
},
|
|
@@ -1807,7 +2185,9 @@ var localStorage = {
|
|
|
1807
2185
|
try {
|
|
1808
2186
|
window.localStorage.removeItem(key);
|
|
1809
2187
|
} catch (error) {
|
|
1810
|
-
|
|
2188
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
2189
|
+
console.error("localStorage.remove error");
|
|
2190
|
+
}
|
|
1811
2191
|
}
|
|
1812
2192
|
},
|
|
1813
2193
|
/**
|
|
@@ -1818,7 +2198,9 @@ var localStorage = {
|
|
|
1818
2198
|
try {
|
|
1819
2199
|
window.localStorage.clear();
|
|
1820
2200
|
} catch (error) {
|
|
1821
|
-
|
|
2201
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
2202
|
+
console.error("localStorage.clear error");
|
|
2203
|
+
}
|
|
1822
2204
|
}
|
|
1823
2205
|
},
|
|
1824
2206
|
/**
|
|
@@ -1834,7 +2216,9 @@ var localStorage = {
|
|
|
1834
2216
|
if (key) keys2.push(key);
|
|
1835
2217
|
}
|
|
1836
2218
|
} catch (error) {
|
|
1837
|
-
|
|
2219
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
2220
|
+
console.error("localStorage.keys error");
|
|
2221
|
+
}
|
|
1838
2222
|
}
|
|
1839
2223
|
return keys2;
|
|
1840
2224
|
}
|
|
@@ -1854,17 +2238,24 @@ var sessionStorage = {
|
|
|
1854
2238
|
const { publicKey } = options;
|
|
1855
2239
|
try {
|
|
1856
2240
|
const jsonStr = JSON.stringify(value);
|
|
2241
|
+
await ensureKeyPair();
|
|
1857
2242
|
const keyToUse = publicKey || globalKeyPair?.publicKey;
|
|
1858
2243
|
if (keyToUse) {
|
|
1859
|
-
const encrypted = await encryptValue(jsonStr, keyToUse);
|
|
2244
|
+
const encrypted = await encryptValue(jsonStr, keyToUse, globalHMACKey);
|
|
1860
2245
|
window.sessionStorage.setItem(key, encrypted);
|
|
1861
2246
|
} else {
|
|
1862
|
-
|
|
1863
|
-
|
|
2247
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
2248
|
+
console.warn(
|
|
2249
|
+
`\u26A0\uFE0F SECURITY WARNING: Storing data without encryption for key "${key}". Data is only Base64 encoded, not encrypted!`
|
|
2250
|
+
);
|
|
2251
|
+
}
|
|
2252
|
+
const encoded = base64Encode(jsonStr);
|
|
1864
2253
|
window.sessionStorage.setItem(key, encoded);
|
|
1865
2254
|
}
|
|
1866
2255
|
} catch (error) {
|
|
1867
|
-
|
|
2256
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
2257
|
+
console.error("sessionStorage.set error:", error);
|
|
2258
|
+
}
|
|
1868
2259
|
throw error;
|
|
1869
2260
|
}
|
|
1870
2261
|
},
|
|
@@ -1882,28 +2273,27 @@ var sessionStorage = {
|
|
|
1882
2273
|
try {
|
|
1883
2274
|
const encryptedStr = window.sessionStorage.getItem(key);
|
|
1884
2275
|
if (!encryptedStr) return defaultValue;
|
|
2276
|
+
await ensureKeyPair();
|
|
1885
2277
|
let decryptedStr;
|
|
1886
2278
|
const keyToUse = privateKey || globalKeyPair?.privateKey;
|
|
1887
2279
|
if (keyToUse) {
|
|
1888
2280
|
try {
|
|
1889
|
-
decryptedStr = await decryptValue(encryptedStr, keyToUse);
|
|
1890
|
-
} catch {
|
|
1891
|
-
const { base64Decode: base64Decode2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
|
|
2281
|
+
decryptedStr = await decryptValue(encryptedStr, keyToUse, globalHMACKey);
|
|
2282
|
+
} catch (error) {
|
|
1892
2283
|
try {
|
|
1893
|
-
decryptedStr =
|
|
2284
|
+
decryptedStr = await handleDecryptError(error, encryptedStr, key);
|
|
1894
2285
|
} catch {
|
|
1895
2286
|
return defaultValue;
|
|
1896
2287
|
}
|
|
1897
2288
|
}
|
|
1898
2289
|
} else {
|
|
1899
|
-
const { base64Decode: base64Decode2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
|
|
1900
2290
|
try {
|
|
1901
|
-
decryptedStr =
|
|
2291
|
+
decryptedStr = handleNoKeyDecode(encryptedStr, key);
|
|
1902
2292
|
} catch {
|
|
1903
2293
|
return defaultValue;
|
|
1904
2294
|
}
|
|
1905
2295
|
}
|
|
1906
|
-
return
|
|
2296
|
+
return safeParseJSON(decryptedStr);
|
|
1907
2297
|
} catch {
|
|
1908
2298
|
return defaultValue;
|
|
1909
2299
|
}
|
|
@@ -1917,7 +2307,9 @@ var sessionStorage = {
|
|
|
1917
2307
|
try {
|
|
1918
2308
|
window.sessionStorage.removeItem(key);
|
|
1919
2309
|
} catch (error) {
|
|
1920
|
-
|
|
2310
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
2311
|
+
console.error("sessionStorage.remove error");
|
|
2312
|
+
}
|
|
1921
2313
|
}
|
|
1922
2314
|
},
|
|
1923
2315
|
/**
|
|
@@ -1928,7 +2320,9 @@ var sessionStorage = {
|
|
|
1928
2320
|
try {
|
|
1929
2321
|
window.sessionStorage.clear();
|
|
1930
2322
|
} catch (error) {
|
|
1931
|
-
|
|
2323
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.error) {
|
|
2324
|
+
console.error("sessionStorage.clear error");
|
|
2325
|
+
}
|
|
1932
2326
|
}
|
|
1933
2327
|
}
|
|
1934
2328
|
};
|
|
@@ -1963,10 +2357,15 @@ var cookie = {
|
|
|
1963
2357
|
if (typeof document === "undefined") return void 0;
|
|
1964
2358
|
const name = encodeURIComponent(key);
|
|
1965
2359
|
const cookies = document.cookie.split(";");
|
|
1966
|
-
for (const
|
|
1967
|
-
const
|
|
2360
|
+
for (const cookieStr of cookies) {
|
|
2361
|
+
const trimmed = cookieStr.trim();
|
|
2362
|
+
const eqIndex = trimmed.indexOf("=");
|
|
2363
|
+
if (eqIndex === -1) continue;
|
|
2364
|
+
const cookieKey = trimmed.substring(0, eqIndex).trim();
|
|
2365
|
+
const cookieValue = trimmed.substring(eqIndex + 1).trim();
|
|
1968
2366
|
if (cookieKey === name) {
|
|
1969
|
-
|
|
2367
|
+
const decoded = decodeURIComponent(cookieValue);
|
|
2368
|
+
return decoded === "" ? void 0 : decoded;
|
|
1970
2369
|
}
|
|
1971
2370
|
}
|
|
1972
2371
|
return void 0;
|
|
@@ -1990,15 +2389,24 @@ var cookie = {
|
|
|
1990
2389
|
if (typeof document === "undefined") return {};
|
|
1991
2390
|
const cookies = {};
|
|
1992
2391
|
document.cookie.split(";").forEach((cookieStr) => {
|
|
1993
|
-
const
|
|
2392
|
+
const trimmed = cookieStr.trim();
|
|
2393
|
+
if (!trimmed) return;
|
|
2394
|
+
const eqIndex = trimmed.indexOf("=");
|
|
2395
|
+
if (eqIndex === -1) return;
|
|
2396
|
+
const key = trimmed.substring(0, eqIndex).trim();
|
|
2397
|
+
const value = trimmed.substring(eqIndex + 1).trim();
|
|
1994
2398
|
if (key && value) {
|
|
1995
|
-
|
|
2399
|
+
const decodedKey = decodeURIComponent(key);
|
|
2400
|
+
const decodedValue = decodeURIComponent(value);
|
|
2401
|
+
if (decodedValue !== "") {
|
|
2402
|
+
cookies[decodedKey] = decodedValue;
|
|
2403
|
+
}
|
|
1996
2404
|
}
|
|
1997
2405
|
});
|
|
1998
2406
|
return cookies;
|
|
1999
2407
|
}
|
|
2000
2408
|
};
|
|
2001
|
-
var
|
|
2409
|
+
var secureStorage = {
|
|
2002
2410
|
/**
|
|
2003
2411
|
* 设置值(优先使用localStorage,失败则使用sessionStorage)
|
|
2004
2412
|
* @param key - 键
|
|
@@ -2013,7 +2421,9 @@ var storage = {
|
|
|
2013
2421
|
try {
|
|
2014
2422
|
await sessionStorage.set(key, value, { publicKey: options.publicKey });
|
|
2015
2423
|
} catch {
|
|
2016
|
-
|
|
2424
|
+
if (!initOptions.isProduction && typeof console !== "undefined" && console.warn) {
|
|
2425
|
+
console.warn("Both localStorage and sessionStorage are not available");
|
|
2426
|
+
}
|
|
2017
2427
|
}
|
|
2018
2428
|
}
|
|
2019
2429
|
},
|
|
@@ -2043,10 +2453,28 @@ var storage = {
|
|
|
2043
2453
|
clear() {
|
|
2044
2454
|
localStorage.clear();
|
|
2045
2455
|
sessionStorage.clear();
|
|
2456
|
+
},
|
|
2457
|
+
/**
|
|
2458
|
+
* 获取所有键名
|
|
2459
|
+
* @returns 键名数组
|
|
2460
|
+
*/
|
|
2461
|
+
keys() {
|
|
2462
|
+
const localKeys = localStorage.keys();
|
|
2463
|
+
const sessionKeys = [];
|
|
2464
|
+
if (typeof window !== "undefined" && window.sessionStorage) {
|
|
2465
|
+
try {
|
|
2466
|
+
for (let i = 0; i < window.sessionStorage.length; i++) {
|
|
2467
|
+
const key = window.sessionStorage.key(i);
|
|
2468
|
+
if (key) sessionKeys.push(key);
|
|
2469
|
+
}
|
|
2470
|
+
} catch (error) {
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
return Array.from(/* @__PURE__ */ new Set([...localKeys, ...sessionKeys]));
|
|
2046
2474
|
}
|
|
2047
2475
|
};
|
|
2048
2476
|
|
|
2049
|
-
// src/browser/network.ts
|
|
2477
|
+
// src/browser/network/index.ts
|
|
2050
2478
|
async function fetchWithRetry(url, options = {}, retryCount = 3, retryDelay = 1e3) {
|
|
2051
2479
|
let lastError = null;
|
|
2052
2480
|
for (let i = 0; i <= retryCount; i++) {
|
|
@@ -2143,7 +2571,7 @@ async function request(url, options = {}) {
|
|
|
2143
2571
|
return response.text();
|
|
2144
2572
|
}
|
|
2145
2573
|
|
|
2146
|
-
// src/browser/dom.ts
|
|
2574
|
+
// src/browser/dom/index.ts
|
|
2147
2575
|
function $(selector, context = document) {
|
|
2148
2576
|
return context.querySelector(selector);
|
|
2149
2577
|
}
|
|
@@ -2227,7 +2655,7 @@ async function copyToClipboard(text) {
|
|
|
2227
2655
|
}
|
|
2228
2656
|
}
|
|
2229
2657
|
|
|
2230
|
-
// src/data/transform.ts
|
|
2658
|
+
// src/data/transform/index.ts
|
|
2231
2659
|
function csvToJson(csv, options = {}) {
|
|
2232
2660
|
const { delimiter = ",", headers, skipEmptyLines = true } = options;
|
|
2233
2661
|
const lines = csv.split(/\r?\n/).filter((line) => {
|
|
@@ -2526,7 +2954,7 @@ ${valStr}`;
|
|
|
2526
2954
|
return String(value);
|
|
2527
2955
|
}
|
|
2528
2956
|
|
|
2529
|
-
// src/data/data-structure.ts
|
|
2957
|
+
// src/data/data-structure/index.ts
|
|
2530
2958
|
var Stack = class {
|
|
2531
2959
|
constructor() {
|
|
2532
2960
|
this.items = [];
|
|
@@ -3007,7 +3435,7 @@ var LRUCache = class {
|
|
|
3007
3435
|
}
|
|
3008
3436
|
};
|
|
3009
3437
|
|
|
3010
|
-
// src/data/algorithm.ts
|
|
3438
|
+
// src/data/algorithm/index.ts
|
|
3011
3439
|
function binarySearch(array, target, compareFn) {
|
|
3012
3440
|
let left = 0;
|
|
3013
3441
|
let right = array.length - 1;
|
|
@@ -3137,7 +3565,7 @@ function lcm(a, b) {
|
|
|
3137
3565
|
return Math.abs(a * b) / gcd(a, b);
|
|
3138
3566
|
}
|
|
3139
3567
|
|
|
3140
|
-
// src/helper/performance.ts
|
|
3568
|
+
// src/helper/performance/index.ts
|
|
3141
3569
|
function debounce(fn, delay) {
|
|
3142
3570
|
let timeoutId = null;
|
|
3143
3571
|
return function(...args) {
|
|
@@ -3282,10 +3710,7 @@ var Queue = class {
|
|
|
3282
3710
|
}
|
|
3283
3711
|
};
|
|
3284
3712
|
|
|
3285
|
-
// src/index.ts
|
|
3286
|
-
init_crypto();
|
|
3287
|
-
|
|
3288
|
-
// src/helper/tracking.ts
|
|
3713
|
+
// src/helper/tracking/index.ts
|
|
3289
3714
|
var Tracker = class {
|
|
3290
3715
|
constructor(options) {
|
|
3291
3716
|
this.eventQueue = [];
|
|
@@ -3918,4 +4343,4 @@ function convertRes2Blob(response) {
|
|
|
3918
4343
|
}
|
|
3919
4344
|
}
|
|
3920
4345
|
|
|
3921
|
-
export { $, $$, BinaryTree, ChunkUploader, DataQueue, Graph, LRUCache, LinkedList, Queue, Stack, Tracker, UploadStatus, addClass, addDays, addMonths, addYears, base64Decode, base64Encode, batch, binarySearch, bubbleSort, buildUrl, calculateBlobMD5, calculateFileMD5, camelCase, capitalize, ceil, checkOnline, chunk, clamp, compact, contrast, cookie, copyToClipboard, createTracker, createTranslator, createUploader, csvToJson, darken, debounce, deepClone, deepMerge, defaults, diffDays, difference, downloadFile, drop, dropWhile, endOfDay, escapeHtml, exportPrivateKey, exportPublicKey, factorial, fetchWithRetry, fetchWithTimeout, fibonacci, fibonacciSequence, findIndexBy, flatten, floor, flush, formatBytes, formatCurrency, formatCurrencyI18n, formatDate, formatDateI18n, formatFileSize, formatNumber, formatNumberI18n, formatRelativeTime, gcd, generateRSAKeyPair, generateRandomString, generateUUID, get, getDateFormatByGMT, getElementOffset, getFileExtension, getFileNameWithoutExtension, getLocale, getQuarter, getQueryParams, getRelativeTime, getScrollPosition, getStorageKeyPair, getStyle, getTimeFromGMT, getTracker, getWeekNumber, groupBy, hash, hexToRgb, highlight, hslToRgb, importPrivateKey, importPublicKey, initTracker, intersection, invert, isAbsoluteUrl, isBetween, isEmpty, isEmptyObject, isEqual, isFloat, isInViewport, isInteger, isNumeric, isToday, isValidDomain, isValidEmail, isValidHexColor, isValidIP, isValidIdCard, isValidJSON, isValidPhone, isValidUUID, isValidUrl, isWeekday, isWeekend, isYesterday, joinUrl, jsonToCsv, jsonToXml, jsonToYaml, kebabCase, keys, lcm, lighten,
|
|
4346
|
+
export { $, $$, BinaryTree, ChunkUploader, DataQueue, Graph, LRUCache, LinkedList, Queue, Stack, Tracker, UploadStatus, addClass, addDays, addMonths, addYears, aesGCMDecrypt, aesGCMEncrypt, base64Decode, base64Encode, batch, binarySearch, bubbleSort, buildUrl, calculateBlobMD5, calculateFileMD5, camelCase, capitalize, ceil, checkOnline, chunk, clamp, clearPersistedKeys, compact, computeHMAC, contrast, cookie, copyToClipboard, createTracker, createTranslator, createUploader, csvToJson, darken, debounce, deepClone, deepMerge, defaults, deriveKeyFromPassword, diffDays, difference, downloadFile, drop, dropWhile, endOfDay, escapeHtml, exportPrivateKey, exportPublicKey, factorial, fetchWithRetry, fetchWithTimeout, fibonacci, fibonacciSequence, findIndexBy, flatten, floor, flush, formatBytes, formatCurrency, formatCurrencyI18n, formatDate, formatDateI18n, formatFileSize, formatNumber, formatNumberI18n, formatRelativeTime, gcd, generateHMACKey, generateRSAKeyPair, generateRandomString, generateUUID, get, getDateFormatByGMT, getElementOffset, getFileExtension, getFileNameWithoutExtension, getKeyUsageCount, getLocale, getQuarter, getQueryParams, getRelativeTime, getScrollPosition, getStorageKeyPair, getStyle, getTimeFromGMT, getTracker, getWeekNumber, groupBy, hash, hexToRgb, highlight, hslToRgb, importPrivateKey, importPublicKey, initTracker, initializeStorageKeys, intersection, invert, isAbsoluteUrl, isBetween, isEmpty, isEmptyObject, isEqual, isFloat, isInViewport, isInteger, isNumeric, isToday, isValidDomain, isValidEmail, isValidHexColor, isValidIP, isValidIdCard, isValidJSON, isValidPhone, isValidUUID, isValidUrl, isWeekday, isWeekend, isYesterday, joinUrl, jsonToCsv, jsonToXml, jsonToYaml, kebabCase, keys, lcm, lighten, mapKeys, mapValues, mask, maskEmail, maskPhone, memoize, merge, mergeSort, mix, normalizeUrl, omit, omitBy, once, parseNumber, parseUrl, partition, pascalCase, percent, pick, pickBy, pluralize, quickSort, random, removeAccents, removeClass, removeQueryParams, request, resetKeyUsageCount, retry, rgbToHex, rgbToHsl, round, rsaDecrypt, rsaEncrypt, sample, scrollTo, secureStorage, set, setCommonParams, setQueryParams, setServiceEventHandlers, setStorageKeyPair, setStyle, setUserInfo, sha256, shuffle, slugify, snakeCase, sortBy, splitFileIntoChunks, startOfDay, take, takeWhile, template, throttle, timeout, toFixed, toggleClass, trackClick, trackEvent, trackExposure, trackPageView, transform, translate, truncate, unescapeHtml, union, unique, unzip, updateQueryParams, uploadFile, values, verifyHMAC, xmlToJson, yamlToJson, zip };
|