@fast-china/utils 2.0.2 → 2.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/CHANGELOG.md +24 -0
- package/README.md +11 -1
- package/README.zh.md +12 -2
- package/dist/base64/index.d.mts +2 -2
- package/dist/base64/index.mjs +8 -7
- package/dist/base64/index.mjs.map +1 -1
- package/dist/crypto/index.d.mts +7 -6
- package/dist/crypto/index.mjs +15 -24
- package/dist/crypto/index.mjs.map +1 -1
- package/dist/env/index.d.mts +2 -2
- package/dist/env/index.mjs +12 -10
- package/dist/env/index.mjs.map +1 -1
- package/dist/identity/index.d.mts +6 -6
- package/dist/identity/index.mjs +4 -4
- package/dist/identity/index.mjs.map +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.global.min.js +2 -2
- package/dist/index.global.min.js.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/internal/text.mjs +2 -3
- package/dist/internal/text.mjs.map +1 -1
- package/dist/logger/index.mjs +1 -2
- package/dist/logger/index.mjs.map +1 -1
- package/dist/number/index.d.mts +6 -5
- package/dist/number/index.mjs +10 -9
- package/dist/number/index.mjs.map +1 -1
- package/dist/storage/index.mjs +2 -3
- package/dist/storage/index.mjs.map +1 -1
- package/dist/string/index.d.mts +18 -6
- package/dist/string/index.mjs +77 -24
- package/dist/string/index.mjs.map +1 -1
- package/docs/API.md +30 -16
- package/docs/API.zh-CN.md +21 -7
- package/docs/RUNTIME_CONTRACT.md +2 -2
- package/package.json +1 -1
package/dist/string/index.mjs
CHANGED
|
@@ -3,18 +3,17 @@ const defaultRandomAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
|
|
|
3
3
|
const defaultStringLocale = "en-US";
|
|
4
4
|
const maximumRandomStringLength = 1e6;
|
|
5
5
|
const maximumRandomValuesPerBatch = 16384;
|
|
6
|
+
const uint32Range = 4294967296;
|
|
6
7
|
const uuidV4Pattern = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
if (typeof crypto?.getRandomValues !== "function") throw new Error("Web Crypto random generation is unavailable in the current runtime.");
|
|
17
|
-
return crypto;
|
|
8
|
+
/** 使用 Web Crypto 填充随机值,能力缺失时回退到 `Math.random()`。 */
|
|
9
|
+
const fillRandomValues = (values) => {
|
|
10
|
+
const crypto = globalThis.crypto;
|
|
11
|
+
if (typeof crypto?.getRandomValues === "function") {
|
|
12
|
+
crypto.getRandomValues(values);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const range = values.BYTES_PER_ELEMENT === Uint8Array.BYTES_PER_ELEMENT ? 256 : uint32Range;
|
|
16
|
+
for (let index = 0; index < values.length; index += 1) values[index] = Math.floor(Math.random() * range);
|
|
18
17
|
};
|
|
19
18
|
/**
|
|
20
19
|
* 从随机字节创建 UUID v4。
|
|
@@ -37,7 +36,7 @@ const createUuidV4FromBytes = (bytes) => {
|
|
|
37
36
|
* @throws `Error` 当平台缺少 `Intl.Segmenter`。
|
|
38
37
|
*/
|
|
39
38
|
const splitGraphemes = (value, locale) => {
|
|
40
|
-
const Segmenter =
|
|
39
|
+
const Segmenter = globalThis.Intl?.Segmenter;
|
|
41
40
|
if (typeof Segmenter !== "function") throw new Error("Intl.Segmenter is unavailable in the current runtime.");
|
|
42
41
|
const segmenter = new Segmenter(locale ?? defaultStringLocale, { granularity: "grapheme" });
|
|
43
42
|
return Array.from(segmenter.segment(value), ({ segment }) => segment);
|
|
@@ -192,26 +191,77 @@ function truncateGraphemes(value, maxLength, suffix = "…", locale) {
|
|
|
192
191
|
return segments.length > maxLength ? segments.slice(0, maxLength).join("") + suffix : value;
|
|
193
192
|
}
|
|
194
193
|
/**
|
|
195
|
-
*
|
|
194
|
+
* 把文本复制到系统剪贴板。
|
|
195
|
+
*
|
|
196
|
+
* @remarks uni-app 使用 `setClipboardData`;浏览器优先使用 Clipboard API,并在该 API 不可用时
|
|
197
|
+
* 回退到 `document.execCommand("copy")`。平台拒绝访问剪贴板时不会静默忽略错误。
|
|
198
|
+
* @param value - 要复制的文本。
|
|
199
|
+
* @returns 复制完成后兑现的 Promise。
|
|
200
|
+
* @throws `Error` 当运行时没有可用的剪贴板能力或复制失败。
|
|
201
|
+
*/
|
|
202
|
+
async function copy(value) {
|
|
203
|
+
const uni = Reflect.get(globalThis, "uni");
|
|
204
|
+
if (uni !== void 0) {
|
|
205
|
+
if (typeof uni !== "object" && typeof uni !== "function" || uni === null) throw new TypeError("The global uni object does not provide setClipboardData.");
|
|
206
|
+
const setClipboardData = Reflect.get(uni, "setClipboardData");
|
|
207
|
+
if (typeof setClipboardData !== "function") throw new TypeError("The global uni object does not provide setClipboardData.");
|
|
208
|
+
await new Promise((resolve, reject) => {
|
|
209
|
+
Reflect.apply(setClipboardData, uni, [{
|
|
210
|
+
data: value,
|
|
211
|
+
fail: (error) => {
|
|
212
|
+
reject(error instanceof Error ? error : new Error("Failed to copy text to the clipboard.", { cause: error }));
|
|
213
|
+
},
|
|
214
|
+
success: resolve
|
|
215
|
+
}]);
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const clipboard = globalThis.navigator?.clipboard;
|
|
220
|
+
if (globalThis.isSecureContext === true && typeof clipboard?.writeText === "function") {
|
|
221
|
+
await clipboard.writeText(value);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const document = globalThis.document;
|
|
225
|
+
if (typeof document?.createElement !== "function" || document.body === null || typeof document.execCommand !== "function") throw new Error("Clipboard access is unavailable in the current runtime.");
|
|
226
|
+
const textarea = document.createElement("textarea");
|
|
227
|
+
textarea.value = value;
|
|
228
|
+
textarea.style.left = "-999999px";
|
|
229
|
+
textarea.style.opacity = "0";
|
|
230
|
+
textarea.style.position = "fixed";
|
|
231
|
+
textarea.style.top = "-999999px";
|
|
232
|
+
document.body.appendChild(textarea);
|
|
233
|
+
let copied = false;
|
|
234
|
+
try {
|
|
235
|
+
textarea.focus();
|
|
236
|
+
textarea.select();
|
|
237
|
+
copied = document.execCommand("copy");
|
|
238
|
+
} finally {
|
|
239
|
+
textarea.remove();
|
|
240
|
+
}
|
|
241
|
+
if (!copied) throw new Error("Failed to copy text to the clipboard.");
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* 生成随机字符串。
|
|
196
245
|
*
|
|
246
|
+
* @remarks 优先使用 Web Crypto;平台缺少安全随机能力时回退到 `Math.random()`。
|
|
197
247
|
* @param length - 字符数量,必须是 0 至 1,000,000 的安全整数。
|
|
198
248
|
* @param alphabet - 不得为空、包含重复字符或超过 2^32 个 Unicode 码点。
|
|
199
249
|
* @returns 由 `alphabet` 中 Unicode 码点组成的随机文本。
|
|
200
|
-
* @throws `RangeError`
|
|
250
|
+
* @throws `RangeError` 当长度或字母表非法。
|
|
201
251
|
*/
|
|
202
|
-
function
|
|
252
|
+
function randomString(length, alphabet = defaultRandomAlphabet) {
|
|
203
253
|
if (!Number.isSafeInteger(length) || length < 0 || length > maximumRandomStringLength) throw new RangeError(`length must be a safe integer from 0 through ${maximumRandomStringLength}.`);
|
|
204
254
|
const characters = Array.from(alphabet);
|
|
205
255
|
if (characters.length === 0) throw new RangeError("alphabet cannot be empty.");
|
|
206
256
|
if (new Set(characters).size !== characters.length) throw new RangeError("alphabet cannot contain duplicate characters.");
|
|
207
257
|
if (characters.length > 4294967296) throw new RangeError("alphabet cannot contain more than 2^32 characters.");
|
|
208
258
|
if (length === 0) return "";
|
|
209
|
-
const
|
|
210
|
-
const acceptanceLimit = Math.floor(4294967296 / characters.length) * characters.length;
|
|
259
|
+
const acceptanceLimit = Math.floor(uint32Range / characters.length) * characters.length;
|
|
211
260
|
const result = [];
|
|
212
261
|
while (result.length < length) {
|
|
213
262
|
const remaining = length - result.length;
|
|
214
|
-
const samples =
|
|
263
|
+
const samples = new Uint32Array(Math.min(remaining, maximumRandomValuesPerBatch));
|
|
264
|
+
fillRandomValues(samples);
|
|
215
265
|
for (const sample of samples) {
|
|
216
266
|
if (sample >= acceptanceLimit) continue;
|
|
217
267
|
const character = characters[sample % characters.length];
|
|
@@ -223,15 +273,18 @@ function secureRandomString(length, alphabet = defaultRandomAlphabet) {
|
|
|
223
273
|
return result.join("");
|
|
224
274
|
}
|
|
225
275
|
/**
|
|
226
|
-
*
|
|
276
|
+
* 生成 RFC 4122 version 4 UUID。
|
|
227
277
|
*
|
|
278
|
+
* @remarks 优先使用 Web Crypto;平台缺少安全随机能力时回退到 `Math.random()`。
|
|
279
|
+
* 该 UUID 适合普通唯一标识,不应作为安全令牌或秘密。
|
|
228
280
|
* @returns 小写、带连字符的 UUID v4。
|
|
229
|
-
* @throws 缺少 Web Crypto 时抛出 `Error`。
|
|
230
281
|
*/
|
|
231
282
|
function generateUuidV4() {
|
|
232
|
-
const crypto =
|
|
233
|
-
if (typeof crypto
|
|
234
|
-
|
|
283
|
+
const crypto = globalThis.crypto;
|
|
284
|
+
if (typeof crypto?.randomUUID === "function") return crypto.randomUUID();
|
|
285
|
+
const bytes = /* @__PURE__ */ new Uint8Array(16);
|
|
286
|
+
fillRandomValues(bytes);
|
|
287
|
+
return createUuidV4FromBytes(bytes);
|
|
235
288
|
}
|
|
236
289
|
/**
|
|
237
290
|
* 判断字符串是否为 RFC 4122 version 4 UUID。
|
|
@@ -270,6 +323,6 @@ function normalizeWhitespace(value) {
|
|
|
270
323
|
return value.trim().replace(/\s+/gu, " ");
|
|
271
324
|
}
|
|
272
325
|
//#endregion
|
|
273
|
-
export { camelCase, decodeURIComponentRepeatedly, escapeHtml, generateUuidV4, isUuidV4, isValidJson, kebabCase, lowerFirst, normalizeWhitespace, parseQueryString, pascalCase,
|
|
326
|
+
export { camelCase, copy, decodeURIComponentRepeatedly, escapeHtml, generateUuidV4, isUuidV4, isValidJson, kebabCase, lowerFirst, normalizeWhitespace, parseQueryString, pascalCase, randomString, splitWords, truncateGraphemes, upperFirst };
|
|
274
327
|
|
|
275
328
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/string/index.ts"],"sourcesContent":["const defaultRandomAlphabet = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\nconst defaultStringLocale = \"en-US\";\nconst maximumRandomStringLength = 1_000_000;\nconst maximumRandomValuesPerBatch = 16_384;\nconst uuidV4Pattern = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;\n\n/** 字符串随机 API 需要的 Web Crypto 最小能力。 */\ntype RuntimeStringCrypto = Partial<Pick<Crypto, \"getRandomValues\" | \"randomUUID\">>;\n\n/** 字素分割需要的可选 Intl 能力。 */\ninterface RuntimeStringIntl {\n\t/** 可选 Segmenter 构造器;缺失时字素 API 使用内部兼容路径。 */\n\tSegmenter?: typeof Intl.Segmenter;\n}\n\n/** 字符串工具延迟访问的平台全局对象最小视图。 */\ninterface RuntimeStringGlobals {\n\t/** 安全随机字符串与 UUID 所需的可选 Web Crypto 能力。 */\n\tcrypto?: RuntimeStringCrypto;\n\t/** 字素分割所需的可选 Intl 能力。 */\n\tIntl?: RuntimeStringIntl;\n}\n\nconst runtimeGlobals = globalThis as unknown as RuntimeStringGlobals;\n\n/** 查询字符串解析结果;重复键保留为数组,不存在的键读取为 `undefined`。 */\nexport type ParsedQueryParameters = Record<string, string | string[] | undefined>;\n\n/** 大小写与字素分割可接受的显式语言;省略时固定使用 `en-US` 以保持输出稳定。 */\nexport type StringLocale = string | readonly string[] | undefined;\n\n/**\n * 获取字符串随机 API 所需的 Web Crypto 能力。\n *\n * @returns 具有 `getRandomValues` 的当前 Crypto 对象。\n * @throws `Error` 当平台没有安全随机能力;绝不回退到 `Math.random()`。\n */\nconst requireWebCrypto = (): Crypto => {\n\tconst crypto = runtimeGlobals.crypto;\n\tif (typeof crypto?.getRandomValues !== \"function\") {\n\t\tthrow new Error(\"Web Crypto random generation is unavailable in the current runtime.\");\n\t}\n\treturn crypto as Crypto;\n};\n\n/**\n * 从随机字节创建 UUID v4。\n *\n * @param bytes - 长度至少为 16 的随机字节;Version 与 Variant 位会被原地修改。\n * @returns 小写、带连字符的 RFC 4122 UUID v4。\n */\nconst createUuidV4FromBytes = (bytes: Uint8Array): string => {\n\tbytes[6] = ((bytes[6] ?? 0) & 15) | 64;\n\tbytes[8] = ((bytes[8] ?? 0) & 63) | 128;\n\tconst hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n\treturn `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n};\n\n/**\n * 按用户可见字素切分文本。\n *\n * @param value - 待切分字符串。\n * @param locale - Segmenter 使用的显式语言;省略时使用固定默认值。\n * @returns 保留组合 Emoji、变音符号和连接序列的字素数组。\n * @throws `Error` 当平台缺少 `Intl.Segmenter`。\n */\nconst splitGraphemes = (value: string, locale: StringLocale): string[] => {\n\tconst Segmenter = runtimeGlobals.Intl?.Segmenter;\n\tif (typeof Segmenter !== \"function\") {\n\t\tthrow new Error(\"Intl.Segmenter is unavailable in the current runtime.\");\n\t}\n\tconst segmenter = new Segmenter(locale ?? defaultStringLocale, { granularity: \"grapheme\" });\n\treturn Array.from(segmenter.segment(value), ({ segment }) => segment);\n};\n\n/**\n * 重复执行 URI 组件解码,直到值稳定或达到深度上限。\n *\n * @param value - 不包含 URI 路径语义的编码组件。\n * @param maxDepth - 最大解码次数,默认 `10`。\n * @returns 解码稳定或达到上限后的组件文本。\n * @throws `URIError` 当任一层包含非法百分号序列;深度非法时抛出 `RangeError`。\n */\nexport function decodeURIComponentRepeatedly(value: string, maxDepth = 10): string {\n\tif (!Number.isSafeInteger(maxDepth) || maxDepth < 0) throw new RangeError(\"maxDepth must be a non-negative safe integer.\");\n\tlet decoded = value;\n\tfor (let index = 0; index < maxDepth; index += 1) {\n\t\tconst next = decodeURIComponent(decoded);\n\t\tif (next === decoded) break;\n\t\tdecoded = next;\n\t}\n\treturn decoded;\n}\n\n/**\n * 解析带 `://` 的绝对 URL、`?query` 或纯查询字符串。\n *\n * @remarks 纯查询字符串值中的未编码 `?` 会作为值内容保留;片段标识及其后内容被忽略。\n * @param input - 完整 URL、带前导问号或不带前导问号的查询文本。\n * @returns 重复键对应字符串数组,空值保留为空字符串。\n */\nexport function parseQueryString(input: string): ParsedQueryParameters {\n\tconst fragmentStart = input.indexOf(\"#\");\n\tconst withoutFragment = fragmentStart < 0 ? input : input.slice(0, fragmentStart);\n\tconst isAbsoluteUrl = /^[a-z][a-z\\d+.-]*:\\/\\//iu.test(withoutFragment);\n\tconst queryStart = withoutFragment.indexOf(\"?\");\n\tif (isAbsoluteUrl && queryStart < 0) return {};\n\tconst query = isAbsoluteUrl ? withoutFragment.slice(queryStart + 1) : withoutFragment.replace(/^\\?/u, \"\");\n\tconst result: ParsedQueryParameters = {};\n\tfor (const [key, value] of new URLSearchParams(query)) {\n\t\tconst existing = Object.hasOwn(result, key) ? result[key] : undefined;\n\t\tif (existing === undefined) {\n\t\t\t// defineProperty 让 `__proto__` 成为普通自有键,不触发 Object.prototype Setter。\n\t\t\tObject.defineProperty(result, key, { configurable: true, enumerable: true, value, writable: true });\n\t\t} else if (Array.isArray(existing)) existing.push(value);\n\t\telse Object.defineProperty(result, key, { configurable: true, enumerable: true, value: [existing, value], writable: true });\n\t}\n\treturn result;\n}\n\n/**\n * 判断文本是否为任意合法 JSON 值,包括标量与 `null`。\n *\n * @param value - 待解析文本;纯空白不视为 JSON。\n * @returns `JSON.parse` 能完整解析时返回 `true`。\n */\nexport function isValidJson(value: string): boolean {\n\tif (value.trim().length === 0) return false;\n\ttry {\n\t\tJSON.parse(value);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * 按大小写边界、连字符、下划线与空白切分单词。\n *\n * @example `XMLHttp_request` 返回 `[\"XML\", \"Http\", \"request\"]`。\n * @param value - 待拆分文本。\n * @returns 删除空项、保持输入顺序的单词数组。\n */\nexport function splitWords(value: string): string[] {\n\treturn value\n\t\t.trim()\n\t\t.replace(/(\\p{Ll}|\\p{N})(\\p{Lu})/gu, \"$1 $2\")\n\t\t.replace(/(\\p{Lu})(\\p{Lu}\\p{Ll})/gu, \"$1 $2\")\n\t\t.split(/[\\s_-]+/u)\n\t\t.filter((part) => part.length > 0);\n}\n\n/**\n * 将首个 Unicode 码点转为大写。\n *\n * @param value - 输入文本;空字符串保持为空。\n * @param locale - 显式语言,默认固定为 `en-US`。\n * @returns 首个 Unicode 码点转换后的文本。\n */\nexport function upperFirst(value: string, locale?: StringLocale): string {\n\tconst characters = Array.from(value);\n\tconst first = characters.shift();\n\treturn first === undefined ? \"\" : first.toLocaleUpperCase(locale ?? defaultStringLocale) + characters.join(\"\");\n}\n\n/**\n * 将首个 Unicode 码点转为小写。\n *\n * @param value - 输入文本;空字符串保持为空。\n * @param locale - 显式语言,默认固定为 `en-US`。\n * @returns 首个 Unicode 码点转换后的文本。\n */\nexport function lowerFirst(value: string, locale?: StringLocale): string {\n\tconst characters = Array.from(value);\n\tconst first = characters.shift();\n\treturn first === undefined ? \"\" : first.toLocaleLowerCase(locale ?? defaultStringLocale) + characters.join(\"\");\n}\n\n/**\n * 将文本转换为 camelCase。\n *\n * @param value - 由大小写、连字符、下划线或空白分隔的文本。\n * @param locale - 大小写转换使用的语言,默认固定为 `en-US`。\n * @returns camelCase 文本。\n */\nexport function camelCase(value: string, locale?: StringLocale): string {\n\treturn splitWords(value)\n\t\t.map((part, index) => {\n\t\t\tconst normalized = part.toLocaleLowerCase(locale ?? defaultStringLocale);\n\t\t\treturn index === 0 ? normalized : upperFirst(normalized, locale);\n\t\t})\n\t\t.join(\"\");\n}\n\n/**\n * 将文本转换为 PascalCase。\n *\n * @param value - 参数语义与 {@link camelCase} 一致。\n * @param locale - 大小写转换使用的显式语言。\n * @returns PascalCase 文本。\n */\nexport function pascalCase(value: string, locale?: StringLocale): string {\n\treturn upperFirst(camelCase(value, locale), locale);\n}\n\n/**\n * 将文本转换为 kebab-case。\n *\n * @param value - 参数语义与 {@link camelCase} 一致。\n * @param locale - 大小写转换使用的显式语言。\n * @returns kebab-case 文本。\n */\nexport function kebabCase(value: string, locale?: StringLocale): string {\n\treturn splitWords(value)\n\t\t.map((part) => part.toLocaleLowerCase(locale ?? defaultStringLocale))\n\t\t.join(\"-\");\n}\n\n/**\n * 按 Unicode 字素簇截断文本,避免拆开 emoji、组合音标或代理对。\n *\n * @param value - 输入文本。\n * @param maxLength - 保留的最大字素簇数量。\n * @param suffix - 被截断时追加的文本,默认单字符省略号 `…`;不计入上限。\n * @param locale - 字素分割语言,默认固定为 `en-US`。\n * @returns 未超限时返回原字符串,否则返回截断内容与后缀。\n * @throws `RangeError` 当 `maxLength` 不是非负安全整数或 Locale 无效;缺少\n * `Intl.Segmenter` 时抛出 `Error`。\n */\nexport function truncateGraphemes(value: string, maxLength: number, suffix = \"…\", locale?: StringLocale): string {\n\tif (!Number.isSafeInteger(maxLength) || maxLength < 0) throw new RangeError(\"maxLength must be a non-negative safe integer.\");\n\tconst segments = splitGraphemes(value, locale);\n\treturn segments.length > maxLength ? segments.slice(0, maxLength).join(\"\") + suffix : value;\n}\n\n/**\n * 使用无偏 Web Crypto 随机数生成字符串。\n *\n * @param length - 字符数量,必须是 0 至 1,000,000 的安全整数。\n * @param alphabet - 不得为空、包含重复字符或超过 2^32 个 Unicode 码点。\n * @returns 由 `alphabet` 中 Unicode 码点组成的随机文本。\n * @throws `RangeError` 当长度或字母表非法;缺少 Web Crypto 时抛出 `Error`。\n */\nexport function secureRandomString(length: number, alphabet: string = defaultRandomAlphabet): string {\n\tif (!Number.isSafeInteger(length) || length < 0 || length > maximumRandomStringLength) {\n\t\tthrow new RangeError(`length must be a safe integer from 0 through ${maximumRandomStringLength}.`);\n\t}\n\tconst characters = Array.from(alphabet);\n\tif (characters.length === 0) throw new RangeError(\"alphabet cannot be empty.\");\n\tif (new Set(characters).size !== characters.length) throw new RangeError(\"alphabet cannot contain duplicate characters.\");\n\tif (characters.length > 0x1_0000_0000) throw new RangeError(\"alphabet cannot contain more than 2^32 characters.\");\n\tif (length === 0) return \"\";\n\n\tconst crypto = requireWebCrypto();\n\tconst uint32Range = 0x1_0000_0000;\n\t// 丢弃不能平均映射到字母表的尾部区间,避免 `%` 造成前部字符概率偏高。\n\tconst acceptanceLimit = Math.floor(uint32Range / characters.length) * characters.length;\n\tconst result: string[] = [];\n\twhile (result.length < length) {\n\t\tconst remaining = length - result.length;\n\t\t// 分批请求可控制临时内存,并避开 Web Crypto 单次随机数组大小限制。\n\t\tconst samples = crypto.getRandomValues(new Uint32Array(Math.min(remaining, maximumRandomValuesPerBatch)));\n\t\tfor (const sample of samples) {\n\t\t\tif (sample >= acceptanceLimit) continue;\n\t\t\tconst character = characters[sample % characters.length];\n\t\t\tif (character === undefined) continue;\n\t\t\tresult.push(character);\n\t\t\tif (result.length === length) break;\n\t\t}\n\t}\n\treturn result.join(\"\");\n}\n\n/**\n * 使用 Web Crypto 生成 RFC 4122 version 4 UUID。\n *\n * @returns 小写、带连字符的 UUID v4。\n * @throws 缺少 Web Crypto 时抛出 `Error`。\n */\nexport function generateUuidV4(): string {\n\tconst crypto = requireWebCrypto();\n\tif (typeof crypto.randomUUID === \"function\") return crypto.randomUUID();\n\treturn createUuidV4FromBytes(crypto.getRandomValues(new Uint8Array(16)));\n}\n\n/**\n * 判断字符串是否为 RFC 4122 version 4 UUID。\n *\n * @param value - 待验证文本;十六进制字母大小写均可。\n * @returns 版本位与 Variant 位均正确时返回 `true`。\n */\nexport function isUuidV4(value: string): boolean {\n\treturn uuidV4Pattern.test(value);\n}\n\n/**\n * 转义 HTML 文本上下文中的五个特殊字符。\n *\n * @remarks 这不是 HTML 清洗器,不能让不可信文本安全进入 URL、CSS、脚本或属性名上下文。\n * @param value - 将作为 HTML 文本节点内容的字符串。\n * @returns 转义 `&`、`<`、`>`、双引号与单引号后的文本。\n */\nexport function escapeHtml(value: string): string {\n\treturn value.replace(/[&<>\"']/gu, (character) => {\n\t\tswitch (character) {\n\t\t\tcase \"&\":\n\t\t\t\treturn \"&\";\n\t\t\tcase \"<\":\n\t\t\t\treturn \"<\";\n\t\t\tcase \">\":\n\t\t\t\treturn \">\";\n\t\t\tcase '\"':\n\t\t\t\treturn \""\";\n\t\t\tdefault:\n\t\t\t\treturn \"'\";\n\t\t}\n\t});\n}\n\n/**\n * 把连续 Unicode 空白折叠为单个空格并删除两端空白。\n *\n * @param value - 输入文本。\n * @returns 规范化后的文本;全空白输入返回空字符串。\n */\nexport function normalizeWhitespace(value: string): string {\n\treturn value.trim().replace(/\\s+/gu, \" \");\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/string/index.ts"],"sourcesContent":["const defaultRandomAlphabet = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\nconst defaultStringLocale = \"en-US\";\nconst maximumRandomStringLength = 1_000_000;\nconst maximumRandomValuesPerBatch = 16_384;\nconst uint32Range = 0x1_0000_0000;\nconst uuidV4Pattern = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;\n\n/** 查询字符串解析结果;重复键保留为数组,不存在的键读取为 `undefined`。 */\nexport type ParsedQueryParameters = Record<string, string | string[] | undefined>;\n\n/** 大小写与字素分割可接受的显式语言;省略时固定使用 `en-US` 以保持输出稳定。 */\nexport type StringLocale = string | readonly string[] | undefined;\n\n/** 使用 Web Crypto 填充随机值,能力缺失时回退到 `Math.random()`。 */\nconst fillRandomValues = (values: Uint8Array<ArrayBuffer> | Uint32Array<ArrayBuffer>): void => {\n\tconst crypto = globalThis.crypto;\n\tif (typeof crypto?.getRandomValues === \"function\") {\n\t\tcrypto.getRandomValues(values);\n\t\treturn;\n\t}\n\tconst range = values.BYTES_PER_ELEMENT === Uint8Array.BYTES_PER_ELEMENT ? 0x100 : uint32Range;\n\tfor (let index = 0; index < values.length; index += 1) values[index] = Math.floor(Math.random() * range);\n};\n\n/**\n * 从随机字节创建 UUID v4。\n *\n * @param bytes - 长度至少为 16 的随机字节;Version 与 Variant 位会被原地修改。\n * @returns 小写、带连字符的 RFC 4122 UUID v4。\n */\nconst createUuidV4FromBytes = (bytes: Uint8Array): string => {\n\tbytes[6] = ((bytes[6] ?? 0) & 15) | 64;\n\tbytes[8] = ((bytes[8] ?? 0) & 63) | 128;\n\tconst hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n\treturn `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n};\n\n/**\n * 按用户可见字素切分文本。\n *\n * @param value - 待切分字符串。\n * @param locale - Segmenter 使用的显式语言;省略时使用固定默认值。\n * @returns 保留组合 Emoji、变音符号和连接序列的字素数组。\n * @throws `Error` 当平台缺少 `Intl.Segmenter`。\n */\nconst splitGraphemes = (value: string, locale: StringLocale): string[] => {\n\tconst Segmenter = globalThis.Intl?.Segmenter;\n\tif (typeof Segmenter !== \"function\") {\n\t\tthrow new Error(\"Intl.Segmenter is unavailable in the current runtime.\");\n\t}\n\tconst segmenter = new Segmenter(locale ?? defaultStringLocale, { granularity: \"grapheme\" });\n\treturn Array.from(segmenter.segment(value), ({ segment }) => segment);\n};\n\n/**\n * 重复执行 URI 组件解码,直到值稳定或达到深度上限。\n *\n * @param value - 不包含 URI 路径语义的编码组件。\n * @param maxDepth - 最大解码次数,默认 `10`。\n * @returns 解码稳定或达到上限后的组件文本。\n * @throws `URIError` 当任一层包含非法百分号序列;深度非法时抛出 `RangeError`。\n */\nexport function decodeURIComponentRepeatedly(value: string, maxDepth = 10): string {\n\tif (!Number.isSafeInteger(maxDepth) || maxDepth < 0) throw new RangeError(\"maxDepth must be a non-negative safe integer.\");\n\tlet decoded = value;\n\tfor (let index = 0; index < maxDepth; index += 1) {\n\t\tconst next = decodeURIComponent(decoded);\n\t\tif (next === decoded) break;\n\t\tdecoded = next;\n\t}\n\treturn decoded;\n}\n\n/**\n * 解析带 `://` 的绝对 URL、`?query` 或纯查询字符串。\n *\n * @remarks 纯查询字符串值中的未编码 `?` 会作为值内容保留;片段标识及其后内容被忽略。\n * @param input - 完整 URL、带前导问号或不带前导问号的查询文本。\n * @returns 重复键对应字符串数组,空值保留为空字符串。\n */\nexport function parseQueryString(input: string): ParsedQueryParameters {\n\tconst fragmentStart = input.indexOf(\"#\");\n\tconst withoutFragment = fragmentStart < 0 ? input : input.slice(0, fragmentStart);\n\tconst isAbsoluteUrl = /^[a-z][a-z\\d+.-]*:\\/\\//iu.test(withoutFragment);\n\tconst queryStart = withoutFragment.indexOf(\"?\");\n\tif (isAbsoluteUrl && queryStart < 0) return {};\n\tconst query = isAbsoluteUrl ? withoutFragment.slice(queryStart + 1) : withoutFragment.replace(/^\\?/u, \"\");\n\tconst result: ParsedQueryParameters = {};\n\tfor (const [key, value] of new URLSearchParams(query)) {\n\t\tconst existing = Object.hasOwn(result, key) ? result[key] : undefined;\n\t\tif (existing === undefined) {\n\t\t\t// defineProperty 让 `__proto__` 成为普通自有键,不触发 Object.prototype Setter。\n\t\t\tObject.defineProperty(result, key, { configurable: true, enumerable: true, value, writable: true });\n\t\t} else if (Array.isArray(existing)) existing.push(value);\n\t\telse Object.defineProperty(result, key, { configurable: true, enumerable: true, value: [existing, value], writable: true });\n\t}\n\treturn result;\n}\n\n/**\n * 判断文本是否为任意合法 JSON 值,包括标量与 `null`。\n *\n * @param value - 待解析文本;纯空白不视为 JSON。\n * @returns `JSON.parse` 能完整解析时返回 `true`。\n */\nexport function isValidJson(value: string): boolean {\n\tif (value.trim().length === 0) return false;\n\ttry {\n\t\tJSON.parse(value);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * 按大小写边界、连字符、下划线与空白切分单词。\n *\n * @example `XMLHttp_request` 返回 `[\"XML\", \"Http\", \"request\"]`。\n * @param value - 待拆分文本。\n * @returns 删除空项、保持输入顺序的单词数组。\n */\nexport function splitWords(value: string): string[] {\n\treturn value\n\t\t.trim()\n\t\t.replace(/(\\p{Ll}|\\p{N})(\\p{Lu})/gu, \"$1 $2\")\n\t\t.replace(/(\\p{Lu})(\\p{Lu}\\p{Ll})/gu, \"$1 $2\")\n\t\t.split(/[\\s_-]+/u)\n\t\t.filter((part) => part.length > 0);\n}\n\n/**\n * 将首个 Unicode 码点转为大写。\n *\n * @param value - 输入文本;空字符串保持为空。\n * @param locale - 显式语言,默认固定为 `en-US`。\n * @returns 首个 Unicode 码点转换后的文本。\n */\nexport function upperFirst(value: string, locale?: StringLocale): string {\n\tconst characters = Array.from(value);\n\tconst first = characters.shift();\n\treturn first === undefined ? \"\" : first.toLocaleUpperCase(locale ?? defaultStringLocale) + characters.join(\"\");\n}\n\n/**\n * 将首个 Unicode 码点转为小写。\n *\n * @param value - 输入文本;空字符串保持为空。\n * @param locale - 显式语言,默认固定为 `en-US`。\n * @returns 首个 Unicode 码点转换后的文本。\n */\nexport function lowerFirst(value: string, locale?: StringLocale): string {\n\tconst characters = Array.from(value);\n\tconst first = characters.shift();\n\treturn first === undefined ? \"\" : first.toLocaleLowerCase(locale ?? defaultStringLocale) + characters.join(\"\");\n}\n\n/**\n * 将文本转换为 camelCase。\n *\n * @param value - 由大小写、连字符、下划线或空白分隔的文本。\n * @param locale - 大小写转换使用的语言,默认固定为 `en-US`。\n * @returns camelCase 文本。\n */\nexport function camelCase(value: string, locale?: StringLocale): string {\n\treturn splitWords(value)\n\t\t.map((part, index) => {\n\t\t\tconst normalized = part.toLocaleLowerCase(locale ?? defaultStringLocale);\n\t\t\treturn index === 0 ? normalized : upperFirst(normalized, locale);\n\t\t})\n\t\t.join(\"\");\n}\n\n/**\n * 将文本转换为 PascalCase。\n *\n * @param value - 参数语义与 {@link camelCase} 一致。\n * @param locale - 大小写转换使用的显式语言。\n * @returns PascalCase 文本。\n */\nexport function pascalCase(value: string, locale?: StringLocale): string {\n\treturn upperFirst(camelCase(value, locale), locale);\n}\n\n/**\n * 将文本转换为 kebab-case。\n *\n * @param value - 参数语义与 {@link camelCase} 一致。\n * @param locale - 大小写转换使用的显式语言。\n * @returns kebab-case 文本。\n */\nexport function kebabCase(value: string, locale?: StringLocale): string {\n\treturn splitWords(value)\n\t\t.map((part) => part.toLocaleLowerCase(locale ?? defaultStringLocale))\n\t\t.join(\"-\");\n}\n\n/**\n * 按 Unicode 字素簇截断文本,避免拆开 emoji、组合音标或代理对。\n *\n * @param value - 输入文本。\n * @param maxLength - 保留的最大字素簇数量。\n * @param suffix - 被截断时追加的文本,默认单字符省略号 `…`;不计入上限。\n * @param locale - 字素分割语言,默认固定为 `en-US`。\n * @returns 未超限时返回原字符串,否则返回截断内容与后缀。\n * @throws `RangeError` 当 `maxLength` 不是非负安全整数或 Locale 无效;缺少\n * `Intl.Segmenter` 时抛出 `Error`。\n */\nexport function truncateGraphemes(value: string, maxLength: number, suffix = \"…\", locale?: StringLocale): string {\n\tif (!Number.isSafeInteger(maxLength) || maxLength < 0) throw new RangeError(\"maxLength must be a non-negative safe integer.\");\n\tconst segments = splitGraphemes(value, locale);\n\treturn segments.length > maxLength ? segments.slice(0, maxLength).join(\"\") + suffix : value;\n}\n\n/**\n * 把文本复制到系统剪贴板。\n *\n * @remarks uni-app 使用 `setClipboardData`;浏览器优先使用 Clipboard API,并在该 API 不可用时\n * 回退到 `document.execCommand(\"copy\")`。平台拒绝访问剪贴板时不会静默忽略错误。\n * @param value - 要复制的文本。\n * @returns 复制完成后兑现的 Promise。\n * @throws `Error` 当运行时没有可用的剪贴板能力或复制失败。\n */\nexport async function copy(value: string): Promise<void> {\n\tconst uni: unknown = Reflect.get(globalThis, \"uni\");\n\tif (uni !== undefined) {\n\t\tif ((typeof uni !== \"object\" && typeof uni !== \"function\") || uni === null) {\n\t\t\tthrow new TypeError(\"The global uni object does not provide setClipboardData.\");\n\t\t}\n\t\tconst setClipboardData: unknown = Reflect.get(uni, \"setClipboardData\");\n\t\tif (typeof setClipboardData !== \"function\") throw new TypeError(\"The global uni object does not provide setClipboardData.\");\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tReflect.apply(setClipboardData, uni, [\n\t\t\t\t{\n\t\t\t\t\tdata: value,\n\t\t\t\t\tfail: (error: unknown): void => {\n\t\t\t\t\t\treject(error instanceof Error ? error : new Error(\"Failed to copy text to the clipboard.\", { cause: error }));\n\t\t\t\t\t},\n\t\t\t\t\tsuccess: resolve,\n\t\t\t\t},\n\t\t\t]);\n\t\t});\n\t\treturn;\n\t}\n\n\tconst clipboard = globalThis.navigator?.clipboard;\n\tif (globalThis.isSecureContext === true && typeof clipboard?.writeText === \"function\") {\n\t\tawait clipboard.writeText(value);\n\t\treturn;\n\t}\n\n\tconst document = globalThis.document;\n\tif (typeof document?.createElement !== \"function\" || document.body === null || typeof document.execCommand !== \"function\") {\n\t\tthrow new Error(\"Clipboard access is unavailable in the current runtime.\");\n\t}\n\tconst textarea = document.createElement(\"textarea\");\n\ttextarea.value = value;\n\ttextarea.style.left = \"-999999px\";\n\ttextarea.style.opacity = \"0\";\n\ttextarea.style.position = \"fixed\";\n\ttextarea.style.top = \"-999999px\";\n\tdocument.body.appendChild(textarea);\n\tlet copied = false;\n\ttry {\n\t\ttextarea.focus();\n\t\ttextarea.select();\n\t\tcopied = document.execCommand(\"copy\");\n\t} finally {\n\t\ttextarea.remove();\n\t}\n\tif (!copied) throw new Error(\"Failed to copy text to the clipboard.\");\n}\n\n/**\n * 生成随机字符串。\n *\n * @remarks 优先使用 Web Crypto;平台缺少安全随机能力时回退到 `Math.random()`。\n * @param length - 字符数量,必须是 0 至 1,000,000 的安全整数。\n * @param alphabet - 不得为空、包含重复字符或超过 2^32 个 Unicode 码点。\n * @returns 由 `alphabet` 中 Unicode 码点组成的随机文本。\n * @throws `RangeError` 当长度或字母表非法。\n */\nexport function randomString(length: number, alphabet: string = defaultRandomAlphabet): string {\n\tif (!Number.isSafeInteger(length) || length < 0 || length > maximumRandomStringLength) {\n\t\tthrow new RangeError(`length must be a safe integer from 0 through ${maximumRandomStringLength}.`);\n\t}\n\tconst characters = Array.from(alphabet);\n\tif (characters.length === 0) throw new RangeError(\"alphabet cannot be empty.\");\n\tif (new Set(characters).size !== characters.length) throw new RangeError(\"alphabet cannot contain duplicate characters.\");\n\tif (characters.length > 0x1_0000_0000) throw new RangeError(\"alphabet cannot contain more than 2^32 characters.\");\n\tif (length === 0) return \"\";\n\n\t// 丢弃不能平均映射到字母表的尾部区间,避免 `%` 造成前部字符概率偏高。\n\tconst acceptanceLimit = Math.floor(uint32Range / characters.length) * characters.length;\n\tconst result: string[] = [];\n\twhile (result.length < length) {\n\t\tconst remaining = length - result.length;\n\t\t// 分批填充可控制临时内存,并避开 Web Crypto 单次随机数组大小限制。\n\t\tconst samples = new Uint32Array(Math.min(remaining, maximumRandomValuesPerBatch));\n\t\tfillRandomValues(samples);\n\t\tfor (const sample of samples) {\n\t\t\tif (sample >= acceptanceLimit) continue;\n\t\t\tconst character = characters[sample % characters.length];\n\t\t\tif (character === undefined) continue;\n\t\t\tresult.push(character);\n\t\t\tif (result.length === length) break;\n\t\t}\n\t}\n\treturn result.join(\"\");\n}\n\n/**\n * 生成 RFC 4122 version 4 UUID。\n *\n * @remarks 优先使用 Web Crypto;平台缺少安全随机能力时回退到 `Math.random()`。\n * 该 UUID 适合普通唯一标识,不应作为安全令牌或秘密。\n * @returns 小写、带连字符的 UUID v4。\n */\nexport function generateUuidV4(): string {\n\tconst crypto = globalThis.crypto;\n\tif (typeof crypto?.randomUUID === \"function\") return crypto.randomUUID();\n\tconst bytes = new Uint8Array(16);\n\tfillRandomValues(bytes);\n\treturn createUuidV4FromBytes(bytes);\n}\n\n/**\n * 判断字符串是否为 RFC 4122 version 4 UUID。\n *\n * @param value - 待验证文本;十六进制字母大小写均可。\n * @returns 版本位与 Variant 位均正确时返回 `true`。\n */\nexport function isUuidV4(value: string): boolean {\n\treturn uuidV4Pattern.test(value);\n}\n\n/**\n * 转义 HTML 文本上下文中的五个特殊字符。\n *\n * @remarks 这不是 HTML 清洗器,不能让不可信文本安全进入 URL、CSS、脚本或属性名上下文。\n * @param value - 将作为 HTML 文本节点内容的字符串。\n * @returns 转义 `&`、`<`、`>`、双引号与单引号后的文本。\n */\nexport function escapeHtml(value: string): string {\n\treturn value.replace(/[&<>\"']/gu, (character) => {\n\t\tswitch (character) {\n\t\t\tcase \"&\":\n\t\t\t\treturn \"&\";\n\t\t\tcase \"<\":\n\t\t\t\treturn \"<\";\n\t\t\tcase \">\":\n\t\t\t\treturn \">\";\n\t\t\tcase '\"':\n\t\t\t\treturn \""\";\n\t\t\tdefault:\n\t\t\t\treturn \"'\";\n\t\t}\n\t});\n}\n\n/**\n * 把连续 Unicode 空白折叠为单个空格并删除两端空白。\n *\n * @param value - 输入文本。\n * @returns 规范化后的文本;全空白输入返回空字符串。\n */\nexport function normalizeWhitespace(value: string): string {\n\treturn value.trim().replace(/\\s+/gu, \" \");\n}\n"],"mappings":"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"}
|
package/docs/API.md
CHANGED
|
@@ -35,11 +35,11 @@ Local.set("token", "value");
|
|
|
35
35
|
|
|
36
36
|
`configureStorage({ prefix: "admin:", crypto: true })` restores the old global prefix and Base64-obfuscation options. `crypto: true` and `base64StorageCodec` are reversible encoding rather than encryption and must not protect secrets. A custom `codec` may be supplied instead of `crypto`.
|
|
37
37
|
|
|
38
|
-
`encodeSecureBase64` and `decodeSecureBase64` preserve the legacy dictionary payload
|
|
38
|
+
`encodeSecureBase64` and `decodeSecureBase64` preserve the legacy dictionary payload. The random prefix prefers Web Crypto and falls back to `Math.random()` when unavailable; it does not provide a security property. Given the same default six-character prefix, valid legacy payloads remain byte-for-byte compatible. The old dictionary references an unavailable character for Base64 lengths 101–124, so the current implementation inserts a one-character fallback that the legacy removal flow can decode. The old custom-length argument always generated six random characters; the current API correctly generates `prefixLength` characters. Custom lengths must match during encoding and decoding; `0` disables both the prefix and dictionary insertion. The format remains reversible encoding rather than encryption.
|
|
39
39
|
|
|
40
40
|
## Identity
|
|
41
41
|
|
|
42
|
-
`installationIdentity` is the global installation identifier facade. Call `configureInstallationIdentity` in the application entry before first use to override its `identity:installation-id` cache key. `getOrCreateInstallationId(installationId?)` loads, creates, or replaces its UUID v4 value in `Local` storage. Storage uses its defaults when no explicit configuration was supplied. UUID generation
|
|
42
|
+
`installationIdentity` is the global installation identifier facade. Call `configureInstallationIdentity` in the application entry before first use to override its `identity:installation-id` cache key. `getOrCreateInstallationId(installationId?)` loads, creates, or replaces its UUID v4 value in `Local` storage. Storage uses its defaults when no explicit configuration was supplied. UUID generation prefers Web Crypto and falls back to `Math.random()` when unavailable.
|
|
43
43
|
|
|
44
44
|
```ts
|
|
45
45
|
import { configureInstallationIdentity, configureStorage, getOrCreateInstallationId, installationIdentity } from "@fast-china/utils";
|
|
@@ -66,22 +66,32 @@ logger.error("network", "request failed", error);
|
|
|
66
66
|
|
|
67
67
|
`createLogger` configures the minimum level, brand prefix, sink, and optional uni-app App-Plus split output. Scope must be a non-empty string without surrounding whitespace.
|
|
68
68
|
|
|
69
|
+
## Clipboard
|
|
70
|
+
|
|
71
|
+
`copy(value)` restores the V1 text-copy capability and returns `Promise<void>`. uni-app uses `setClipboardData`; browsers prefer the Clipboard API and fall back to `document.execCommand("copy")` when it is unavailable. Missing capabilities, denied permission, and copy failures throw errors.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { copy } from "@fast-china/utils";
|
|
75
|
+
|
|
76
|
+
await copy("Fast utilities");
|
|
77
|
+
```
|
|
78
|
+
|
|
69
79
|
## Crypto
|
|
70
80
|
|
|
71
81
|
The TypeScript Crypto public API mirrors the public methods and algorithm casing of .NET `CryptoUtil`:
|
|
72
82
|
|
|
73
|
-
| Capability
|
|
74
|
-
|
|
|
75
|
-
|
|
|
76
|
-
| MD5, SHA-1, and SHA-2 digests
|
|
77
|
-
| HMAC
|
|
78
|
-
| Password derivation and hashing
|
|
79
|
-
| HKDF
|
|
80
|
-
| AES
|
|
81
|
-
| RSA
|
|
82
|
-
| Elliptic curves
|
|
83
|
+
| Capability | Shared method names |
|
|
84
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
85
|
+
| Random bytes and byte comparison | `GenerateRandomBytes`, `FixedTimeEquals` |
|
|
86
|
+
| MD5, SHA-1, and SHA-2 digests | `MD5Encrypt`, `SHA1Encrypt`, `SHA256Encrypt`, `SHA256Bytes`, `SHA384Encrypt`, `SHA384Bytes`, `SHA512Encrypt`, `SHA512Bytes` |
|
|
87
|
+
| HMAC | `HMACSHA256Encrypt`, `HMACSHA384Encrypt`, `HMACSHA512Encrypt` |
|
|
88
|
+
| Password derivation and hashing | `PBKDF2SHA256`, `HashPasswordPBKDF2SHA256`, `VerifyPasswordPBKDF2SHA256` |
|
|
89
|
+
| HKDF | `HKDFSHA256` |
|
|
90
|
+
| AES | `AESEncrypt`, `AESDecrypt`, `AESEncryptAuthenticated`, `AESDecryptAuthenticated`, `AESEncryptWithPassword`, `AESDecryptWithPassword` |
|
|
91
|
+
| RSA | `GenerateRSAKeyPair`, `RSAEncryptOAEP`, `RSADecryptOAEP`, `RSASignPSS`, `RSAVerifyPSS` |
|
|
92
|
+
| Elliptic curves | `GenerateECDSAKeyPair`, `ECDSASign`, `ECDSAVerify`, `GenerateECDHKeyPair`, `DeriveECDHSecret`, `DeriveECDHKeySHA256` |
|
|
83
93
|
|
|
84
|
-
The Base64 v1 payload produced by `AESEncryptAuthenticated`, the `FAST-AES-256-GCM-
|
|
94
|
+
The Base64 v1 payload produced by `AESEncryptAuthenticated`, the `FAST-AES-256-GCM-V1` password payload, PBKDF2 password hashes, and PKCS#8/SPKI PEM keys interoperate with .NET in both directions. MD5 and HMAC output lowercase hexadecimal; SHA-1/256/384/512 output uppercase hexadecimal, matching .NET.
|
|
85
95
|
|
|
86
96
|
Store passwords with `HashPasswordPBKDF2SHA256` and `VerifyPasswordPBKDF2SHA256`; the result is not decryptable. AES-GCM provides confidentiality and integrity, HMAC authenticates with a shared key, SHA-2 computes digests, and HKDF/PBKDF2 derive keys. MD5, SHA-1, AES-CBC, and AES-ECB do not provide modern password-storage or authenticated-encryption guarantees.
|
|
87
97
|
|
|
@@ -91,14 +101,14 @@ Store passwords with `HashPasswordPBKDF2SHA256` and `VerifyPasswordPBKDF2SHA256`
|
|
|
91
101
|
- `async`: abort-aware `sleep`, timeout, retry, bounded concurrent mapping, debounce, and throttle primitives.
|
|
92
102
|
- `base64`: strict UTF-8 Base64/Base64URL byte and text functions plus the historical Latin-1 and dictionary-obfuscation functions.
|
|
93
103
|
- `color`: Hex parsing/formatting/mixing, explicit black/white mixing, luminance, and contrast helpers.
|
|
94
|
-
- `crypto`:
|
|
104
|
+
- `crypto`: random bytes, digests, HMAC, PBKDF2, HKDF, AES, RSA-OAEP/PSS, ECDSA, and ECDH.
|
|
95
105
|
- `date`: date validation and arithmetic, day ranges, relative formatting, and the seven historical date helpers as named functions.
|
|
96
106
|
- `dom`: CSS unit and style serialization helpers.
|
|
97
107
|
- `env`: capability and user-agent detection. Detection does not expand the supported runtime contract.
|
|
98
108
|
- `logger`: isolated configurable loggers and the default `logger`.
|
|
99
|
-
- `number`: ranges, rounding, aggregation, interpolation, byte formatting, and
|
|
109
|
+
- `number`: ranges, rounding, aggregation, interpolation, byte formatting, and Web Crypto-preferred `randomInt`.
|
|
100
110
|
- `object`: prototype-safe selection, comparison, mapping, and query serialization. Style serialization is provided by the `dom` module.
|
|
101
|
-
- `string`: query parsing, casing, grapheme-aware truncation, UUID,
|
|
111
|
+
- `string`: query parsing, casing, grapheme-aware truncation, clipboard copying, UUID, Web Crypto-preferred `randomString`, escaping, and whitespace normalization.
|
|
102
112
|
- `vue`: Composition API, type, render, and `app.use()` registration helpers for Vue 3.
|
|
103
113
|
|
|
104
114
|
## Security and limits
|
|
@@ -110,3 +120,7 @@ Query and object helpers reject prototype-polluting keys. URL decoders are bound
|
|
|
110
120
|
## Errors and compatibility
|
|
111
121
|
|
|
112
122
|
Programming errors, invalid inputs, unsupported platform capabilities, and malformed protected data throw native errors unless a function explicitly documents a nullable result.
|
|
123
|
+
|
|
124
|
+
`randomInt`, `randomString`, `generateUuidV4`, and `GenerateRandomBytes` all prefer Web Crypto and fall back to `Math.random()` when unavailable.
|
|
125
|
+
|
|
126
|
+
Fast.Utils 2.1.0 removes `secureRandomInt` and `secureRandomString`. This is a breaking change; consumers must migrate to `randomInt` and `randomString`, respectively.
|
package/docs/API.zh-CN.md
CHANGED
|
@@ -35,11 +35,11 @@ Local.set("token", "value");
|
|
|
35
35
|
|
|
36
36
|
`configureStorage({ prefix: "admin:", crypto: true })` 恢复了旧版全局前缀与 Base64 混淆选项。`crypto: true` 和 `base64StorageCodec` 都只是可逆编码,不是加密,不能保护敏感数据。可以使用自定义 `codec` 替代 `crypto`。
|
|
37
37
|
|
|
38
|
-
`encodeSecureBase64` 与 `decodeSecureBase64`
|
|
38
|
+
`encodeSecureBase64` 与 `decodeSecureBase64` 保留旧字典兼容载荷。随机前缀优先使用 Web Crypto,能力缺失时回退到 `Math.random()`;它不承担安全用途。给定相同的默认 6 字符前缀时,有效旧载荷保持逐字符兼容;旧字典在 Base64 长度 101–124 时会引用越界,当前实现使用单字符回退,旧删除字典流程仍可解码。旧自定义长度参数始终生成 6 个随机字符,当前 API 已按 `prefixLength` 正确生成。自定义 `prefixLength` 必须在编码和解码时保持一致;传入 `0` 会同时关闭随机前缀与字典插入。该格式仍是可逆编码,不等同于加密。
|
|
39
39
|
|
|
40
40
|
## Identity
|
|
41
41
|
|
|
42
|
-
`installationIdentity` 是全局安装标识门面。可在程序入口、首次使用前调用 `configureInstallationIdentity` 覆盖默认缓存键 `identity:installation-id`。`getOrCreateInstallationId(installationId?)` 会通过 `Local` 读取、生成或替换 UUID v4;未显式配置 Storage 时使用其默认值。UUID
|
|
42
|
+
`installationIdentity` 是全局安装标识门面。可在程序入口、首次使用前调用 `configureInstallationIdentity` 覆盖默认缓存键 `identity:installation-id`。`getOrCreateInstallationId(installationId?)` 会通过 `Local` 读取、生成或替换 UUID v4;未显式配置 Storage 时使用其默认值。UUID 优先使用 Web Crypto 生成,能力缺失时回退到 `Math.random()`。
|
|
43
43
|
|
|
44
44
|
```ts
|
|
45
45
|
import { configureInstallationIdentity, configureStorage, getOrCreateInstallationId, installationIdentity } from "@fast-china/utils";
|
|
@@ -66,13 +66,23 @@ logger.error("network", "request failed", error);
|
|
|
66
66
|
|
|
67
67
|
`createLogger` 只配置最低级别、品牌前缀、Sink 和可选的 uni-app App-Plus 拆分输出。作用域必须是无外围空白的非空字符串。
|
|
68
68
|
|
|
69
|
+
## 剪贴板
|
|
70
|
+
|
|
71
|
+
`copy(value)` 恢复 V1 的文本复制能力,并返回 `Promise<void>`。uni-app 使用 `setClipboardData`;浏览器优先使用 Clipboard API,不可用时回退到 `document.execCommand("copy")`。平台能力缺失、权限被拒绝或复制失败时会抛出错误。
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { copy } from "@fast-china/utils";
|
|
75
|
+
|
|
76
|
+
await copy("Fast 工具库");
|
|
77
|
+
```
|
|
78
|
+
|
|
69
79
|
## Crypto
|
|
70
80
|
|
|
71
81
|
TypeScript Crypto 公共 API 与 .NET `CryptoUtil` 的公开方法及算法名称大小写保持一致:
|
|
72
82
|
|
|
73
83
|
| 能力 | 两端统一的方法名 |
|
|
74
84
|
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
75
|
-
|
|
|
85
|
+
| 随机字节与字节比较 | `GenerateRandomBytes`、`FixedTimeEquals` |
|
|
76
86
|
| MD5、SHA-1 与 SHA-2 摘要 | `MD5Encrypt`、`SHA1Encrypt`、`SHA256Encrypt`、`SHA256Bytes`、`SHA384Encrypt`、`SHA384Bytes`、`SHA512Encrypt`、`SHA512Bytes` |
|
|
77
87
|
| HMAC | `HMACSHA256Encrypt`、`HMACSHA384Encrypt`、`HMACSHA512Encrypt` |
|
|
78
88
|
| 密码派生与密码哈希 | `PBKDF2SHA256`、`HashPasswordPBKDF2SHA256`、`VerifyPasswordPBKDF2SHA256` |
|
|
@@ -81,7 +91,7 @@ TypeScript Crypto 公共 API 与 .NET `CryptoUtil` 的公开方法及算法名
|
|
|
81
91
|
| RSA | `GenerateRSAKeyPair`、`RSAEncryptOAEP`、`RSADecryptOAEP`、`RSASignPSS`、`RSAVerifyPSS` |
|
|
82
92
|
| 椭圆曲线 | `GenerateECDSAKeyPair`、`ECDSASign`、`ECDSAVerify`、`GenerateECDHKeyPair`、`DeriveECDHSecret`、`DeriveECDHKeySHA256` |
|
|
83
93
|
|
|
84
|
-
`AESEncryptAuthenticated` 的 Base64 v1 载荷、`AESEncryptWithPassword` 的 `FAST-AES-256-GCM-
|
|
94
|
+
`AESEncryptAuthenticated` 的 Base64 v1 载荷、`AESEncryptWithPassword` 的 `FAST-AES-256-GCM-V1` 载荷、PBKDF2 密码哈希以及 PKCS#8/SPKI PEM 密钥均可与 .NET 双向使用。MD5 与 HMAC 输出小写十六进制;SHA-1/256/384/512 输出大写十六进制,与 .NET 保持一致。
|
|
85
95
|
|
|
86
96
|
密码存储使用 `HashPasswordPBKDF2SHA256` 和 `VerifyPasswordPBKDF2SHA256`;该哈希不可解密。需要同时保证机密性和完整性的文本使用 AES-GCM 入口。HMAC 用于共享密钥认证,SHA-2 用于摘要,HKDF/PBKDF2 用于密钥派生。MD5、SHA-1、AES-CBC 和 AES-ECB 不提供现代密码存储或认证加密保证。
|
|
87
97
|
|
|
@@ -91,14 +101,14 @@ TypeScript Crypto 公共 API 与 .NET `CryptoUtil` 的公开方法及算法名
|
|
|
91
101
|
- `async`:支持取消的 Sleep、超时、重试、受限并发映射、防抖和节流。
|
|
92
102
|
- `base64`:严格 UTF-8 Base64/Base64URL 字节与文本函数,以及 Latin-1 和 SecureBase64 兼容函数。
|
|
93
103
|
- `color`:颜色解析、格式化、混合、明暗、亮度和对比度。
|
|
94
|
-
- `crypto
|
|
104
|
+
- `crypto`:随机字节、摘要、HMAC、PBKDF2、HKDF、AES、RSA-OAEP/PSS、ECDSA 和 ECDH。
|
|
95
105
|
- `date`:日期校验、加减、日范围、相对时间,以及七个历史日期功能的具名函数。
|
|
96
106
|
- `dom`:CSS 单位和 Style 序列化。
|
|
97
107
|
- `env`:能力与 User-Agent 检测;检测函数不扩大运行时支持范围。
|
|
98
108
|
- `logger`:隔离的可配置 Logger 和默认 `logger`。
|
|
99
|
-
- `number
|
|
109
|
+
- `number`:范围、舍入、聚合、插值、字节格式化,以及优先使用 Web Crypto 的 `randomInt`。
|
|
100
110
|
- `object`:防原型污染的选择、比较、映射和 Query 序列化;Style 序列化由 `dom` 模块提供。
|
|
101
|
-
- `string`:Query
|
|
111
|
+
- `string`:Query 解析、大小写、字素截断、剪贴板复制、UUID、优先使用 Web Crypto 的 `randomString`、转义和空白规范化。
|
|
102
112
|
- `vue`:Vue 3 的 Composition API、类型、Render 和 `app.use()` 注册 Helper。
|
|
103
113
|
|
|
104
114
|
## 安全与限制
|
|
@@ -110,3 +120,7 @@ Query 与 Object API 拒绝原型污染键,URL 解码有最大深度,Storage
|
|
|
110
120
|
## 错误与兼容性
|
|
111
121
|
|
|
112
122
|
除明确说明返回空值的函数外,编程错误、非法输入、平台能力缺失和受保护数据损坏均抛出原生错误。
|
|
123
|
+
|
|
124
|
+
`randomInt`、`randomString`、`generateUuidV4` 与 `GenerateRandomBytes` 默认都优先使用 Web Crypto,能力缺失时回退到 `Math.random()`。
|
|
125
|
+
|
|
126
|
+
Fast.Utils 2.1.0 已删除 `secureRandomInt` 与 `secureRandomString`,这是破坏性修改;调用方应分别改用 `randomInt` 与 `randomString`。
|
package/docs/RUNTIME_CONTRACT.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
- uni-app boundary: the first Storage operation, or an earlier `configureStorage({ prefix })` call, detects global `uni` and uses its synchronous Storage API.
|
|
9
9
|
- Browser storage: applications import `Local` and `Session` directly; `configureStorage()` is needed only to override defaults before the first operation.
|
|
10
10
|
- Stateful browser defaults: Storage and Identity configuration are page-global by design. Conflicting reconfiguration throws.
|
|
11
|
-
-
|
|
11
|
+
- Randomness: every random generation entry prefers Web Crypto and falls back to `Math.random()` when unavailable.
|
|
12
12
|
- Publishing: the repository root is the only package, `dist/` is the only build output, and `package.json#exports` is the complete public path whitelist.
|
|
13
13
|
|
|
14
14
|
Importing a module does not itself read `window`, browser Storage, or `uni`, so unsupported platform capabilities fail only when the corresponding API is called.
|
|
@@ -29,7 +29,7 @@ Importing a module does not itself read `window`, browser Storage, or `uni`, so
|
|
|
29
29
|
- uni-app:首次 Storage 操作或更早的 `configureStorage({ prefix })` 调用会检测全局 `uni`,并使用其同步 Storage API。
|
|
30
30
|
- Storage:直接从包导入 `Local` 和 `Session` 即可;只有覆盖默认值时才需在首次操作前调用 `configureStorage()`。
|
|
31
31
|
- 状态:Storage 与 Identity 配置按浏览器页面全局共享;冲突配置明确抛错。
|
|
32
|
-
-
|
|
32
|
+
- 随机数:所有随机生成入口都优先使用 Web Crypto,缺失时回退到 `Math.random()`。
|
|
33
33
|
- 发布:根目录是唯一 npm 包,`dist/` 是唯一构建输出,`exports` 是完整公共路径白名单。
|
|
34
34
|
|
|
35
35
|
模块导入本身不读取 `window`、浏览器 Storage 或 `uni`,不具备对应平台能力时只在调用相关 API 时明确失败。
|