@seayoo-web/captcha 1.0.1 → 2.0.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/README.md +52 -1
- package/dist/index.js +56 -51
- package/package.json +26 -21
- package/types/src/tencent.d.ts +73 -83
package/README.md
CHANGED
|
@@ -1 +1,52 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @seayoo-web/captcha
|
|
2
|
+
|
|
3
|
+
腾讯天御验证码(Captcha)前端封装,对接 [腾讯云验证码](https://cloud.tencent.com/document/product/1110/36841)。
|
|
4
|
+
|
|
5
|
+
负责动态加载腾讯 `TJCaptcha.js`、弹出验证码、并把结果归一化返回。**不包含**「向自家服务端拉取验证码开关 / 配置」这类业务编排,`captchaAppId` 等由调用方(通常来自服务端下发)传入。
|
|
6
|
+
|
|
7
|
+
## 用法
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { TencentCaptchaAgent } from "@seayoo-web/captcha";
|
|
11
|
+
|
|
12
|
+
// 可选:首屏渲染完成后提前预热 SDK,减少首次弹窗延迟
|
|
13
|
+
TencentCaptchaAgent.preloadSDK();
|
|
14
|
+
|
|
15
|
+
const agent = new TencentCaptchaAgent({
|
|
16
|
+
captchaAppId, // 验证码应用 id,通常由服务端下发
|
|
17
|
+
// aidEncrypted、userLanguage、needFeedBack、enableDarkMode 可选
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const result = await agent.verify();
|
|
21
|
+
if (result.code === 0) {
|
|
22
|
+
// 验证通过,用 result.ticket / result.randstr 交服务端二次校验
|
|
23
|
+
} else if (result.code === 2) {
|
|
24
|
+
// 用户主动关闭
|
|
25
|
+
} else {
|
|
26
|
+
// 验证失败或容灾(code === 1001,ticket 为 trerror_ 前缀容灾票据)
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## API
|
|
31
|
+
|
|
32
|
+
### `new TencentCaptchaAgent(option)`
|
|
33
|
+
|
|
34
|
+
- `option.captchaAppId` — 必填,验证码应用 id。
|
|
35
|
+
- 其余字段(`aidEncrypted` / `userLanguage` / `needFeedBack` / `enableDarkMode`)透传给腾讯 SDK。
|
|
36
|
+
|
|
37
|
+
### `agent.verify(): Promise<CaptchaResult>`
|
|
38
|
+
|
|
39
|
+
触发一次人机验证。**不会抛错**:SDK 加载失败、用户关闭、超时(3 分钟)都会以 `CaptchaResult` 返回,其中加载 / 运行异常返回 `trerror_` 前缀的容灾票据(`code === 1001`),交由服务端识别降级。
|
|
40
|
+
|
|
41
|
+
`CaptchaResult`:
|
|
42
|
+
|
|
43
|
+
| 字段 | 说明 |
|
|
44
|
+
| --------- | ---------------------------------------------------------- |
|
|
45
|
+
| `code` | `0` 通过 / `2` 用户关闭 / `1001` 容灾 / 其它 失败 |
|
|
46
|
+
| `ticket` | 验证票据(`code === 0` 时有效) |
|
|
47
|
+
| `randstr` | 本次验证随机串 |
|
|
48
|
+
| `message` | 错误信息(正常时为空串) |
|
|
49
|
+
|
|
50
|
+
### `TencentCaptchaAgent.preloadSDK()`
|
|
51
|
+
|
|
52
|
+
提前加载腾讯 js sdk。建议在首屏渲染完成后调用——过早加载会在 Safari 上因样式未就绪而闪现一个很大的图标。
|
package/dist/index.js
CHANGED
|
@@ -1,54 +1,59 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { loadJS as e, useConsole as t } from "@seayoo-web/utils";
|
|
2
|
+
//#region src/tencent.ts
|
|
3
|
+
var n = t("CaptchaSDK"), r = "https://turing.captcha.qcloud.com/TJCaptcha.js", i = 6e4 * 3, a = null;
|
|
4
|
+
function o() {
|
|
5
|
+
return typeof document > "u" || typeof window > "u" ? Promise.resolve(null) : window.TencentCaptcha ? Promise.resolve(window.TencentCaptcha) : (a ||= e(r).then((e) => {
|
|
6
|
+
let t = e ? window.TencentCaptcha ?? null : null;
|
|
7
|
+
return t || (a = null), t;
|
|
8
|
+
}), a);
|
|
6
9
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* 创建验证码实例,创建后立即开始加载腾讯天御 SDK
|
|
17
|
-
*/
|
|
18
|
-
constructor(a) {
|
|
19
|
-
s().then((t) => {
|
|
20
|
-
if (!t)
|
|
21
|
-
p.error("Load TencentCaptcha Error!");
|
|
22
|
-
else {
|
|
23
|
-
const { captchaAppId: o, ...i } = a;
|
|
24
|
-
this.sdk = new t(
|
|
25
|
-
o,
|
|
26
|
-
(n) => {
|
|
27
|
-
this.event.emit("done", n);
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
...i,
|
|
31
|
-
ready: (n) => {
|
|
32
|
-
this.isReady = !0, this.event.emit("ready", n);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
async ready() {
|
|
40
|
-
if (this.isReady)
|
|
41
|
-
return;
|
|
42
|
-
const { promise: a, resolve: t } = d();
|
|
43
|
-
this.event.once("ready", t), await a;
|
|
44
|
-
}
|
|
45
|
-
async show() {
|
|
46
|
-
await this.ready(), this.sdk?.show();
|
|
47
|
-
}
|
|
48
|
-
async destroy() {
|
|
49
|
-
await this.ready(), this.sdk?.destroy();
|
|
50
|
-
}
|
|
10
|
+
function s(e, t) {
|
|
11
|
+
return {
|
|
12
|
+
code: 1001,
|
|
13
|
+
ticket: `trerror_1001_${e}_${Math.floor(Date.now() / 1e3)}`,
|
|
14
|
+
randstr: `@${Math.random().toString(36).slice(2)}`,
|
|
15
|
+
message: t
|
|
16
|
+
};
|
|
51
17
|
}
|
|
52
|
-
|
|
53
|
-
|
|
18
|
+
var c = class {
|
|
19
|
+
static preloadSDK() {
|
|
20
|
+
o();
|
|
21
|
+
}
|
|
22
|
+
constructor(e) {
|
|
23
|
+
this.option = e;
|
|
24
|
+
}
|
|
25
|
+
async verify() {
|
|
26
|
+
let { captchaAppId: e, ...t } = this.option, r = await o();
|
|
27
|
+
if (!r) return n.error("Load TencentCaptcha Error!"), s(e, "Captcha init Error");
|
|
28
|
+
try {
|
|
29
|
+
return await new Promise((n, a) => {
|
|
30
|
+
let o = new r(e, (e) => {
|
|
31
|
+
s(), n({
|
|
32
|
+
code: e.errorCode || e.ret,
|
|
33
|
+
ticket: e.ticket,
|
|
34
|
+
randstr: e.randstr,
|
|
35
|
+
message: e.errorMessage ?? ""
|
|
36
|
+
});
|
|
37
|
+
}, t);
|
|
38
|
+
function s() {
|
|
39
|
+
window.removeEventListener("resize", c), clearTimeout(l);
|
|
40
|
+
}
|
|
41
|
+
function c() {
|
|
42
|
+
let e = document.getElementById("tcaptcha_transform_dy");
|
|
43
|
+
if (!e) return;
|
|
44
|
+
let t = e.style.opacity;
|
|
45
|
+
t === "1" ? o.show() : t === "0" && (o.destroy(), s(), a(/* @__PURE__ */ Error("Captcha is hidden")));
|
|
46
|
+
}
|
|
47
|
+
window.addEventListener("resize", c);
|
|
48
|
+
let l = setTimeout(() => {
|
|
49
|
+
o.destroy(), s(), a(/* @__PURE__ */ Error("Captcha Timeout"));
|
|
50
|
+
}, i);
|
|
51
|
+
o.show();
|
|
52
|
+
});
|
|
53
|
+
} catch (t) {
|
|
54
|
+
return s(e, t instanceof Error ? t.message : `Captcha Error: ${String(t)}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
54
57
|
};
|
|
58
|
+
//#endregion
|
|
59
|
+
export { c as TencentCaptchaAgent };
|
package/package.json
CHANGED
|
@@ -1,42 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seayoo-web/captcha",
|
|
3
|
+
"version": "2.0.0",
|
|
3
4
|
"description": "captcha agent",
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"
|
|
5
|
+
"keywords": [
|
|
6
|
+
"captcha"
|
|
7
|
+
],
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"author": "web@seayoo.com",
|
|
8
10
|
"source": "./index.ts",
|
|
9
|
-
"types": "./types/index.d.ts",
|
|
10
11
|
"files": [
|
|
11
12
|
"dist",
|
|
12
13
|
"types",
|
|
13
14
|
"README.md"
|
|
14
15
|
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"module": "./dist/index.js",
|
|
20
|
+
"types": "./types/index.d.ts",
|
|
15
21
|
"publishConfig": {
|
|
16
22
|
"access": "public"
|
|
17
23
|
},
|
|
18
|
-
"engines": {
|
|
19
|
-
"node": ">=22"
|
|
20
|
-
},
|
|
21
|
-
"keywords": [
|
|
22
|
-
"captcha"
|
|
23
|
-
],
|
|
24
|
-
"author": "web@seayoo.com",
|
|
25
|
-
"license": "MIT",
|
|
26
24
|
"devDependencies": {
|
|
27
|
-
"@
|
|
28
|
-
"
|
|
29
|
-
"
|
|
25
|
+
"@vitest/coverage-istanbul": "^4.1.4",
|
|
26
|
+
"happy-dom": "^13.10.1",
|
|
27
|
+
"vitest": "^4.1.4",
|
|
28
|
+
"@seayoo-web/scripts": "^4.3.10",
|
|
29
|
+
"@seayoo-web/utils": "^4.5.4",
|
|
30
|
+
"@seayoo-web/tsconfig": "^1.0.6"
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
32
|
-
"@seayoo-web/utils": "^4.
|
|
33
|
+
"@seayoo-web/utils": "^4.5.4"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=22"
|
|
33
37
|
},
|
|
34
38
|
"scripts": {
|
|
35
39
|
"prebuild": "pnpm -F utils build",
|
|
36
|
-
"build": "vite build && tsc --emitDeclarationOnly",
|
|
40
|
+
"build": "vite build && tsc --build --emitDeclarationOnly",
|
|
37
41
|
"type-check": "tsc --noEmit",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
42
|
+
"test": "vitest --dom",
|
|
43
|
+
"coverage": "vitest run --coverage",
|
|
44
|
+
"lint": "oxlint -c ../../oxlint.config.mjs",
|
|
45
|
+
"lint:fix": "oxlint -c ../../oxlint.config.mjs --fix"
|
|
41
46
|
}
|
|
42
47
|
}
|
package/types/src/tencent.d.ts
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
/** spell-checker:ignore trerror_, randstr */
|
|
2
|
-
/** 对腾讯的sdk做简单声明 */
|
|
1
|
+
/** spell-checker:ignore trerror_, randstr, tcaptcha */
|
|
2
|
+
/** 对腾讯的 sdk 做简单声明 */
|
|
3
3
|
declare global {
|
|
4
4
|
interface Window {
|
|
5
5
|
TencentCaptcha?: TencentCaptchaSDK;
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
|
-
|
|
8
|
+
/** 腾讯 sdk 回调返回的原始结构 */
|
|
9
|
+
interface TencentVerifyResponse {
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
+
* 验证结果。0 = 成功;2 = 用户主动关闭;其它 = 失败
|
|
11
12
|
*/
|
|
12
13
|
ret: number;
|
|
13
14
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* 请求验证码发生错误,验证码自动返回 trerror_ 前缀的容灾票据
|
|
15
|
+
* 验证票据,ret = 0 时有值;客户端发生异常时返回 trerror_ 前缀的容灾票据
|
|
17
16
|
*/
|
|
18
17
|
ticket: string;
|
|
19
18
|
/**
|
|
@@ -21,108 +20,99 @@ interface VerifyResult {
|
|
|
21
20
|
*/
|
|
22
21
|
randstr: string;
|
|
23
22
|
/**
|
|
24
|
-
*
|
|
25
|
-
*/
|
|
26
|
-
bizState?: unknown;
|
|
27
|
-
/**
|
|
28
|
-
* 验证码校验接口耗时(ms)
|
|
23
|
+
* 客户端异常错误码,出现时仍可能返回可用票据
|
|
29
24
|
*/
|
|
30
|
-
|
|
25
|
+
errorCode?: number;
|
|
31
26
|
/**
|
|
32
|
-
*
|
|
27
|
+
* 客户端异常错误信息
|
|
33
28
|
*/
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* 链路 sid
|
|
37
|
-
*/
|
|
38
|
-
sid: string;
|
|
39
|
-
}
|
|
40
|
-
interface TencentCaptchaSDK {
|
|
41
|
-
new (captchaAppId: string, callback: (result: VerifyResult) => void, option?: Omit<TencentCaptchaOption, "captchaAppId"> & {
|
|
42
|
-
showFn?: () => void;
|
|
43
|
-
}): TencentCaptchaSDKIns;
|
|
44
|
-
}
|
|
45
|
-
interface TencentCaptchaSDKIns {
|
|
46
|
-
/** 显示验证码,可以反复调用 */
|
|
47
|
-
show(): void;
|
|
48
|
-
/** 隐藏验证码,可以反复调用 */
|
|
49
|
-
destroy(): void;
|
|
50
|
-
/** 获取验证成功后的 ticket */
|
|
51
|
-
getTicket(): {
|
|
52
|
-
CaptchaAppId: string;
|
|
53
|
-
ticket: string;
|
|
54
|
-
};
|
|
29
|
+
errorMessage?: string;
|
|
55
30
|
}
|
|
31
|
+
/** 透传给腾讯 sdk 的配置项 */
|
|
56
32
|
interface TencentCaptchaOption {
|
|
57
|
-
captchaAppId: string;
|
|
58
|
-
/**
|
|
59
|
-
* 验证码相关资源加载完成的回调,回调参数为验证码实际的宽高(单位:px)
|
|
60
|
-
*
|
|
61
|
-
* 该参数仅为查看验证码宽高使用
|
|
62
|
-
*/
|
|
63
|
-
ready?: (info: {
|
|
64
|
-
sdkView: {
|
|
65
|
-
width: number;
|
|
66
|
-
height: number;
|
|
67
|
-
};
|
|
68
|
-
}) => void;
|
|
69
33
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* - true 开启自适应深夜模式
|
|
73
|
-
* - "force" 强制深夜模式
|
|
34
|
+
* 指定验证码提示文案的语言,优先级高于控制台配置。大小写不敏感。
|
|
74
35
|
*/
|
|
75
|
-
|
|
36
|
+
userLanguage?: string;
|
|
76
37
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* 💡注意,此参数调整的并非验证码弹窗大小
|
|
38
|
+
* CaptchaAppId 加密校验串,详见 [CaptchaAppid 加密校验能力接入指引](https://cloud.tencent.com/document/product/1110/36841#c6a3e517-4962-4932-9bf8-9fb10fd03166)
|
|
80
39
|
*/
|
|
81
|
-
|
|
82
|
-
width: number;
|
|
83
|
-
height: number;
|
|
84
|
-
};
|
|
40
|
+
aidEncrypted?: string;
|
|
85
41
|
/**
|
|
86
42
|
* 隐藏帮助按钮或自定义帮助按钮链接
|
|
87
43
|
*/
|
|
88
44
|
needFeedBack?: boolean | `https://${string}`;
|
|
89
45
|
/**
|
|
90
|
-
*
|
|
46
|
+
* 开启自适应深夜模式或强制深夜模式
|
|
91
47
|
*
|
|
92
|
-
*
|
|
93
|
-
|
|
94
|
-
loading?: boolean;
|
|
95
|
-
/**
|
|
96
|
-
* 指定验证码提示文案的语言,优先级高于控制台配置。大小写不敏感。
|
|
48
|
+
* - true 开启自适应深夜模式
|
|
49
|
+
* - "force" 强制深夜模式
|
|
97
50
|
*/
|
|
98
|
-
|
|
51
|
+
enableDarkMode?: boolean | "force";
|
|
52
|
+
}
|
|
53
|
+
interface TencentCaptchaSDK {
|
|
54
|
+
new (captchaAppId: string, callback: (res: TencentVerifyResponse) => void, option?: TencentCaptchaOption): TencentCaptchaSDKIns;
|
|
55
|
+
}
|
|
56
|
+
interface TencentCaptchaSDKIns {
|
|
57
|
+
/** 显示验证码,可反复调用 */
|
|
58
|
+
show(): void;
|
|
59
|
+
/** 重新加载验证码 */
|
|
60
|
+
reload(): void;
|
|
61
|
+
/** 销毁验证码,可反复调用 */
|
|
62
|
+
destroy(): void;
|
|
63
|
+
}
|
|
64
|
+
/** 归一化后的验证结果 */
|
|
65
|
+
export interface CaptchaResult {
|
|
99
66
|
/**
|
|
100
|
-
*
|
|
67
|
+
* 结果码
|
|
101
68
|
*
|
|
102
|
-
* -
|
|
103
|
-
* -
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* CaptchaAppId 加密校验串,详见 [CaptchaAppid 加密校验能力接入指引](https://cloud.tencent.com/document/product/1110/36841#c6a3e517-4962-4932-9bf8-9fb10fd03166)
|
|
69
|
+
* - 0 验证通过,ticket 有效
|
|
70
|
+
* - 2 用户主动关闭验证码
|
|
71
|
+
* - 1001 容灾(加载失败 / 超时 / 异常关闭),ticket 为 trerror_ 前缀票据
|
|
72
|
+
* - 其它 验证失败
|
|
108
73
|
*/
|
|
109
|
-
|
|
74
|
+
code: number;
|
|
75
|
+
ticket: string;
|
|
76
|
+
randstr: string;
|
|
77
|
+
message: string;
|
|
78
|
+
}
|
|
79
|
+
/** {@link TencentCaptchaAgent} 构造参数 */
|
|
80
|
+
export interface TencentCaptchaAgentOption extends TencentCaptchaOption {
|
|
81
|
+
/** 验证码应用 id,通常由服务端下发 */
|
|
82
|
+
captchaAppId: string;
|
|
110
83
|
}
|
|
111
84
|
/**
|
|
112
85
|
* 腾讯天御验证码
|
|
86
|
+
*
|
|
87
|
+
* https://cloud.tencent.com/document/product/1110/36841
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* // 建议在首屏渲染后提前预热,减少首次弹窗延迟
|
|
92
|
+
* TencentCaptchaAgent.preloadSDK();
|
|
93
|
+
*
|
|
94
|
+
* const agent = new TencentCaptchaAgent({ captchaAppId });
|
|
95
|
+
* const result = await agent.verify();
|
|
96
|
+
* if (result.code === 0) {
|
|
97
|
+
* // 用 result.ticket / result.randstr 去服务端校验
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
113
100
|
*/
|
|
114
101
|
export declare class TencentCaptchaAgent {
|
|
115
|
-
private
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
102
|
+
private readonly option;
|
|
103
|
+
/**
|
|
104
|
+
* 提前加载腾讯 js sdk。
|
|
105
|
+
*
|
|
106
|
+
* 建议在首屏渲染完成后调用:过早加载会在 Safari 上因样式未就绪而闪现一个很大的图标。
|
|
107
|
+
*/
|
|
119
108
|
static preloadSDK(): void;
|
|
109
|
+
constructor(option: TencentCaptchaAgentOption);
|
|
120
110
|
/**
|
|
121
|
-
*
|
|
111
|
+
* 触发一次人机验证,返回归一化结果。
|
|
112
|
+
*
|
|
113
|
+
* 该方法**不会抛错**:SDK 加载失败、用户关闭、超时等都会以 {@link CaptchaResult} 返回,
|
|
114
|
+
* 其中加载/运行异常返回 `trerror_` 前缀的容灾票据(`code === 1001`),交由服务端降级。
|
|
122
115
|
*/
|
|
123
|
-
|
|
124
|
-
ready(): Promise<void>;
|
|
125
|
-
show(): Promise<void>;
|
|
126
|
-
destroy(): Promise<void>;
|
|
116
|
+
verify(): Promise<CaptchaResult>;
|
|
127
117
|
}
|
|
128
118
|
export {};
|