@alibarbar/common 1.1.2 → 1.1.4

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/crypto.js CHANGED
@@ -1,10 +1,7 @@
1
- // src/helper/crypto/index.ts
2
- async function sha256(data) {
3
- const buffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
4
- const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
5
- const hashArray = Array.from(new Uint8Array(hashBuffer));
6
- return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
7
- }
1
+ import JSEncrypt from 'jsencrypt';
2
+ import CryptoJS from 'crypto-js';
3
+
4
+ // src/helper/encryption/index.ts
8
5
  function base64Encode(data) {
9
6
  if (typeof data === "string") {
10
7
  return btoa(unescape(encodeURIComponent(data)));
@@ -16,264 +13,199 @@ function base64Encode(data) {
16
13
  }
17
14
  return btoa(binary);
18
15
  }
19
- function base64Decode(data) {
20
- try {
21
- return decodeURIComponent(escape(atob(data)));
22
- } catch {
23
- throw new Error("Invalid Base64 string");
24
- }
16
+ function generateRandomAESKeyString() {
17
+ return CryptoJS.lib.WordArray.random(256 / 8).toString();
25
18
  }
26
- function generateUUID() {
27
- if (typeof crypto !== "undefined" && crypto.randomUUID) {
28
- return crypto.randomUUID();
19
+ function encryptJsonWithAES(data, aesKey) {
20
+ try {
21
+ const jsonString = JSON.stringify(data);
22
+ return CryptoJS.AES.encrypt(jsonString, aesKey).toString();
23
+ } catch (error) {
24
+ throw new Error(
25
+ "[encryption] AES encryptJsonWithAES failed: " + (error instanceof Error ? error.message : String(error))
26
+ );
29
27
  }
30
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
31
- const r = Math.random() * 16 | 0;
32
- const v = c === "x" ? r : r & 3 | 8;
33
- return v.toString(16);
34
- });
35
28
  }
36
- function generateRandomString(length, charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") {
37
- let result = "";
38
- for (let i = 0; i < length; i++) {
39
- result += charset.charAt(Math.floor(Math.random() * charset.length));
29
+ function decryptJsonWithAES(encryptedData, aesKey) {
30
+ try {
31
+ const decrypted = CryptoJS.AES.decrypt(encryptedData, aesKey);
32
+ const jsonString = decrypted.toString(CryptoJS.enc.Utf8);
33
+ if (!jsonString) {
34
+ throw new Error("decrypted JSON string is empty");
35
+ }
36
+ return JSON.parse(jsonString);
37
+ } catch (error) {
38
+ throw new Error(
39
+ "[encryption] AES decryptJsonWithAES failed: " + (error instanceof Error ? error.message : String(error))
40
+ );
40
41
  }
41
- return result;
42
42
  }
43
- function hash(data) {
44
- let hashValue = 0;
45
- for (let i = 0; i < data.length; i++) {
46
- const char = data.charCodeAt(i);
47
- hashValue = (hashValue << 5) - hashValue + char;
48
- hashValue = hashValue & hashValue;
43
+ function getEnvPublicKey() {
44
+ const key = import.meta.env.VITE_RSA_PUBLIC_KEY?.trim();
45
+ if (!key) {
46
+ throw new Error(
47
+ "[encryption] VITE_RSA_PUBLIC_KEY is not set. Please configure RSA public key in environment variables."
48
+ );
49
49
  }
50
- return Math.abs(hashValue);
50
+ return key;
51
51
  }
52
- async function generateRSAKeyPair(modulusLength = 2048) {
53
- if (typeof crypto === "undefined" || !crypto.subtle) {
54
- throw new Error("Web Crypto API is not available");
55
- }
56
- const keyPair = await crypto.subtle.generateKey(
57
- {
58
- name: "RSA-OAEP",
59
- modulusLength,
60
- publicExponent: new Uint8Array([1, 0, 1]),
61
- hash: "SHA-256"
62
- },
63
- true,
64
- ["encrypt", "decrypt"]
65
- );
66
- return {
67
- publicKey: keyPair.publicKey,
68
- privateKey: keyPair.privateKey
69
- };
70
- }
71
- async function rsaEncrypt(data, publicKey) {
72
- if (typeof crypto === "undefined" || !crypto.subtle) {
73
- throw new Error("Web Crypto API is not available");
74
- }
75
- const encoder = new TextEncoder();
76
- const dataBuffer = encoder.encode(data);
77
- const algo = publicKey.algorithm;
78
- const modulusLength = typeof algo.modulusLength === "number" ? algo.modulusLength : 2048;
79
- const hashName = typeof algo.hash === "object" && "name" in algo.hash ? algo.hash.name : "SHA-256";
80
- const hashLength = hashName === "SHA-1" ? 20 : 32;
81
- const maxChunkSize = Math.max(Math.floor(modulusLength / 8 - 2 * hashLength - 2), 1);
82
- const chunks = [];
83
- for (let i = 0; i < dataBuffer.length; i += maxChunkSize) {
84
- const chunk = dataBuffer.slice(i, i + maxChunkSize);
85
- const encrypted = await crypto.subtle.encrypt(
86
- {
87
- name: "RSA-OAEP"
88
- },
89
- publicKey,
90
- chunk
52
+ function getEnvPrivateKey() {
53
+ const key = import.meta.env.VITE_RSA_PRIVATE_KEY?.trim();
54
+ if (!key) {
55
+ throw new Error(
56
+ "[encryption] VITE_RSA_PRIVATE_KEY is not set. Please configure RSA private key in environment variables."
91
57
  );
92
- chunks.push(encrypted);
93
- }
94
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
95
- const merged = new Uint8Array(totalLength);
96
- let offset = 0;
97
- for (const chunk of chunks) {
98
- merged.set(new Uint8Array(chunk), offset);
99
- offset += chunk.byteLength;
100
58
  }
101
- return base64Encode(merged.buffer);
59
+ return key;
102
60
  }
103
- async function rsaDecrypt(encryptedData, privateKey) {
104
- if (typeof crypto === "undefined" || !crypto.subtle) {
105
- throw new Error("Web Crypto API is not available");
106
- }
107
- const binaryString = atob(encryptedData);
108
- const encryptedArray = new Uint8Array(binaryString.length);
109
- for (let i = 0; i < binaryString.length; i++) {
110
- encryptedArray[i] = binaryString.charCodeAt(i);
111
- }
112
- const chunkSize = 256;
61
+ function base64ToPem(base64Key, type) {
113
62
  const chunks = [];
114
- for (let i = 0; i < encryptedArray.length; i += chunkSize) {
115
- const chunk = encryptedArray.slice(i, i + chunkSize);
116
- const decrypted = await crypto.subtle.decrypt(
117
- {
118
- name: "RSA-OAEP"
119
- },
120
- privateKey,
121
- chunk
122
- );
123
- const decoder = new TextDecoder();
124
- chunks.push(decoder.decode(decrypted));
63
+ for (let i = 0; i < base64Key.length; i += 64) {
64
+ chunks.push(base64Key.slice(i, i + 64));
125
65
  }
126
- return chunks.join("");
66
+ const body = chunks.join("\n");
67
+ const header = type === "PUBLIC" ? "-----BEGIN PUBLIC KEY-----" : "-----BEGIN PRIVATE KEY-----";
68
+ const footer = type === "PUBLIC" ? "-----END PUBLIC KEY-----" : "-----END PRIVATE KEY-----";
69
+ return `${header}
70
+ ${body}
71
+ ${footer}`;
127
72
  }
128
- async function exportPublicKey(publicKey) {
129
- if (typeof crypto === "undefined" || !crypto.subtle) {
130
- throw new Error("Web Crypto API is not available");
73
+ async function rsaEncrypt(plain, publicKeyPem) {
74
+ try {
75
+ const encrypt = new JSEncrypt();
76
+ let publicKeyString;
77
+ if (typeof publicKeyPem === "string") {
78
+ const trimmedKey = publicKeyPem.trim();
79
+ if (!trimmedKey) {
80
+ throw new Error("Public key string is empty");
81
+ }
82
+ if (trimmedKey.includes("-----BEGIN")) {
83
+ publicKeyString = trimmedKey;
84
+ } else {
85
+ publicKeyString = base64ToPem(trimmedKey, "PUBLIC");
86
+ }
87
+ } else if (publicKeyPem instanceof CryptoJS.lib.WordArray) {
88
+ const base64Key = publicKeyPem.toString();
89
+ publicKeyString = base64ToPem(base64Key, "PUBLIC");
90
+ } else {
91
+ try {
92
+ const exported = await crypto.subtle.exportKey("spki", publicKeyPem);
93
+ const base64Key = base64Encode(exported);
94
+ publicKeyString = base64ToPem(base64Key, "PUBLIC");
95
+ } catch (error) {
96
+ throw new Error(
97
+ "Failed to export CryptoKey. In non-HTTPS environment, use string publicKey (Base64 or PEM) directly. " + (error instanceof Error ? error.message : String(error))
98
+ );
99
+ }
100
+ }
101
+ encrypt.setPublicKey(publicKeyString);
102
+ const MAX_ENCRYPT_LENGTH = 200;
103
+ if (plain.length > MAX_ENCRYPT_LENGTH) {
104
+ const chunks = [];
105
+ for (let i = 0; i < plain.length; i += MAX_ENCRYPT_LENGTH) {
106
+ const chunk = plain.slice(i, i + MAX_ENCRYPT_LENGTH);
107
+ const encrypted2 = encrypt.encrypt(chunk);
108
+ if (!encrypted2) {
109
+ throw new Error(
110
+ `RSA encryption failed for chunk ${i / MAX_ENCRYPT_LENGTH + 1}. Public key may be invalid or JSEncrypt may not be loaded correctly.`
111
+ );
112
+ }
113
+ chunks.push(encrypted2);
114
+ }
115
+ return chunks.join("|");
116
+ }
117
+ const encrypted = encrypt.encrypt(plain);
118
+ if (!encrypted) {
119
+ throw new Error(
120
+ `RSA encryption failed. Public key may be invalid or JSEncrypt may not be loaded correctly. Plain text length: ${plain.length} bytes.`
121
+ );
122
+ }
123
+ return encrypted;
124
+ } catch (error) {
125
+ if (error instanceof Error) {
126
+ if (error.message.includes("RSA encryption failed") || error.message.includes("Public key")) {
127
+ throw error;
128
+ }
129
+ throw new Error(`[encryption] rsaEncrypt failed: ${error.message}`);
130
+ }
131
+ throw new Error(`[encryption] rsaEncrypt failed: ${String(error)}`);
132
+ }
133
+ }
134
+ async function rsaDecrypt(cipher, privateKeyPem) {
135
+ const decrypt = new JSEncrypt();
136
+ let privateKeyString;
137
+ if (typeof privateKeyPem === "string") {
138
+ if (privateKeyPem.includes("-----BEGIN")) {
139
+ privateKeyString = privateKeyPem.trim();
140
+ } else {
141
+ const trimmed = privateKeyPem.trim();
142
+ privateKeyString = base64ToPem(trimmed, "PRIVATE");
143
+ }
144
+ } else if (privateKeyPem instanceof CryptoJS.lib.WordArray) {
145
+ const base64Key = privateKeyPem.toString();
146
+ privateKeyString = base64ToPem(base64Key, "PRIVATE");
147
+ } else {
148
+ try {
149
+ const exported = await crypto.subtle.exportKey("pkcs8", privateKeyPem);
150
+ const base64Key = base64Encode(exported);
151
+ privateKeyString = base64ToPem(base64Key, "PRIVATE");
152
+ } catch (error) {
153
+ throw new Error(
154
+ "[encryption] rsaDecrypt: failed to export CryptoKey. In non-HTTPS environment, use PEM string directly."
155
+ );
156
+ }
157
+ }
158
+ decrypt.setPrivateKey(privateKeyString);
159
+ const chunks = cipher.split("|");
160
+ const decryptedChunks = [];
161
+ for (const chunk of chunks) {
162
+ const decrypted = decrypt.decrypt(chunk);
163
+ if (!decrypted) {
164
+ throw new Error("[encryption] rsaDecrypt: RSA decryption failed");
165
+ }
166
+ decryptedChunks.push(decrypted);
131
167
  }
132
- const exported = await crypto.subtle.exportKey("spki", publicKey);
133
- return base64Encode(exported);
168
+ return decryptedChunks.join("");
134
169
  }
135
- async function exportPrivateKey(privateKey) {
136
- if (typeof crypto === "undefined" || !crypto.subtle) {
137
- throw new Error("Web Crypto API is not available");
138
- }
139
- const exported = await crypto.subtle.exportKey("pkcs8", privateKey);
140
- return base64Encode(exported);
170
+ async function encryptWithEnvPublicKey(plain) {
171
+ const publicKey = getEnvPublicKey();
172
+ return rsaEncrypt(plain, publicKey);
141
173
  }
142
- async function importPublicKey(keyData) {
143
- if (typeof crypto === "undefined" || !crypto.subtle) {
144
- throw new Error("Web Crypto API is not available");
145
- }
146
- const keyBuffer = base64Decode(keyData);
147
- const keyArray = new Uint8Array(keyBuffer.split("").map((char) => char.charCodeAt(0)));
148
- return crypto.subtle.importKey(
149
- "spki",
150
- keyArray.buffer,
151
- {
152
- name: "RSA-OAEP",
153
- hash: "SHA-256"
154
- },
155
- true,
156
- ["encrypt"]
157
- );
174
+ async function decryptWithEnvPrivateKey(cipher) {
175
+ const privateKey = getEnvPrivateKey();
176
+ return rsaDecrypt(cipher, privateKey);
158
177
  }
159
- async function importPrivateKey(keyData) {
178
+ async function generateRSAKeyPair(modulusLength = 2048) {
160
179
  if (typeof crypto === "undefined" || !crypto.subtle) {
161
- throw new Error("Web Crypto API is not available");
180
+ throw new Error(
181
+ "[encryption] Web Crypto API is not available. Cannot generate RSA key pair. Please use fixed key pair from environment variables."
182
+ );
162
183
  }
163
- const keyBuffer = base64Decode(keyData);
164
- const keyArray = new Uint8Array(keyBuffer.split("").map((char) => char.charCodeAt(0)));
165
- return crypto.subtle.importKey(
166
- "pkcs8",
167
- keyArray.buffer,
184
+ const keyPair = await crypto.subtle.generateKey(
168
185
  {
169
186
  name: "RSA-OAEP",
187
+ modulusLength,
188
+ publicExponent: new Uint8Array([1, 0, 1]),
170
189
  hash: "SHA-256"
171
190
  },
172
191
  true,
173
- ["decrypt"]
174
- );
175
- }
176
- async function generateHMACKey() {
177
- if (typeof crypto === "undefined" || !crypto.subtle) {
178
- throw new Error("Web Crypto API is not available");
179
- }
180
- return crypto.subtle.generateKey(
181
- {
182
- name: "HMAC",
183
- hash: "SHA-256"
184
- },
185
- true,
186
- ["sign", "verify"]
187
- );
188
- }
189
- async function computeHMAC(data, key) {
190
- if (typeof crypto === "undefined" || !crypto.subtle) {
191
- throw new Error("Web Crypto API is not available");
192
- }
193
- const buffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
194
- const signature = await crypto.subtle.sign("HMAC", key, buffer);
195
- return base64Encode(signature);
196
- }
197
- async function verifyHMAC(data, signature, key) {
198
- if (typeof crypto === "undefined" || !crypto.subtle) {
199
- throw new Error("Web Crypto API is not available");
200
- }
201
- try {
202
- const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
203
- const signatureBuffer = new Uint8Array(
204
- atob(signature).split("").map((char) => char.charCodeAt(0))
205
- );
206
- return await crypto.subtle.verify("HMAC", key, signatureBuffer, dataBuffer);
207
- } catch {
208
- return false;
209
- }
210
- }
211
- async function deriveKeyFromPassword(password, salt, iterations = 1e5, keyLength = 256) {
212
- if (typeof crypto === "undefined" || !crypto.subtle) {
213
- throw new Error("Web Crypto API is not available");
214
- }
215
- const saltBuffer = typeof salt === "string" ? new TextEncoder().encode(salt) : salt;
216
- const passwordKey = await crypto.subtle.importKey(
217
- "raw",
218
- new TextEncoder().encode(password),
219
- "PBKDF2",
220
- false,
221
- ["deriveBits", "deriveKey"]
222
- );
223
- return crypto.subtle.deriveKey(
224
- {
225
- name: "PBKDF2",
226
- salt: saltBuffer,
227
- iterations,
228
- hash: "SHA-256"
229
- },
230
- passwordKey,
231
- {
232
- name: "AES-GCM",
233
- length: keyLength
234
- },
235
- false,
236
192
  ["encrypt", "decrypt"]
237
193
  );
238
- }
239
- async function aesGCMEncrypt(data, key) {
240
- if (typeof crypto === "undefined" || !crypto.subtle) {
241
- throw new Error("Web Crypto API is not available");
242
- }
243
- const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
244
- const iv = crypto.getRandomValues(new Uint8Array(12));
245
- const encrypted = await crypto.subtle.encrypt(
246
- {
247
- name: "AES-GCM",
248
- iv
249
- },
250
- key,
251
- dataBuffer
252
- );
194
+ const publicKeyDer = await crypto.subtle.exportKey("spki", keyPair.publicKey);
195
+ const privateKeyDer = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
196
+ const publicKeyBase64 = base64Encode(publicKeyDer);
197
+ const privateKeyBase64 = base64Encode(privateKeyDer);
253
198
  return {
254
- encrypted: base64Encode(encrypted),
255
- iv: base64Encode(iv.buffer)
199
+ publicKey: base64ToPem(publicKeyBase64, "PUBLIC"),
200
+ privateKey: base64ToPem(privateKeyBase64, "PRIVATE")
256
201
  };
257
202
  }
258
- async function aesGCMDecrypt(encryptedData, iv, key) {
259
- if (typeof crypto === "undefined" || !crypto.subtle) {
260
- throw new Error("Web Crypto API is not available");
261
- }
262
- const encryptedBuffer = new Uint8Array(
263
- atob(encryptedData).split("").map((char) => char.charCodeAt(0))
264
- );
265
- const ivBuffer = new Uint8Array(
266
- atob(iv).split("").map((char) => char.charCodeAt(0))
267
- );
268
- const decrypted = await crypto.subtle.decrypt(
269
- {
270
- name: "AES-GCM",
271
- iv: ivBuffer
272
- },
273
- key,
274
- encryptedBuffer
275
- );
276
- return new TextDecoder().decode(decrypted);
277
- }
203
+ var rsaEnv = {
204
+ encrypt: encryptWithEnvPublicKey,
205
+ decrypt: decryptWithEnvPrivateKey,
206
+ getPublicKey: getEnvPublicKey,
207
+ getPrivateKey: getEnvPrivateKey
208
+ };
209
+ var encryption_default = rsaEnv;
278
210
 
279
- export { aesGCMDecrypt, aesGCMEncrypt, base64Decode, base64Encode, computeHMAC, deriveKeyFromPassword, exportPrivateKey, exportPublicKey, generateHMACKey, generateRSAKeyPair, generateRandomString, generateUUID, hash, importPrivateKey, importPublicKey, rsaDecrypt, rsaEncrypt, sha256, verifyHMAC };
211
+ export { base64Encode, decryptJsonWithAES, decryptWithEnvPrivateKey, encryption_default as default, encryptJsonWithAES, encryptWithEnvPublicKey, generateRSAKeyPair, generateRandomAESKeyString, getEnvPrivateKey, getEnvPublicKey, rsaDecrypt, rsaEncrypt };
@@ -148,6 +148,8 @@ declare class ChunkUploader {
148
148
  private uploadedChunks;
149
149
  private status;
150
150
  private abortController;
151
+ /** 进度流专用 AbortController,用于在上传结束/取消时关闭流 */
152
+ private progressStreamAbortController;
151
153
  constructor(file: File, options?: UploadOptions);
152
154
  /**
153
155
  * 初始化上传
@@ -174,9 +176,13 @@ declare class ChunkUploader {
174
176
  */
175
177
  private uploadChunksConcurrently;
176
178
  /**
177
- * 更新上传进度(仅用于回调前端显示)
179
+ * 启动进度流式接口(单次请求,通过 ReadableStream 消费多条 SSE 事件)
178
180
  */
179
- private updateProgress;
181
+ private startProgressStream;
182
+ /**
183
+ * 关闭进度流
184
+ */
185
+ private closeProgressStream;
180
186
  /**
181
187
  * 获取上传进度
182
188
  */
@@ -148,6 +148,8 @@ declare class ChunkUploader {
148
148
  private uploadedChunks;
149
149
  private status;
150
150
  private abortController;
151
+ /** 进度流专用 AbortController,用于在上传结束/取消时关闭流 */
152
+ private progressStreamAbortController;
151
153
  constructor(file: File, options?: UploadOptions);
152
154
  /**
153
155
  * 初始化上传
@@ -174,9 +176,13 @@ declare class ChunkUploader {
174
176
  */
175
177
  private uploadChunksConcurrently;
176
178
  /**
177
- * 更新上传进度(仅用于回调前端显示)
179
+ * 启动进度流式接口(单次请求,通过 ReadableStream 消费多条 SSE 事件)
178
180
  */
179
- private updateProgress;
181
+ private startProgressStream;
182
+ /**
183
+ * 关闭进度流
184
+ */
185
+ private closeProgressStream;
180
186
  /**
181
187
  * 获取上传进度
182
188
  */