@jayfong/x-utils 1.0.0-202510061015 → 3.4.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/lib/ai.d.mts +103 -0
- package/lib/ai.mjs +75 -0
- package/lib/cache.d.mts +464 -0
- package/lib/cache.mjs +531 -0
- package/lib/captcha.d.mts +259 -0
- package/lib/captcha.mjs +174 -0
- package/lib/case.d.mts +184 -0
- package/lib/case.mjs +120 -0
- package/lib/chinese.d.mts +57 -0
- package/lib/chinese.mjs +79 -0
- package/lib/cli.d.mts +52 -0
- package/lib/cli.mjs +62 -0
- package/lib/clipboard.d.mts +50 -0
- package/lib/clipboard.mjs +122 -0
- package/lib/cmd.d.mts +38 -0
- package/lib/cmd.mjs +52 -0
- package/lib/crawler.d.mts +330 -0
- package/lib/crawler.mjs +313 -0
- package/lib/crypto.d.mts +158 -0
- package/lib/crypto.mjs +213 -0
- package/lib/csv.d.mts +67 -0
- package/lib/csv.mjs +76 -0
- package/lib/data_set.d.mts +76 -0
- package/lib/data_set.mjs +104 -0
- package/lib/db_value.d.mts +213 -0
- package/lib/db_value.mjs +208 -0
- package/lib/dispose.d.mts +98 -0
- package/lib/dispose.mjs +107 -0
- package/lib/file_store.d.mts +167 -0
- package/lib/file_store.mjs +120 -0
- package/lib/fs.d.mts +111 -0
- package/lib/fs.mjs +127 -0
- package/lib/html.d.mts +103 -0
- package/lib/html.mjs +112 -0
- package/lib/json.d.mts +50 -0
- package/lib/json.mjs +74 -0
- package/lib/jwt.d.mts +128 -0
- package/lib/jwt.mjs +104 -0
- package/lib/mail.d.mts +125 -0
- package/lib/mail.mjs +108 -0
- package/lib/pay.d.mts +394 -0
- package/lib/pay.mjs +343 -0
- package/lib/puppeteer.d.mts +88 -0
- package/lib/puppeteer.mjs +219 -0
- package/lib/random.d.mts +71 -0
- package/lib/random.mjs +92 -0
- package/lib/redis.d.mts +32 -0
- package/lib/redis.mjs +24 -0
- package/lib/runner.d.mts +80 -0
- package/lib/runner.mjs +66 -0
- package/lib/sensitive_words.d.mts +82 -0
- package/lib/sensitive_words.mjs +73 -0
- package/lib/sms.d.mts +133 -0
- package/lib/sms.mjs +100 -0
- package/lib/task.d.mts +37 -0
- package/lib/task.mjs +43 -0
- package/lib/text.d.mts +84 -0
- package/lib/text.mjs +141 -0
- package/lib/yaml.d.mts +51 -0
- package/lib/yaml.mjs +64 -0
- package/package.json +82 -164
- package/_cjs/ai.js +0 -80
- package/_cjs/alipan.js +0 -173
- package/_cjs/cli.js +0 -51
- package/_cjs/clipboard.js +0 -65
- package/_cjs/cmd.js +0 -42
- package/_cjs/compressor.js +0 -47
- package/_cjs/crawler.js +0 -341
- package/_cjs/csv.js +0 -97
- package/_cjs/data_set.js +0 -115
- package/_cjs/env.js +0 -82
- package/_cjs/fs.js +0 -86
- package/_cjs/html.js +0 -62
- package/_cjs/http_server.js +0 -79
- package/_cjs/json.js +0 -49
- package/_cjs/ksb.js +0 -218
- package/_cjs/number.js +0 -39
- package/_cjs/ocr.js +0 -74
- package/_cjs/package.json +0 -3
- package/_cjs/path.js +0 -35
- package/_cjs/pinyin.js +0 -37
- package/_cjs/puppeteer.js +0 -271
- package/_cjs/runner.js +0 -88
- package/_cjs/task.js +0 -41
- package/_cjs/text.js +0 -136
- package/_cjs/unicode/lib_unicode.js +0 -58643
- package/_cjs/unicode/unicode.js +0 -441
- package/_cjs/upload.js +0 -101
- package/_cjs/yaml.js +0 -52
- package/ai.d.ts +0 -26
- package/ai.js +0 -60
- package/alipan.d.ts +0 -209
- package/alipan.js +0 -153
- package/cli.d.ts +0 -14
- package/cli.js +0 -21
- package/clipboard.d.ts +0 -6
- package/clipboard.js +0 -35
- package/cmd.d.ts +0 -5
- package/cmd.js +0 -22
- package/compressor.d.ts +0 -5
- package/compressor.js +0 -17
- package/crawler.d.ts +0 -162
- package/crawler.js +0 -332
- package/csv.d.ts +0 -14
- package/csv.js +0 -67
- package/data_set.d.ts +0 -22
- package/data_set.js +0 -85
- package/env.d.ts +0 -6
- package/env.js +0 -52
- package/fs.d.ts +0 -23
- package/fs.js +0 -56
- package/html.d.ts +0 -7
- package/html.js +0 -45
- package/http_server.d.ts +0 -13
- package/http_server.js +0 -49
- package/json.d.ts +0 -6
- package/json.js +0 -19
- package/ksb.d.ts +0 -37
- package/ksb.js +0 -198
- package/number.d.ts +0 -3
- package/number.js +0 -9
- package/ocr.d.ts +0 -17
- package/ocr.js +0 -54
- package/path.d.ts +0 -2
- package/path.js +0 -5
- package/pinyin.d.ts +0 -8
- package/pinyin.js +0 -17
- package/puppeteer.d.ts +0 -38
- package/puppeteer.js +0 -241
- package/runner.d.ts +0 -13
- package/runner.js +0 -58
- package/task.d.ts +0 -6
- package/task.js +0 -21
- package/text.d.ts +0 -63
- package/text.js +0 -119
- package/unicode/lib_unicode.d.ts +0 -4
- package/unicode/lib_unicode.js +0 -58623
- package/unicode/unicode.d.ts +0 -347
- package/unicode/unicode.js +0 -426
- package/upload.d.ts +0 -34
- package/upload.js +0 -71
- package/yaml.d.ts +0 -6
- package/yaml.js +0 -22
package/lib/html.d.mts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { HTMLElement } from "node-html-parser";
|
|
2
|
+
/**
|
|
3
|
+
* HTML 转义与反转义工具。
|
|
4
|
+
*
|
|
5
|
+
* 基于 `fast-escape-html` 实现,用于在把不可信文本嵌入 HTML 前进行转义,
|
|
6
|
+
* 或将已转义的 HTML 实体还原为原始字符,避免标签错乱与 XSS 注入。
|
|
7
|
+
*
|
|
8
|
+
* 转义字符对照:
|
|
9
|
+
* | 原字符 | 转义结果 |
|
|
10
|
+
* | ------ | -------- |
|
|
11
|
+
* | `&` | `&` |
|
|
12
|
+
* | `<` | `<` |
|
|
13
|
+
* | `>` | `>` |
|
|
14
|
+
* | `"` | `"` |
|
|
15
|
+
* | `'` | `'` |
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const html = new XHtml();
|
|
19
|
+
* html.escape('<a href="x">\'</a>') // '<a href="x">'</a>'
|
|
20
|
+
* html.unescape('<a&b>') // '<a&b>'
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* - `escape` 是无状态转义:已含实体的文本会再次被转义(如 `<` →
|
|
24
|
+
* `&lt;`),因此不要对同一段文本重复调用;
|
|
25
|
+
* - `unescape` 只还原本工具转义出的实体(`&`、`<`、`>`、
|
|
26
|
+
* `"`、`'`、`'`),不处理 ` `、`©` 等其它命名
|
|
27
|
+
* 实体,也不处理 `A`、`A` 这类十进制/十六进制数字实体;
|
|
28
|
+
* - 本工具面向 HTML 文本节点/属性值的转义场景,不负责 URL 编码,也不保证
|
|
29
|
+
* 脚本、样式等非 HTML 上下文的安全。
|
|
30
|
+
*/
|
|
31
|
+
export declare class XHtml {
|
|
32
|
+
/**
|
|
33
|
+
* 将字符串转义为可安全嵌入 HTML 的实体文本。
|
|
34
|
+
*
|
|
35
|
+
* 处理规则:
|
|
36
|
+
* - `&`、`<`、`>`、`"`、`'` 会被替换为对应实体;
|
|
37
|
+
* - 其余字符(中文、字母、数字、换行、空白等)原样保留;
|
|
38
|
+
* - 空字符串返回空字符串。
|
|
39
|
+
*
|
|
40
|
+
* 边界与意外场景:
|
|
41
|
+
* - 已转义文本会被二次转义(如 `<` → `&lt;`),调用方需自行保证
|
|
42
|
+
* 同一段文本只转义一次。
|
|
43
|
+
*
|
|
44
|
+
* @param html 待转义的原始字符串。
|
|
45
|
+
* @returns 转义后的字符串。
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* new XHtml().escape('<a href="x">') // '<a href="x">'
|
|
49
|
+
* new XHtml().escape('') // ''
|
|
50
|
+
*/
|
|
51
|
+
static escape(html: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* 将 HTML 实体还原为原始字符。
|
|
54
|
+
*
|
|
55
|
+
* 处理规则:
|
|
56
|
+
* - 还原 `&`、`<`、`>`、`"`、`'`、`'`;
|
|
57
|
+
* - 未匹配到上述实体的文本原样保留;
|
|
58
|
+
* - 空字符串返回空字符串。
|
|
59
|
+
*
|
|
60
|
+
* 边界与意外场景:
|
|
61
|
+
* - ` `、`©` 等其它命名实体不会被还原;
|
|
62
|
+
* - `A`、`A` 等数字实体不会被还原;
|
|
63
|
+
* - 反转义是一次性替换而非循环展开,例如 `&amp;` 还原后得到 `&`
|
|
64
|
+
* 而不会继续还原成 `&`。
|
|
65
|
+
*
|
|
66
|
+
* @param html 含 HTML 实体的字符串。
|
|
67
|
+
* @returns 还原后的字符串。
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* new XHtml().unescape('<a&b>') // '<a&b>'
|
|
71
|
+
* new XHtml().unescape(''ok'') // "'ok'"
|
|
72
|
+
* new XHtml().unescape('') // ''
|
|
73
|
+
*/
|
|
74
|
+
static unescape(html: string): string;
|
|
75
|
+
/**
|
|
76
|
+
* 将 HTML 字符串解析为可查询的节点树。
|
|
77
|
+
*
|
|
78
|
+
* 基于 `node-html-parser` 实现,返回的根节点是一个“文档容器”:
|
|
79
|
+
* - 根节点自身不携带标签名(`tagName` / `rawTagName` 为 `null`),
|
|
80
|
+
* 真正解析出的元素位于 `childNodes` 中;
|
|
81
|
+
* - 同时支持完整文档与 HTML 片段(多个顶层节点);
|
|
82
|
+
* - 元素节点与文本节点会被保留,注释节点默认被丢弃;
|
|
83
|
+
* - 属性可通过 `getAttribute` / `rawAttributes` 读取,元素可通过
|
|
84
|
+
* `querySelector` / `querySelectorAll` / `getElementById` /
|
|
85
|
+
* `getElementsByTagName` 等方法查询。
|
|
86
|
+
*
|
|
87
|
+
* 边界与意外场景:
|
|
88
|
+
* - 空字符串解析为空树(`childNodes` 为空、`textContent` 为空字符串);
|
|
89
|
+
* - 查询不到匹配元素时,选择器方法返回 `null` / 空数组,而不会抛出异常;
|
|
90
|
+
* - 本方法不负责 HTML 清理与消毒,不可信输入需先经过 `escape` 或其它
|
|
91
|
+
* 消毒流程后再使用;
|
|
92
|
+
* - 底层解析失败时会直接抛出异常,本方法不做兜底转换。
|
|
93
|
+
*
|
|
94
|
+
* @param html 待解析的 HTML 字符串。
|
|
95
|
+
* @returns 解析得到的根节点(HTMLElement),其子节点为实际解析出的元素。
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* const root = new XHtml().parse('<div class="box">hi<span>!</span></div>');
|
|
99
|
+
* root.querySelector('.box')?.textContent // 'hi!'
|
|
100
|
+
* root.querySelectorAll('span').length // 1
|
|
101
|
+
*/
|
|
102
|
+
static parse(html: string): HTMLElement;
|
|
103
|
+
}
|
package/lib/html.mjs
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { parse } from "node-html-parser";
|
|
2
|
+
import { escapeHTML, unescapeHTML } from "fast-escape-html";
|
|
3
|
+
// src/html.ts
|
|
4
|
+
/**
|
|
5
|
+
* HTML 转义与反转义工具。
|
|
6
|
+
*
|
|
7
|
+
* 基于 `fast-escape-html` 实现,用于在把不可信文本嵌入 HTML 前进行转义,
|
|
8
|
+
* 或将已转义的 HTML 实体还原为原始字符,避免标签错乱与 XSS 注入。
|
|
9
|
+
*
|
|
10
|
+
* 转义字符对照:
|
|
11
|
+
* | 原字符 | 转义结果 |
|
|
12
|
+
* | ------ | -------- |
|
|
13
|
+
* | `&` | `&` |
|
|
14
|
+
* | `<` | `<` |
|
|
15
|
+
* | `>` | `>` |
|
|
16
|
+
* | `"` | `"` |
|
|
17
|
+
* | `'` | `'` |
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const html = new XHtml();
|
|
21
|
+
* html.escape('<a href="x">\'</a>') // '<a href="x">'</a>'
|
|
22
|
+
* html.unescape('<a&b>') // '<a&b>'
|
|
23
|
+
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* - `escape` 是无状态转义:已含实体的文本会再次被转义(如 `<` →
|
|
26
|
+
* `&lt;`),因此不要对同一段文本重复调用;
|
|
27
|
+
* - `unescape` 只还原本工具转义出的实体(`&`、`<`、`>`、
|
|
28
|
+
* `"`、`'`、`'`),不处理 ` `、`©` 等其它命名
|
|
29
|
+
* 实体,也不处理 `A`、`A` 这类十进制/十六进制数字实体;
|
|
30
|
+
* - 本工具面向 HTML 文本节点/属性值的转义场景,不负责 URL 编码,也不保证
|
|
31
|
+
* 脚本、样式等非 HTML 上下文的安全。
|
|
32
|
+
*/
|
|
33
|
+
var XHtml = class {
|
|
34
|
+
/**
|
|
35
|
+
* 将字符串转义为可安全嵌入 HTML 的实体文本。
|
|
36
|
+
*
|
|
37
|
+
* 处理规则:
|
|
38
|
+
* - `&`、`<`、`>`、`"`、`'` 会被替换为对应实体;
|
|
39
|
+
* - 其余字符(中文、字母、数字、换行、空白等)原样保留;
|
|
40
|
+
* - 空字符串返回空字符串。
|
|
41
|
+
*
|
|
42
|
+
* 边界与意外场景:
|
|
43
|
+
* - 已转义文本会被二次转义(如 `<` → `&lt;`),调用方需自行保证
|
|
44
|
+
* 同一段文本只转义一次。
|
|
45
|
+
*
|
|
46
|
+
* @param html 待转义的原始字符串。
|
|
47
|
+
* @returns 转义后的字符串。
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* new XHtml().escape('<a href="x">') // '<a href="x">'
|
|
51
|
+
* new XHtml().escape('') // ''
|
|
52
|
+
*/
|
|
53
|
+
static escape(html) {
|
|
54
|
+
return escapeHTML(html);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 将 HTML 实体还原为原始字符。
|
|
58
|
+
*
|
|
59
|
+
* 处理规则:
|
|
60
|
+
* - 还原 `&`、`<`、`>`、`"`、`'`、`'`;
|
|
61
|
+
* - 未匹配到上述实体的文本原样保留;
|
|
62
|
+
* - 空字符串返回空字符串。
|
|
63
|
+
*
|
|
64
|
+
* 边界与意外场景:
|
|
65
|
+
* - ` `、`©` 等其它命名实体不会被还原;
|
|
66
|
+
* - `A`、`A` 等数字实体不会被还原;
|
|
67
|
+
* - 反转义是一次性替换而非循环展开,例如 `&amp;` 还原后得到 `&`
|
|
68
|
+
* 而不会继续还原成 `&`。
|
|
69
|
+
*
|
|
70
|
+
* @param html 含 HTML 实体的字符串。
|
|
71
|
+
* @returns 还原后的字符串。
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* new XHtml().unescape('<a&b>') // '<a&b>'
|
|
75
|
+
* new XHtml().unescape(''ok'') // "'ok'"
|
|
76
|
+
* new XHtml().unescape('') // ''
|
|
77
|
+
*/
|
|
78
|
+
static unescape(html) {
|
|
79
|
+
return unescapeHTML(html);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 将 HTML 字符串解析为可查询的节点树。
|
|
83
|
+
*
|
|
84
|
+
* 基于 `node-html-parser` 实现,返回的根节点是一个“文档容器”:
|
|
85
|
+
* - 根节点自身不携带标签名(`tagName` / `rawTagName` 为 `null`),
|
|
86
|
+
* 真正解析出的元素位于 `childNodes` 中;
|
|
87
|
+
* - 同时支持完整文档与 HTML 片段(多个顶层节点);
|
|
88
|
+
* - 元素节点与文本节点会被保留,注释节点默认被丢弃;
|
|
89
|
+
* - 属性可通过 `getAttribute` / `rawAttributes` 读取,元素可通过
|
|
90
|
+
* `querySelector` / `querySelectorAll` / `getElementById` /
|
|
91
|
+
* `getElementsByTagName` 等方法查询。
|
|
92
|
+
*
|
|
93
|
+
* 边界与意外场景:
|
|
94
|
+
* - 空字符串解析为空树(`childNodes` 为空、`textContent` 为空字符串);
|
|
95
|
+
* - 查询不到匹配元素时,选择器方法返回 `null` / 空数组,而不会抛出异常;
|
|
96
|
+
* - 本方法不负责 HTML 清理与消毒,不可信输入需先经过 `escape` 或其它
|
|
97
|
+
* 消毒流程后再使用;
|
|
98
|
+
* - 底层解析失败时会直接抛出异常,本方法不做兜底转换。
|
|
99
|
+
*
|
|
100
|
+
* @param html 待解析的 HTML 字符串。
|
|
101
|
+
* @returns 解析得到的根节点(HTMLElement),其子节点为实际解析出的元素。
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* const root = new XHtml().parse('<div class="box">hi<span>!</span></div>');
|
|
105
|
+
* root.querySelector('.box')?.textContent // 'hi!'
|
|
106
|
+
* root.querySelectorAll('span').length // 1
|
|
107
|
+
*/
|
|
108
|
+
static parse(html) {
|
|
109
|
+
return parse(html);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
export { XHtml };
|
package/lib/json.d.mts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON 文本与文件读写工具。
|
|
3
|
+
*
|
|
4
|
+
* 提供两类能力:
|
|
5
|
+
* - 严格方法:`parse` / `read`,遇到非法 JSON 或文件异常时抛错;
|
|
6
|
+
* - 安全方法:`safeParse` / `safeRead`,遇到异常时返回 `undefined`,适合可选配置读取场景。
|
|
7
|
+
*/
|
|
8
|
+
export declare class XJson {
|
|
9
|
+
/**
|
|
10
|
+
* 严格解析 JSON 文本。
|
|
11
|
+
*
|
|
12
|
+
* - 解析成功:返回目标类型值;
|
|
13
|
+
* - 解析失败:抛出 `SyntaxError`。
|
|
14
|
+
*/
|
|
15
|
+
static parse<T>(value: string): T;
|
|
16
|
+
/**
|
|
17
|
+
* 安全解析 JSON 文本。
|
|
18
|
+
*
|
|
19
|
+
* - 解析成功:返回目标类型值;
|
|
20
|
+
* - 解析失败:返回 `undefined`。
|
|
21
|
+
*
|
|
22
|
+
* 适用于“配置可缺省、失败可兜底”的场景,例如从环境变量读取可选 JSON。
|
|
23
|
+
*/
|
|
24
|
+
static safeParse<T>(value: string): T | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* 序列化为 JSON 字符串。
|
|
27
|
+
*
|
|
28
|
+
* @param compact `true` 时输出紧凑格式(无缩进);`false`(默认)时输出 2 空格缩进,便于阅读与 git diff。
|
|
29
|
+
*/
|
|
30
|
+
static stringify(value: unknown, compact?: boolean): string;
|
|
31
|
+
/**
|
|
32
|
+
* 严格读取并解析 JSON 文件(UTF-8)。
|
|
33
|
+
*
|
|
34
|
+
* 读取失败(如文件不存在)或解析失败时会抛错。
|
|
35
|
+
*/
|
|
36
|
+
static read<T>(file: string): Promise<T>;
|
|
37
|
+
/**
|
|
38
|
+
* 安全读取并解析 JSON 文件(UTF-8)。
|
|
39
|
+
*
|
|
40
|
+
* - 文件不存在、权限问题、JSON 非法等任意异常:返回 `undefined`;
|
|
41
|
+
* - 读取和解析都成功:返回目标类型值。
|
|
42
|
+
*
|
|
43
|
+
* 适合读取“可选存在”的本地缓存、临时状态文件。
|
|
44
|
+
*/
|
|
45
|
+
static safeRead<T>(file: string): Promise<T | undefined>;
|
|
46
|
+
/**
|
|
47
|
+
* 将值写入 JSON 文件(UTF-8)。
|
|
48
|
+
*/
|
|
49
|
+
static write(file: string, value: unknown, compact?: boolean): Promise<void>;
|
|
50
|
+
}
|
package/lib/json.mjs
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
// src/json.ts
|
|
3
|
+
/**
|
|
4
|
+
* JSON 文本与文件读写工具。
|
|
5
|
+
*
|
|
6
|
+
* 提供两类能力:
|
|
7
|
+
* - 严格方法:`parse` / `read`,遇到非法 JSON 或文件异常时抛错;
|
|
8
|
+
* - 安全方法:`safeParse` / `safeRead`,遇到异常时返回 `undefined`,适合可选配置读取场景。
|
|
9
|
+
*/
|
|
10
|
+
var XJson = class XJson {
|
|
11
|
+
/**
|
|
12
|
+
* 严格解析 JSON 文本。
|
|
13
|
+
*
|
|
14
|
+
* - 解析成功:返回目标类型值;
|
|
15
|
+
* - 解析失败:抛出 `SyntaxError`。
|
|
16
|
+
*/
|
|
17
|
+
static parse(value) {
|
|
18
|
+
return JSON.parse(value);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 安全解析 JSON 文本。
|
|
22
|
+
*
|
|
23
|
+
* - 解析成功:返回目标类型值;
|
|
24
|
+
* - 解析失败:返回 `undefined`。
|
|
25
|
+
*
|
|
26
|
+
* 适用于“配置可缺省、失败可兜底”的场景,例如从环境变量读取可选 JSON。
|
|
27
|
+
*/
|
|
28
|
+
static safeParse(value) {
|
|
29
|
+
try {
|
|
30
|
+
return XJson.parse(value);
|
|
31
|
+
} catch {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 序列化为 JSON 字符串。
|
|
37
|
+
*
|
|
38
|
+
* @param compact `true` 时输出紧凑格式(无缩进);`false`(默认)时输出 2 空格缩进,便于阅读与 git diff。
|
|
39
|
+
*/
|
|
40
|
+
static stringify(value, compact = false) {
|
|
41
|
+
return JSON.stringify(value, null, compact ? void 0 : 2);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 严格读取并解析 JSON 文件(UTF-8)。
|
|
45
|
+
*
|
|
46
|
+
* 读取失败(如文件不存在)或解析失败时会抛错。
|
|
47
|
+
*/
|
|
48
|
+
static async read(file) {
|
|
49
|
+
const value = await fs.promises.readFile(file, "utf8");
|
|
50
|
+
return XJson.parse(value);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 安全读取并解析 JSON 文件(UTF-8)。
|
|
54
|
+
*
|
|
55
|
+
* - 文件不存在、权限问题、JSON 非法等任意异常:返回 `undefined`;
|
|
56
|
+
* - 读取和解析都成功:返回目标类型值。
|
|
57
|
+
*
|
|
58
|
+
* 适合读取“可选存在”的本地缓存、临时状态文件。
|
|
59
|
+
*/
|
|
60
|
+
static async safeRead(file) {
|
|
61
|
+
try {
|
|
62
|
+
return await XJson.read(file);
|
|
63
|
+
} catch {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 将值写入 JSON 文件(UTF-8)。
|
|
69
|
+
*/
|
|
70
|
+
static async write(file, value, compact = false) {
|
|
71
|
+
await fs.promises.writeFile(file, XJson.stringify(value, compact));
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
export { XJson };
|
package/lib/jwt.d.mts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { MsValue } from "vtils";
|
|
2
|
+
import jwt from "jsonwebtoken";
|
|
3
|
+
import { LRUCache } from "lru-cache";
|
|
4
|
+
import { RequiredBy } from "vtils/types";
|
|
5
|
+
import { IncomingHttpHeaders } from "node:http";
|
|
6
|
+
/**
|
|
7
|
+
* JWT 载荷。
|
|
8
|
+
*
|
|
9
|
+
* 即签入 token 的自定义数据;`jsonwebtoken` 签发时会额外写入
|
|
10
|
+
* `iat`(签发时间,Unix 秒)与 `exp`(过期时间,Unix 秒)两个标准字段。
|
|
11
|
+
*
|
|
12
|
+
* 该接口只是宽松约束,实际载荷可包含任意字段,
|
|
13
|
+
* 调用方通过 `sign` / `verify` 的泛型参数指定具体形状。
|
|
14
|
+
*/
|
|
15
|
+
export interface XJwtPayload extends Record<string, any> {}
|
|
16
|
+
/**
|
|
17
|
+
* `XJwt` 的构造选项。
|
|
18
|
+
*/
|
|
19
|
+
export interface XJwtOptions {
|
|
20
|
+
/**
|
|
21
|
+
* 签名密钥。
|
|
22
|
+
*
|
|
23
|
+
* 用于签发与校验 token,必须保密;一旦泄露,
|
|
24
|
+
* 任何人都可以伪造出能被校验通过的 token。
|
|
25
|
+
*/
|
|
26
|
+
secret: string;
|
|
27
|
+
/**
|
|
28
|
+
* 默认有效期。
|
|
29
|
+
*
|
|
30
|
+
* 支持字符串(如 `'1s'`、`'30m'`、`'7d'`)或数字(毫秒)。
|
|
31
|
+
* 当 `sign` 未显式传入 `ttl` 时使用该值。
|
|
32
|
+
*
|
|
33
|
+
* 说明:token 的 `exp` 字段精度为秒(`jsonwebtoken` 的限制),
|
|
34
|
+
* 换算时不足 1 秒的部分会四舍五入;缓存的有效期则使用毫秒精度。
|
|
35
|
+
*/
|
|
36
|
+
ttl: MsValue;
|
|
37
|
+
/**
|
|
38
|
+
* 可选的内存缓存配置。
|
|
39
|
+
*
|
|
40
|
+
* 传入后启用“先查缓存、命中即返回”的快速校验路径,
|
|
41
|
+
* 可避免对同一 token 重复执行 `jsonwebtoken` 验签。
|
|
42
|
+
*/
|
|
43
|
+
cache?: LRUCache.Options<string, XJwtPayload, any>;
|
|
44
|
+
/**
|
|
45
|
+
* 校验通过后的钩子。
|
|
46
|
+
*
|
|
47
|
+
* 在 token 校验成功(或命中缓存)后、`verify` 返回前调用,
|
|
48
|
+
* 可用于记录日志、更新活跃状态等;若钩子抛出异常,
|
|
49
|
+
* `verify` 会以该异常拒绝。
|
|
50
|
+
*/
|
|
51
|
+
onVerify?: (payload: XJwtPayload & RequiredBy<jwt.JwtPayload, 'iat' | 'exp'> & {
|
|
52
|
+
token: string;
|
|
53
|
+
}) => any;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* `XJwt.verify` 的返回结果。
|
|
57
|
+
*
|
|
58
|
+
* 在自定义载荷的基础上,合并 `jsonwebtoken` 校验后的标准字段
|
|
59
|
+
* (`iat` / `exp` 等)以及本次校验所用的 `token` 字段。
|
|
60
|
+
*/
|
|
61
|
+
export type XJwtVerifyResult<T extends XJwtPayload> = T & RequiredBy<jwt.JwtPayload, 'iat' | 'exp'> & {
|
|
62
|
+
token: string;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* JWT 签发与校验工具。
|
|
66
|
+
*
|
|
67
|
+
* 封装 `jsonwebtoken`,提供统一的签发(`sign`)与校验(`verify`),
|
|
68
|
+
* 并额外支持:
|
|
69
|
+
* - 可选的内存缓存(`LRUCache`),命中缓存时跳过重复验签;
|
|
70
|
+
* - 从 HTTP 或 WebSocket 升级请求头中提取 token(`verifyFromHttpHeaders`)。
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* const xJwt = new XJwt({ secret: 'xx', ttl: '1h' });
|
|
74
|
+
*
|
|
75
|
+
* const token = xJwt.sign({ userId: 1 });
|
|
76
|
+
* // 'header.payload.signature'
|
|
77
|
+
*
|
|
78
|
+
* await xJwt.verify(token);
|
|
79
|
+
* // { userId: 1, iat: 1730000000, exp: 1730003600, token: 'header.payload.signature' }
|
|
80
|
+
*/
|
|
81
|
+
export declare class XJwt {
|
|
82
|
+
private options;
|
|
83
|
+
private cache;
|
|
84
|
+
constructor(options: XJwtOptions);
|
|
85
|
+
/**
|
|
86
|
+
* 签发 JWT。
|
|
87
|
+
*
|
|
88
|
+
* 使用 `secret` 对 `payload` 签名,并写入 `iat` 与 `exp`。
|
|
89
|
+
* 若启用了缓存,会将原始 `payload` 以 token 为 key 写入缓存,
|
|
90
|
+
* 有效期与 token 一致,供 `verify` 快速命中。
|
|
91
|
+
*
|
|
92
|
+
* @param payload 要签入 token 的自定义载荷。
|
|
93
|
+
* @param ttl 有效期,默认使用构造时传入的 `options.ttl`;
|
|
94
|
+
* 支持字符串(如 `'1s'`)或数字(毫秒)。
|
|
95
|
+
* @returns 签名后的 JWT 字符串。
|
|
96
|
+
*/
|
|
97
|
+
sign<T extends XJwtPayload>(payload: T, ttl?: MsValue): string;
|
|
98
|
+
/**
|
|
99
|
+
* 校验 JWT。
|
|
100
|
+
*
|
|
101
|
+
* 主要分支:
|
|
102
|
+
* - 启用了缓存且 token 命中缓存:直接返回缓存中的载荷(补上 `token` 字段),
|
|
103
|
+
* 不再执行验签;
|
|
104
|
+
* - 否则调用 `jsonwebtoken.verify` 验签,要求载荷中存在 `iat` 与 `exp`
|
|
105
|
+
* 且未过期;验签通过后写入缓存(有效期 = token 剩余时长)并返回。
|
|
106
|
+
*
|
|
107
|
+
* 校验通过后,会先执行 `options.onVerify`(若已配置),再返回结果。
|
|
108
|
+
*
|
|
109
|
+
* @param token 待校验的 JWT 字符串。
|
|
110
|
+
* @returns 载荷与 `token` 字段合并后的对象(含 `iat` / `exp` 等标准字段)。
|
|
111
|
+
* @throws 当 token 无效、格式错误、已过期,或 `onVerify` 钩子抛出异常时拒绝。
|
|
112
|
+
*/
|
|
113
|
+
verify<T extends XJwtPayload>(token: string): Promise<XJwtVerifyResult<T>>;
|
|
114
|
+
/**
|
|
115
|
+
* 从请求头中提取 token 并校验。
|
|
116
|
+
*
|
|
117
|
+
* 依次尝试以下来源:
|
|
118
|
+
* 1. `authorization` 头:支持 `Bearer <token>` 形式,也兼容不带 scheme 的
|
|
119
|
+
* 原始 token(按空格拆分后,若只有一段则直接作为 token);
|
|
120
|
+
* 2. `sec-websocket-protocol` 头(WebSocket 升级请求):其值为原始 token,
|
|
121
|
+
* 会被包装成 `Bearer <token>` 后按相同逻辑解析。
|
|
122
|
+
*
|
|
123
|
+
* @param headers Node HTTP 请求头对象。
|
|
124
|
+
* @returns `verify` 的校验结果。
|
|
125
|
+
* @throws 当两个头都缺失或为空时抛出异常。
|
|
126
|
+
*/
|
|
127
|
+
verifyFromHttpHeaders<T extends XJwtPayload>(headers: IncomingHttpHeaders): Promise<XJwtVerifyResult<T>>;
|
|
128
|
+
}
|
package/lib/jwt.mjs
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { ms } from "vtils";
|
|
2
|
+
import { differenceInMilliseconds, fromUnixTime } from "vtils/date";
|
|
3
|
+
import jwt from "jsonwebtoken";
|
|
4
|
+
import { LRUCache } from "lru-cache";
|
|
5
|
+
// src/jwt.ts
|
|
6
|
+
/**
|
|
7
|
+
* JWT 签发与校验工具。
|
|
8
|
+
*
|
|
9
|
+
* 封装 `jsonwebtoken`,提供统一的签发(`sign`)与校验(`verify`),
|
|
10
|
+
* 并额外支持:
|
|
11
|
+
* - 可选的内存缓存(`LRUCache`),命中缓存时跳过重复验签;
|
|
12
|
+
* - 从 HTTP 或 WebSocket 升级请求头中提取 token(`verifyFromHttpHeaders`)。
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const xJwt = new XJwt({ secret: 'xx', ttl: '1h' });
|
|
16
|
+
*
|
|
17
|
+
* const token = xJwt.sign({ userId: 1 });
|
|
18
|
+
* // 'header.payload.signature'
|
|
19
|
+
*
|
|
20
|
+
* await xJwt.verify(token);
|
|
21
|
+
* // { userId: 1, iat: 1730000000, exp: 1730003600, token: 'header.payload.signature' }
|
|
22
|
+
*/
|
|
23
|
+
var XJwt = class {
|
|
24
|
+
options;
|
|
25
|
+
cache;
|
|
26
|
+
constructor(options) {
|
|
27
|
+
this.options = options;
|
|
28
|
+
if (options.cache) this.cache = new LRUCache(options.cache);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 签发 JWT。
|
|
32
|
+
*
|
|
33
|
+
* 使用 `secret` 对 `payload` 签名,并写入 `iat` 与 `exp`。
|
|
34
|
+
* 若启用了缓存,会将原始 `payload` 以 token 为 key 写入缓存,
|
|
35
|
+
* 有效期与 token 一致,供 `verify` 快速命中。
|
|
36
|
+
*
|
|
37
|
+
* @param payload 要签入 token 的自定义载荷。
|
|
38
|
+
* @param ttl 有效期,默认使用构造时传入的 `options.ttl`;
|
|
39
|
+
* 支持字符串(如 `'1s'`)或数字(毫秒)。
|
|
40
|
+
* @returns 签名后的 JWT 字符串。
|
|
41
|
+
*/
|
|
42
|
+
sign(payload, ttl = this.options.ttl) {
|
|
43
|
+
const token = jwt.sign(payload, this.options.secret, { expiresIn: ms(ttl, true) });
|
|
44
|
+
if (this.cache) this.cache.set(token, payload, { ttl: ms(ttl) });
|
|
45
|
+
return token;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 校验 JWT。
|
|
49
|
+
*
|
|
50
|
+
* 主要分支:
|
|
51
|
+
* - 启用了缓存且 token 命中缓存:直接返回缓存中的载荷(补上 `token` 字段),
|
|
52
|
+
* 不再执行验签;
|
|
53
|
+
* - 否则调用 `jsonwebtoken.verify` 验签,要求载荷中存在 `iat` 与 `exp`
|
|
54
|
+
* 且未过期;验签通过后写入缓存(有效期 = token 剩余时长)并返回。
|
|
55
|
+
*
|
|
56
|
+
* 校验通过后,会先执行 `options.onVerify`(若已配置),再返回结果。
|
|
57
|
+
*
|
|
58
|
+
* @param token 待校验的 JWT 字符串。
|
|
59
|
+
* @returns 载荷与 `token` 字段合并后的对象(含 `iat` / `exp` 等标准字段)。
|
|
60
|
+
* @throws 当 token 无效、格式错误、已过期,或 `onVerify` 钩子抛出异常时拒绝。
|
|
61
|
+
*/
|
|
62
|
+
async verify(token) {
|
|
63
|
+
const data = await new Promise((resolve, reject) => {
|
|
64
|
+
if (this.cache && this.cache.has(token)) {
|
|
65
|
+
resolve({
|
|
66
|
+
...this.cache.get(token),
|
|
67
|
+
token
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
jwt.verify(token, this.options.secret, { ignoreExpiration: false }, (err, decoded) => {
|
|
72
|
+
if (err || !decoded || typeof decoded === "string" || !decoded.iat || !decoded.exp) {
|
|
73
|
+
reject(/* @__PURE__ */ new Error("JWT 校验失败:token 无效、格式错误或已过期"));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
decoded.token = token;
|
|
77
|
+
if (this.cache) this.cache.set(token, decoded, { ttl: differenceInMilliseconds(fromUnixTime(decoded.exp), /* @__PURE__ */ new Date()) });
|
|
78
|
+
resolve(decoded);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
await this.options.onVerify?.(data);
|
|
82
|
+
return data;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 从请求头中提取 token 并校验。
|
|
86
|
+
*
|
|
87
|
+
* 依次尝试以下来源:
|
|
88
|
+
* 1. `authorization` 头:支持 `Bearer <token>` 形式,也兼容不带 scheme 的
|
|
89
|
+
* 原始 token(按空格拆分后,若只有一段则直接作为 token);
|
|
90
|
+
* 2. `sec-websocket-protocol` 头(WebSocket 升级请求):其值为原始 token,
|
|
91
|
+
* 会被包装成 `Bearer <token>` 后按相同逻辑解析。
|
|
92
|
+
*
|
|
93
|
+
* @param headers Node HTTP 请求头对象。
|
|
94
|
+
* @returns `verify` 的校验结果。
|
|
95
|
+
* @throws 当两个头都缺失或为空时抛出异常。
|
|
96
|
+
*/
|
|
97
|
+
async verifyFromHttpHeaders(headers) {
|
|
98
|
+
const authorization = headers.authorization || headers["sec-websocket-protocol"] && `Bearer ${headers["sec-websocket-protocol"]}` || "";
|
|
99
|
+
if (!authorization) throw new Error("缺少认证信息:请提供 Authorization 或 Sec-WebSocket-Protocol 请求头");
|
|
100
|
+
const [scheme, token = scheme] = authorization.split(" ");
|
|
101
|
+
return this.verify(token);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
export { XJwt };
|