liangzimixin 0.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/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/auth/oauth-client.d.ts +99 -0
- package/dist/auth/oauth-client.d.ts.map +1 -0
- package/dist/auth/oauth-client.js +247 -0
- package/dist/auth/oauth-client.js.map +1 -0
- package/dist/auth/token-manager.d.ts +118 -0
- package/dist/auth/token-manager.d.ts.map +1 -0
- package/dist/auth/token-manager.js +298 -0
- package/dist/auth/token-manager.js.map +1 -0
- package/dist/auth/token-store.d.ts +83 -0
- package/dist/auth/token-store.d.ts.map +1 -0
- package/dist/auth/token-store.js +172 -0
- package/dist/auth/token-store.js.map +1 -0
- package/dist/channel/gate.d.ts +37 -0
- package/dist/channel/gate.d.ts.map +1 -0
- package/dist/channel/gate.js +50 -0
- package/dist/channel/gate.js.map +1 -0
- package/dist/channel/outbound.d.ts +27 -0
- package/dist/channel/outbound.d.ts.map +1 -0
- package/dist/channel/outbound.js +104 -0
- package/dist/channel/outbound.js.map +1 -0
- package/dist/channel/plugin.d.ts +9 -0
- package/dist/channel/plugin.d.ts.map +1 -0
- package/dist/channel/plugin.js +158 -0
- package/dist/channel/plugin.js.map +1 -0
- package/dist/config-schema.d.ts +302 -0
- package/dist/config-schema.d.ts.map +1 -0
- package/dist/config-schema.js +306 -0
- package/dist/config-schema.js.map +1 -0
- package/dist/config.d.ts +83 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +144 -0
- package/dist/config.js.map +1 -0
- package/dist/crypto/crypto-engine.d.ts +120 -0
- package/dist/crypto/crypto-engine.d.ts.map +1 -0
- package/dist/crypto/crypto-engine.js +177 -0
- package/dist/crypto/crypto-engine.js.map +1 -0
- package/dist/crypto/key-manager.d.ts +87 -0
- package/dist/crypto/key-manager.d.ts.map +1 -0
- package/dist/crypto/key-manager.js +137 -0
- package/dist/crypto/key-manager.js.map +1 -0
- package/dist/crypto/quantun-plug-mock.d.ts +62 -0
- package/dist/crypto/quantun-plug-mock.d.ts.map +1 -0
- package/dist/crypto/quantun-plug-mock.js +128 -0
- package/dist/crypto/quantun-plug-mock.js.map +1 -0
- package/dist/crypto/strategy.d.ts +89 -0
- package/dist/crypto/strategy.d.ts.map +1 -0
- package/dist/crypto/strategy.js +170 -0
- package/dist/crypto/strategy.js.map +1 -0
- package/dist/file/api-client.d.ts +30 -0
- package/dist/file/api-client.d.ts.map +1 -0
- package/dist/file/api-client.js +58 -0
- package/dist/file/api-client.js.map +1 -0
- package/dist/file/download.d.ts +89 -0
- package/dist/file/download.d.ts.map +1 -0
- package/dist/file/download.js +145 -0
- package/dist/file/download.js.map +1 -0
- package/dist/file/http-client.d.ts +44 -0
- package/dist/file/http-client.d.ts.map +1 -0
- package/dist/file/http-client.js +161 -0
- package/dist/file/http-client.js.map +1 -0
- package/dist/file/media.d.ts +73 -0
- package/dist/file/media.d.ts.map +1 -0
- package/dist/file/media.js +202 -0
- package/dist/file/media.js.map +1 -0
- package/dist/file/upload.d.ts +49 -0
- package/dist/file/upload.d.ts.map +1 -0
- package/dist/file/upload.js +137 -0
- package/dist/file/upload.js.map +1 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +161 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +28 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +38 -0
- package/dist/logger.js.map +1 -0
- package/dist/message-handler/abort-detect.d.ts +13 -0
- package/dist/message-handler/abort-detect.d.ts.map +1 -0
- package/dist/message-handler/abort-detect.js +24 -0
- package/dist/message-handler/abort-detect.js.map +1 -0
- package/dist/message-handler/content-resolver.d.ts +44 -0
- package/dist/message-handler/content-resolver.d.ts.map +1 -0
- package/dist/message-handler/content-resolver.js +64 -0
- package/dist/message-handler/content-resolver.js.map +1 -0
- package/dist/message-handler/envelope-builder.d.ts +20 -0
- package/dist/message-handler/envelope-builder.d.ts.map +1 -0
- package/dist/message-handler/envelope-builder.js +57 -0
- package/dist/message-handler/envelope-builder.js.map +1 -0
- package/dist/message-handler/handler.d.ts +118 -0
- package/dist/message-handler/handler.d.ts.map +1 -0
- package/dist/message-handler/handler.js +124 -0
- package/dist/message-handler/handler.js.map +1 -0
- package/dist/message-handler/parser.d.ts +33 -0
- package/dist/message-handler/parser.d.ts.map +1 -0
- package/dist/message-handler/parser.js +96 -0
- package/dist/message-handler/parser.js.map +1 -0
- package/dist/push/cockatoo-client.d.ts +95 -0
- package/dist/push/cockatoo-client.d.ts.map +1 -0
- package/dist/push/cockatoo-client.js +155 -0
- package/dist/push/cockatoo-client.js.map +1 -0
- package/dist/push/message-transformer.d.ts +11 -0
- package/dist/push/message-transformer.d.ts.map +1 -0
- package/dist/push/message-transformer.js +13 -0
- package/dist/push/message-transformer.js.map +1 -0
- package/dist/push/push-queue.d.ts +134 -0
- package/dist/push/push-queue.d.ts.map +1 -0
- package/dist/push/push-queue.js +316 -0
- package/dist/push/push-queue.js.map +1 -0
- package/dist/reply-dispatcher/dispatcher.d.ts +36 -0
- package/dist/reply-dispatcher/dispatcher.d.ts.map +1 -0
- package/dist/reply-dispatcher/dispatcher.js +35 -0
- package/dist/reply-dispatcher/dispatcher.js.map +1 -0
- package/dist/reply-dispatcher/stream-handler.d.ts +23 -0
- package/dist/reply-dispatcher/stream-handler.d.ts.map +1 -0
- package/dist/reply-dispatcher/stream-handler.js +41 -0
- package/dist/reply-dispatcher/stream-handler.js.map +1 -0
- package/dist/reply-dispatcher/typing-indicator.d.ts +23 -0
- package/dist/reply-dispatcher/typing-indicator.d.ts.map +1 -0
- package/dist/reply-dispatcher/typing-indicator.js +43 -0
- package/dist/reply-dispatcher/typing-indicator.js.map +1 -0
- package/dist/transport/connection-manager.d.ts +65 -0
- package/dist/transport/connection-manager.d.ts.map +1 -0
- package/dist/transport/connection-manager.js +157 -0
- package/dist/transport/connection-manager.js.map +1 -0
- package/dist/transport/dedup.d.ts +38 -0
- package/dist/transport/dedup.d.ts.map +1 -0
- package/dist/transport/dedup.js +61 -0
- package/dist/transport/dedup.js.map +1 -0
- package/dist/transport/message-pipe.d.ts +80 -0
- package/dist/transport/message-pipe.d.ts.map +1 -0
- package/dist/transport/message-pipe.js +225 -0
- package/dist/transport/message-pipe.js.map +1 -0
- package/dist/transport/ws-client.d.ts +45 -0
- package/dist/transport/ws-client.d.ts.map +1 -0
- package/dist/transport/ws-client.js +93 -0
- package/dist/transport/ws-client.js.map +1 -0
- package/dist/types.d.ts +444 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +37 -0
- package/dist/types.js.map +1 -0
- package/openclaw.plugin.json +9 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 liangzimixin contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# liangzimixin (量子密信)
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/liangzimixin)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
量子加密即时通信 (Quantum-encrypted IM) 渠道插件,用于 [OpenClaw](https://openclaw.ai) 平台。
|
|
7
|
+
|
|
8
|
+
## 功能特性
|
|
9
|
+
|
|
10
|
+
- 💬 **即时通信** — 消息收发、撤回
|
|
11
|
+
- 🔐 **量子加密** — 端到端量子密钥加密,安全通信
|
|
12
|
+
- 📎 **文件传输** — 图片/文件上传下载,支持分片
|
|
13
|
+
- 🔄 **自动重连** — WebSocket 心跳保活 + 断线自动重连
|
|
14
|
+
- 🔔 **推送通知** — Cockatoo 推送(可选)
|
|
15
|
+
- 🔑 **OAuth 认证** — Seal OAuth 自动注册/Token 管理
|
|
16
|
+
|
|
17
|
+
## 安装
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
openclaw plugins install liangzimixin
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 要求
|
|
24
|
+
|
|
25
|
+
- **Node.js**: `v20` 或更高
|
|
26
|
+
- **OpenClaw**: `2026.2.26` 或更高(`openclaw -v` 检查版本)
|
|
27
|
+
|
|
28
|
+
## 配置
|
|
29
|
+
|
|
30
|
+
安装后在 OpenClaw 配置文件中添加插件配置:
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
plugins:
|
|
34
|
+
entries:
|
|
35
|
+
liangzimixin:
|
|
36
|
+
config:
|
|
37
|
+
credentials:
|
|
38
|
+
appId: "你的应用 ID"
|
|
39
|
+
appSecret: "你的应用密钥"
|
|
40
|
+
account: "账户标识"
|
|
41
|
+
quantumId: "量子服务 ID"
|
|
42
|
+
quantumSecret: "量子服务密钥"
|
|
43
|
+
transport:
|
|
44
|
+
wsUrl: "wss://im.example.com/ws"
|
|
45
|
+
auth:
|
|
46
|
+
serverUrl: "http://seal.example.com:8080"
|
|
47
|
+
clientName: "my-quantum-im"
|
|
48
|
+
message:
|
|
49
|
+
messageServiceBaseUrl: "https://msg.example.com/api/v1"
|
|
50
|
+
file:
|
|
51
|
+
fileServiceBaseUrl: "https://fs.example.com/api/v1"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 使用
|
|
55
|
+
|
|
56
|
+
配置完成后重启 OpenClaw Gateway:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
openclaw gateway restart
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
插件将自动连接到密信 IM 服务器,开始接收和发送消息。
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* openclaw-quantum-im — Seal OAuth HTTP 客户端
|
|
3
|
+
*
|
|
4
|
+
* 封装 Seal 授权服务器通信: register / getToken
|
|
5
|
+
* 基于《seal认证技术文档》OAuth2 Client Credentials 模式
|
|
6
|
+
*
|
|
7
|
+
* 安全要求:
|
|
8
|
+
* - client_secret / access_token 不得出现在日志中 (仅前 8 位 + ***)
|
|
9
|
+
* - HTTP 请求使用超时控制 (默认 30s)
|
|
10
|
+
*/
|
|
11
|
+
import type { SealConfig, SealClientRegistration, TokenData, IntrospectResult } from '../types.js';
|
|
12
|
+
/** Seal API 错误 — HTTP 非 2xx 响应 */
|
|
13
|
+
export declare class SealApiError extends Error {
|
|
14
|
+
readonly status: number;
|
|
15
|
+
readonly body: unknown;
|
|
16
|
+
readonly endpoint: string;
|
|
17
|
+
constructor(status: number, body: unknown, endpoint: string);
|
|
18
|
+
}
|
|
19
|
+
/** Token 获取失败 (含重试后仍失败) */
|
|
20
|
+
export declare class TokenAcquireError extends Error {
|
|
21
|
+
constructor(message: string, options?: {
|
|
22
|
+
cause?: Error;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/** 脱敏 — 仅显示前 8 位 + *** */
|
|
26
|
+
declare function sanitize(value: string): string;
|
|
27
|
+
/** 延时等待 */
|
|
28
|
+
declare function sleep(ms: number): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Seal OAuth HTTP 客户端 — 封装与 Seal 授权服务器的所有 HTTP 通信。
|
|
31
|
+
* 提供核心操作: 动态注册 / 获取令牌
|
|
32
|
+
*
|
|
33
|
+
* Introspect 暂不实现,保留接口签名。
|
|
34
|
+
*/
|
|
35
|
+
export declare class SealClient {
|
|
36
|
+
private readonly baseUrl;
|
|
37
|
+
private clientId?;
|
|
38
|
+
private clientSecret?;
|
|
39
|
+
private readonly clientName;
|
|
40
|
+
private readonly scopes;
|
|
41
|
+
constructor(config: SealConfig);
|
|
42
|
+
/**
|
|
43
|
+
* 统一 HTTP 请求封装 — 含超时、日志脱敏和错误处理
|
|
44
|
+
* @param method - HTTP 方法
|
|
45
|
+
* @param path - 请求路径 (如 '/seal/client/register')
|
|
46
|
+
* @param options - 请求选项
|
|
47
|
+
* @returns 解析后的 JSON 响应
|
|
48
|
+
* @throws SealApiError — HTTP 非 2xx 响应
|
|
49
|
+
*/
|
|
50
|
+
private request;
|
|
51
|
+
/** 判断是否已注册 (有 clientId + clientSecret) */
|
|
52
|
+
isRegistered(): boolean;
|
|
53
|
+
/** 更新 clientId / clientSecret (注册成功后或从持久化加载后调用) */
|
|
54
|
+
setCredentials(clientId: string, clientSecret: string): void;
|
|
55
|
+
/**
|
|
56
|
+
* 动态注册客户端 → POST /seal/client/register
|
|
57
|
+
*
|
|
58
|
+
* 首次运行时自动调用,获取 clientId 和 clientSecret。
|
|
59
|
+
* 注册成功后自动更新内部凭据。
|
|
60
|
+
*/
|
|
61
|
+
register(params: {
|
|
62
|
+
/** 客户端显示名称 */
|
|
63
|
+
clientName: string;
|
|
64
|
+
/** 请求的 OAuth scope */
|
|
65
|
+
scopes: string[];
|
|
66
|
+
/** 授权类型 (默认包含 authorization_code, client_credentials, refresh_token) */
|
|
67
|
+
grantTypes?: string[];
|
|
68
|
+
/** 认证方法 (默认 ['client_secret_post']) */
|
|
69
|
+
authMethods?: string[];
|
|
70
|
+
/** 重定向 URI (默认 ['https://placeholder.local']) */
|
|
71
|
+
redirectUris?: string[];
|
|
72
|
+
/** Token 高级设置 */
|
|
73
|
+
tokenSettings?: {
|
|
74
|
+
accessTokenTtl?: string;
|
|
75
|
+
refreshTokenTtl?: string;
|
|
76
|
+
accessTokenFormat?: string;
|
|
77
|
+
reuseRefreshTokens?: boolean;
|
|
78
|
+
};
|
|
79
|
+
}): Promise<SealClientRegistration>;
|
|
80
|
+
/**
|
|
81
|
+
* 获取访问令牌 → POST /seal/oauth2/token (client_credentials)
|
|
82
|
+
*
|
|
83
|
+
* 使用客户端凭证模式获取 Access Token。
|
|
84
|
+
* 前置条件: clientId 和 clientSecret 必须存在 (通过 register 或 setCredentials 设置)
|
|
85
|
+
*
|
|
86
|
+
* @param scope - 请求的权限范围 (空格分隔),不传则使用注册时的全部 scope
|
|
87
|
+
* @returns TokenData 包含 access_token、过期时间等
|
|
88
|
+
* @throws Error — clientId/clientSecret 未设置
|
|
89
|
+
* @throws SealApiError — HTTP 错误
|
|
90
|
+
*/
|
|
91
|
+
getToken(scope?: string): Promise<TokenData>;
|
|
92
|
+
/**
|
|
93
|
+
* 令牌校验 → GET /seal/oauth2/introspect
|
|
94
|
+
* 暂不实现 — 保留接口签名,后续需要时再补充
|
|
95
|
+
*/
|
|
96
|
+
introspect(_token: string): Promise<IntrospectResult>;
|
|
97
|
+
}
|
|
98
|
+
export { sanitize, sleep };
|
|
99
|
+
//# sourceMappingURL=oauth-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-client.d.ts","sourceRoot":"","sources":["../../src/auth/oauth-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,sBAAsB,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAOnG,kCAAkC;AAClC,qBAAa,YAAa,SAAQ,KAAK;aAEnB,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,OAAO;aACb,QAAQ,EAAE,MAAM;gBAFhB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,MAAM;CAKnC;AAED,2BAA2B;AAC3B,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE;CAIzD;AAID,0BAA0B;AAC1B,iBAAS,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGvC;AAED,WAAW;AACX,iBAAS,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAExC;AAKD;;;;;GAKG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;gBAEtB,MAAM,EAAE,UAAU;IAW9B;;;;;;;OAOG;YACW,OAAO;IA6DrB,0CAA0C;IAC1C,YAAY,IAAI,OAAO;IAIvB,mDAAmD;IACnD,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;IAM5D;;;;;OAKG;IACG,QAAQ,CAAC,MAAM,EAAE;QACrB,cAAc;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,sBAAsB;QACtB,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,wEAAwE;QACxE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;QACtB,uCAAuC;QACvC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QACvB,iDAAiD;QACjD,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;QACxB,iBAAiB;QACjB,aAAa,CAAC,EAAE;YACd,cAAc,CAAC,EAAE,MAAM,CAAC;YACxB,eAAe,CAAC,EAAE,MAAM,CAAC;YACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;YAC3B,kBAAkB,CAAC,EAAE,OAAO,CAAC;SAC9B,CAAC;KACH,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAqEnC;;;;;;;;;;OAUG;IACG,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IA6ClD;;;OAGG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAG5D;AAED,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* openclaw-quantum-im — Seal OAuth HTTP 客户端
|
|
3
|
+
*
|
|
4
|
+
* 封装 Seal 授权服务器通信: register / getToken
|
|
5
|
+
* 基于《seal认证技术文档》OAuth2 Client Credentials 模式
|
|
6
|
+
*
|
|
7
|
+
* 安全要求:
|
|
8
|
+
* - client_secret / access_token 不得出现在日志中 (仅前 8 位 + ***)
|
|
9
|
+
* - HTTP 请求使用超时控制 (默认 30s)
|
|
10
|
+
*/
|
|
11
|
+
import { createLogger } from '../logger.js';
|
|
12
|
+
const log = createLogger('auth/oauth-client');
|
|
13
|
+
// ── 错误类型 ──
|
|
14
|
+
/** Seal API 错误 — HTTP 非 2xx 响应 */
|
|
15
|
+
export class SealApiError extends Error {
|
|
16
|
+
status;
|
|
17
|
+
body;
|
|
18
|
+
endpoint;
|
|
19
|
+
constructor(status, body, endpoint) {
|
|
20
|
+
super(`Seal API error: ${status} on ${endpoint}`);
|
|
21
|
+
this.status = status;
|
|
22
|
+
this.body = body;
|
|
23
|
+
this.endpoint = endpoint;
|
|
24
|
+
this.name = 'SealApiError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Token 获取失败 (含重试后仍失败) */
|
|
28
|
+
export class TokenAcquireError extends Error {
|
|
29
|
+
constructor(message, options) {
|
|
30
|
+
super(message, options);
|
|
31
|
+
this.name = 'TokenAcquireError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// ── 工具函数 ──
|
|
35
|
+
/** 脱敏 — 仅显示前 8 位 + *** */
|
|
36
|
+
function sanitize(value) {
|
|
37
|
+
if (value.length <= 8)
|
|
38
|
+
return '***';
|
|
39
|
+
return value.slice(0, 8) + '***';
|
|
40
|
+
}
|
|
41
|
+
/** 延时等待 */
|
|
42
|
+
function sleep(ms) {
|
|
43
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
44
|
+
}
|
|
45
|
+
// ── HTTP 请求默认超时 ──
|
|
46
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
47
|
+
/**
|
|
48
|
+
* Seal OAuth HTTP 客户端 — 封装与 Seal 授权服务器的所有 HTTP 通信。
|
|
49
|
+
* 提供核心操作: 动态注册 / 获取令牌
|
|
50
|
+
*
|
|
51
|
+
* Introspect 暂不实现,保留接口签名。
|
|
52
|
+
*/
|
|
53
|
+
export class SealClient {
|
|
54
|
+
baseUrl;
|
|
55
|
+
clientId;
|
|
56
|
+
clientSecret;
|
|
57
|
+
clientName;
|
|
58
|
+
scopes;
|
|
59
|
+
constructor(config) {
|
|
60
|
+
this.baseUrl = config.serverUrl.replace(/\/+$/, ''); // 去除尾部斜杠
|
|
61
|
+
this.clientId = config.clientId;
|
|
62
|
+
this.clientSecret = config.clientSecret;
|
|
63
|
+
this.clientName = config.clientName;
|
|
64
|
+
this.scopes = config.scopes;
|
|
65
|
+
log.info('SealClient initialized', { serverUrl: this.baseUrl, clientName: config.clientName });
|
|
66
|
+
}
|
|
67
|
+
// ── 通用 HTTP 请求 ──
|
|
68
|
+
/**
|
|
69
|
+
* 统一 HTTP 请求封装 — 含超时、日志脱敏和错误处理
|
|
70
|
+
* @param method - HTTP 方法
|
|
71
|
+
* @param path - 请求路径 (如 '/seal/client/register')
|
|
72
|
+
* @param options - 请求选项
|
|
73
|
+
* @returns 解析后的 JSON 响应
|
|
74
|
+
* @throws SealApiError — HTTP 非 2xx 响应
|
|
75
|
+
*/
|
|
76
|
+
async request(method, path, options = {}) {
|
|
77
|
+
const url = `${this.baseUrl}${path}`;
|
|
78
|
+
const timeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
79
|
+
const headers = { ...options.headers };
|
|
80
|
+
let bodyStr;
|
|
81
|
+
if (options.body) {
|
|
82
|
+
if (options.contentType === 'form' || options.body instanceof URLSearchParams) {
|
|
83
|
+
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
84
|
+
bodyStr = options.body instanceof URLSearchParams
|
|
85
|
+
? options.body.toString()
|
|
86
|
+
: new URLSearchParams(options.body).toString();
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
headers['Content-Type'] = 'application/json';
|
|
90
|
+
bodyStr = JSON.stringify(options.body);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
log.debug('HTTP request', { method, url });
|
|
94
|
+
const response = await fetch(url, {
|
|
95
|
+
method,
|
|
96
|
+
headers,
|
|
97
|
+
body: bodyStr,
|
|
98
|
+
signal: AbortSignal.timeout(timeout),
|
|
99
|
+
});
|
|
100
|
+
const responseBody = await response.text();
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
let parsedBody;
|
|
103
|
+
try {
|
|
104
|
+
parsedBody = JSON.parse(responseBody);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
parsedBody = responseBody;
|
|
108
|
+
}
|
|
109
|
+
log.error('HTTP error', { method, url, status: response.status });
|
|
110
|
+
throw new SealApiError(response.status, parsedBody, path);
|
|
111
|
+
}
|
|
112
|
+
log.debug('HTTP response', { method, url, status: response.status });
|
|
113
|
+
try {
|
|
114
|
+
return JSON.parse(responseBody);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
throw new SealApiError(response.status, responseBody, path);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// ── 公共方法 ──
|
|
121
|
+
/** 判断是否已注册 (有 clientId + clientSecret) */
|
|
122
|
+
isRegistered() {
|
|
123
|
+
return !!(this.clientId && this.clientSecret);
|
|
124
|
+
}
|
|
125
|
+
/** 更新 clientId / clientSecret (注册成功后或从持久化加载后调用) */
|
|
126
|
+
setCredentials(clientId, clientSecret) {
|
|
127
|
+
this.clientId = clientId;
|
|
128
|
+
this.clientSecret = clientSecret;
|
|
129
|
+
log.info('Credentials updated', { clientId: sanitize(clientId) });
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 动态注册客户端 → POST /seal/client/register
|
|
133
|
+
*
|
|
134
|
+
* 首次运行时自动调用,获取 clientId 和 clientSecret。
|
|
135
|
+
* 注册成功后自动更新内部凭据。
|
|
136
|
+
*/
|
|
137
|
+
async register(params) {
|
|
138
|
+
// 构建 Seal API 请求体 (snake_case)
|
|
139
|
+
const body = {
|
|
140
|
+
client_name: params.clientName,
|
|
141
|
+
scopes: params.scopes,
|
|
142
|
+
authentication_methods: params.authMethods ?? ['client_secret_post'],
|
|
143
|
+
grant_types: params.grantTypes ?? ['authorization_code', 'client_credentials', 'refresh_token'],
|
|
144
|
+
redirect_uris: params.redirectUris ?? ['https://placeholder.local'],
|
|
145
|
+
logout_redirect_uris: [],
|
|
146
|
+
client_settings: {
|
|
147
|
+
'settings.client.require-authorization-consent': true,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
// 映射 tokenSettings → Seal API 的 'settings.token.xxx' 格式
|
|
151
|
+
if (params.tokenSettings) {
|
|
152
|
+
const ts = {};
|
|
153
|
+
if (params.tokenSettings.accessTokenTtl) {
|
|
154
|
+
ts['settings.token.access-token-time-to-live'] = params.tokenSettings.accessTokenTtl;
|
|
155
|
+
}
|
|
156
|
+
if (params.tokenSettings.refreshTokenTtl) {
|
|
157
|
+
ts['settings.token.refresh-token-time-to-live'] = params.tokenSettings.refreshTokenTtl;
|
|
158
|
+
}
|
|
159
|
+
if (params.tokenSettings.accessTokenFormat) {
|
|
160
|
+
ts['settings.token.access-token-format'] = params.tokenSettings.accessTokenFormat;
|
|
161
|
+
}
|
|
162
|
+
if (params.tokenSettings.reuseRefreshTokens !== undefined) {
|
|
163
|
+
ts['settings.token.reuse-refresh-tokens'] = params.tokenSettings.reuseRefreshTokens;
|
|
164
|
+
}
|
|
165
|
+
// authorization-code-time-to-live 固定 300s
|
|
166
|
+
ts['settings.token.authorization-code-time-to-live'] = '300';
|
|
167
|
+
body.token_settings = ts;
|
|
168
|
+
}
|
|
169
|
+
log.info('Registering OAuth client', { clientName: params.clientName });
|
|
170
|
+
// 发请求
|
|
171
|
+
// 注意: 响应中的字段名与请求不同:
|
|
172
|
+
// authentication_methods → client_authentication_methods
|
|
173
|
+
// grant_types → authorization_grant_types
|
|
174
|
+
// client_settings / token_settings → 返回为 JSON 字符串
|
|
175
|
+
const response = await this.request('POST', '/seal/client/register', { body, contentType: 'json' });
|
|
176
|
+
// 解析响应 → SealClientRegistration (仅提取关键字段)
|
|
177
|
+
const registration = {
|
|
178
|
+
clientId: response.client_id,
|
|
179
|
+
clientSecret: response.client_secret,
|
|
180
|
+
clientSecretExpiresAt: response.client_secret_expires_at ?? '',
|
|
181
|
+
clientIdIssuedAt: response.client_id_issued_at ?? '',
|
|
182
|
+
scopes: response.scopes ?? params.scopes,
|
|
183
|
+
};
|
|
184
|
+
// 自动更新内部凭据
|
|
185
|
+
this.setCredentials(registration.clientId, registration.clientSecret);
|
|
186
|
+
log.info('OAuth client registered', {
|
|
187
|
+
clientId: sanitize(registration.clientId),
|
|
188
|
+
clientSecret: sanitize(registration.clientSecret),
|
|
189
|
+
scopes: registration.scopes,
|
|
190
|
+
expiresAt: registration.clientSecretExpiresAt,
|
|
191
|
+
});
|
|
192
|
+
return registration;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* 获取访问令牌 → POST /seal/oauth2/token (client_credentials)
|
|
196
|
+
*
|
|
197
|
+
* 使用客户端凭证模式获取 Access Token。
|
|
198
|
+
* 前置条件: clientId 和 clientSecret 必须存在 (通过 register 或 setCredentials 设置)
|
|
199
|
+
*
|
|
200
|
+
* @param scope - 请求的权限范围 (空格分隔),不传则使用注册时的全部 scope
|
|
201
|
+
* @returns TokenData 包含 access_token、过期时间等
|
|
202
|
+
* @throws Error — clientId/clientSecret 未设置
|
|
203
|
+
* @throws SealApiError — HTTP 错误
|
|
204
|
+
*/
|
|
205
|
+
async getToken(scope) {
|
|
206
|
+
if (!this.clientId || !this.clientSecret) {
|
|
207
|
+
throw new Error('Cannot get token: clientId and clientSecret are required. Call register() or setCredentials() first.');
|
|
208
|
+
}
|
|
209
|
+
// 构建 URL-encoded 请求体
|
|
210
|
+
const params = new URLSearchParams();
|
|
211
|
+
params.set('grant_type', 'client_credentials');
|
|
212
|
+
params.set('client_id', this.clientId);
|
|
213
|
+
params.set('client_secret', this.clientSecret);
|
|
214
|
+
if (scope) {
|
|
215
|
+
params.set('scope', scope);
|
|
216
|
+
}
|
|
217
|
+
log.info('Requesting access token', { clientId: sanitize(this.clientId) });
|
|
218
|
+
const response = await this.request('POST', '/seal/oauth2/token', { body: params, contentType: 'form' });
|
|
219
|
+
// 解析响应 → TokenData
|
|
220
|
+
const now = Date.now();
|
|
221
|
+
const expiresIn = response.expires_in;
|
|
222
|
+
const tokenData = {
|
|
223
|
+
accessToken: response.access_token,
|
|
224
|
+
tokenType: response.token_type ?? 'Bearer',
|
|
225
|
+
expiresIn,
|
|
226
|
+
scope: response.scope ?? '',
|
|
227
|
+
refreshToken: undefined, // Seal 不返回 refresh_token
|
|
228
|
+
grantedAt: now,
|
|
229
|
+
expiresAt: now + expiresIn * 1000,
|
|
230
|
+
};
|
|
231
|
+
log.info('Access token acquired', {
|
|
232
|
+
accessToken: sanitize(tokenData.accessToken),
|
|
233
|
+
expiresIn: tokenData.expiresIn,
|
|
234
|
+
scope: tokenData.scope,
|
|
235
|
+
});
|
|
236
|
+
return tokenData;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* 令牌校验 → GET /seal/oauth2/introspect
|
|
240
|
+
* 暂不实现 — 保留接口签名,后续需要时再补充
|
|
241
|
+
*/
|
|
242
|
+
async introspect(_token) {
|
|
243
|
+
throw new Error('introspect() not implemented yet — Seal introspect API 暂不使用');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
export { sanitize, sleep };
|
|
247
|
+
//# sourceMappingURL=oauth-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-client.js","sourceRoot":"","sources":["../../src/auth/oauth-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* openclaw-quantum-im — Token 生命周期管理
|
|
3
|
+
*
|
|
4
|
+
* 自动获取 / 缓存 / 过期重取 / 并发锁 / scope 检查 / 定时刷新
|
|
5
|
+
*
|
|
6
|
+
* Token 生命周期策略:
|
|
7
|
+
* - 首次调用: 检查本地缓存 → 未命中则注册(autoRegister) → 获取 Token → 缓存
|
|
8
|
+
* - 过期处理: 直接重新调用 POST /seal/oauth2/token 获取新 Token (无 refresh_token)
|
|
9
|
+
* - 提前刷新: Token 过期前 refreshAheadMs 毫秒自动重新获取
|
|
10
|
+
* - 并发锁: 多个并发请求共享同一个 Token 获取 Promise
|
|
11
|
+
*
|
|
12
|
+
* 安全要求:
|
|
13
|
+
* - 并发锁防止多个请求同时获取 Token
|
|
14
|
+
* - 重试策略有最大次数限制 (3 次)
|
|
15
|
+
* - 指数退避: 1s, 2s, 4s
|
|
16
|
+
*/
|
|
17
|
+
import type { IntrospectResult } from '../types.js';
|
|
18
|
+
import type { SealClient } from './oauth-client.js';
|
|
19
|
+
import type { TokenStore } from './token-store.js';
|
|
20
|
+
/**
|
|
21
|
+
* Token 生命周期管理器 — 自动处理令牌的获取、缓存、过期重取和并发控制。
|
|
22
|
+
*
|
|
23
|
+
* 核心方法:
|
|
24
|
+
* getValidToken() — 获取有效的 access_token (自动获取/重取/并发锁)
|
|
25
|
+
* hasScope(scope) — 检查当前令牌是否包含指定的 scope
|
|
26
|
+
* isAuthorized() — 检查是否持有有效令牌
|
|
27
|
+
* revokeAndClear() — 废置并清除所有令牌
|
|
28
|
+
* shutdown() — 清理定时器和并发锁
|
|
29
|
+
*/
|
|
30
|
+
export declare class TokenManager {
|
|
31
|
+
private readonly sealClient;
|
|
32
|
+
private readonly tokenStore;
|
|
33
|
+
/** Token 过期前提前刷新的时间 (ms) */
|
|
34
|
+
private readonly refreshAheadMs;
|
|
35
|
+
/** 自动注册的参数 */
|
|
36
|
+
private readonly autoRegisterParams?;
|
|
37
|
+
/** 内存中缓存的 access_token */
|
|
38
|
+
private cachedToken;
|
|
39
|
+
/** 内存中缓存的 scope (用于 hasScope 检查) */
|
|
40
|
+
private cachedScope;
|
|
41
|
+
/** 完整的 TokenData (用于过期判断) */
|
|
42
|
+
private currentTokenData;
|
|
43
|
+
/** 并发刷新锁 — 多个并发 getValidToken() 调用共用同一个 Promise */
|
|
44
|
+
private refreshLock;
|
|
45
|
+
/** 定时刷新器句柄 */
|
|
46
|
+
private refreshTimer;
|
|
47
|
+
constructor(deps: {
|
|
48
|
+
sealClient: SealClient;
|
|
49
|
+
tokenStore: TokenStore;
|
|
50
|
+
refreshAheadMs?: number;
|
|
51
|
+
/** 自动注册参数 — autoRegister=true 时使用 */
|
|
52
|
+
autoRegisterParams?: {
|
|
53
|
+
clientName: string;
|
|
54
|
+
scopes: string[];
|
|
55
|
+
tokenSettings?: {
|
|
56
|
+
accessTokenTtl?: string;
|
|
57
|
+
refreshTokenTtl?: string;
|
|
58
|
+
accessTokenFormat?: string;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
/**
|
|
63
|
+
* 获取有效的 access_token — 自动获取/重取,含并发锁。
|
|
64
|
+
*
|
|
65
|
+
* 流程:
|
|
66
|
+
* 1. 内存缓存检查: cachedToken 存在且未过期 → 直接返回
|
|
67
|
+
* 2. 并发锁检查: refreshLock 不为 null → await 共享 Promise
|
|
68
|
+
* 3. 慢路径: 设置 refreshLock → _acquireTokenWithRetry() → 清除锁
|
|
69
|
+
*
|
|
70
|
+
* @returns 有效的 access_token 字符串
|
|
71
|
+
*/
|
|
72
|
+
getValidToken(): Promise<string>;
|
|
73
|
+
/**
|
|
74
|
+
* 令牌自省 — 暂不实现,后续补充。
|
|
75
|
+
* 返回的 identity_id 用于 gate.ts 的 anti-loop 检查 (bot 自己的 ID)。
|
|
76
|
+
*/
|
|
77
|
+
introspect(): Promise<IntrospectResult>;
|
|
78
|
+
/** 检查当前令牌是否包含指定的 scope */
|
|
79
|
+
hasScope(scope: string): boolean;
|
|
80
|
+
/** 检查是否已授权 (是否持有有效令牌) */
|
|
81
|
+
isAuthorized(): boolean;
|
|
82
|
+
/** 废置并清除所有令牌 (包括文件存储和内存缓存) */
|
|
83
|
+
revokeAndClear(): Promise<void>;
|
|
84
|
+
/** 清理定时器和并发锁 — 优雅关闭时调用 */
|
|
85
|
+
shutdown(): void;
|
|
86
|
+
/**
|
|
87
|
+
* Token 获取 + 指数退避重试
|
|
88
|
+
*
|
|
89
|
+
* 重试策略:
|
|
90
|
+
* - 网络错误 / 5xx → 重试 (最多 3 次)
|
|
91
|
+
* - 401/403 → 不重试 (client_secret 可能无效)
|
|
92
|
+
*/
|
|
93
|
+
private _acquireTokenWithRetry;
|
|
94
|
+
/**
|
|
95
|
+
* Token 获取核心逻辑
|
|
96
|
+
*
|
|
97
|
+
* 流程:
|
|
98
|
+
* 1. 尝试从 TokenStore 加载缓存
|
|
99
|
+
* 2. 检查是否需要注册客户端
|
|
100
|
+
* 3. 调用 SealClient.getToken() 获取新 Token
|
|
101
|
+
* 4. 保存到内存缓存 + TokenStore
|
|
102
|
+
* 5. 设置定时刷新器
|
|
103
|
+
*/
|
|
104
|
+
private _acquireToken;
|
|
105
|
+
/** 更新内存缓存 */
|
|
106
|
+
private updateCache;
|
|
107
|
+
/** Token 是否需要刷新 (含提前量) — 用于触发主动刷新 */
|
|
108
|
+
private isTokenExpired;
|
|
109
|
+
/** Token 是否真正过期 (不含提前量) — 用于判断缓存的 Token 是否还能用 */
|
|
110
|
+
private isTokenHardExpired;
|
|
111
|
+
/**
|
|
112
|
+
* 设置定时刷新器 — 在 Token 过期前 refreshAheadMs 毫秒自动重新获取
|
|
113
|
+
*/
|
|
114
|
+
private scheduleRefresh;
|
|
115
|
+
/** 清除定时刷新器 */
|
|
116
|
+
private clearRefreshTimer;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=token-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-manager.d.ts","sourceRoot":"","sources":["../../src/auth/token-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAa,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAgBnD;;;;;;;;;GASG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,4BAA4B;IAC5B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IAExC,cAAc;IACd,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAQlC;IAIF,0BAA0B;IAC1B,OAAO,CAAC,WAAW,CAAuB;IAC1C,oCAAoC;IACpC,OAAO,CAAC,WAAW,CAAuB;IAC1C,6BAA6B;IAC7B,OAAO,CAAC,gBAAgB,CAA0B;IAIlD,mDAAmD;IACnD,OAAO,CAAC,WAAW,CAAgC;IACnD,cAAc;IACd,OAAO,CAAC,YAAY,CAA8C;gBAEtD,IAAI,EAAE;QAChB,UAAU,EAAE,UAAU,CAAC;QACvB,UAAU,EAAE,UAAU,CAAC;QACvB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,qCAAqC;QACrC,kBAAkB,CAAC,EAAE;YACnB,UAAU,EAAE,MAAM,CAAC;YACnB,MAAM,EAAE,MAAM,EAAE,CAAC;YACjB,aAAa,CAAC,EAAE;gBACd,cAAc,CAAC,EAAE,MAAM,CAAC;gBACxB,eAAe,CAAC,EAAE,MAAM,CAAC;gBACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;aAC5B,CAAC;SACH,CAAC;KACH;IAUD;;;;;;;;;OASG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAqBtC;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAI7C,0BAA0B;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAKhC,yBAAyB;IACzB,YAAY,IAAI,OAAO;IAIvB,8BAA8B;IACxB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IASrC,0BAA0B;IAC1B,QAAQ,IAAI,IAAI;IAQhB;;;;;;OAMG;YACW,sBAAsB;IAoCpC;;;;;;;;;OASG;YACW,aAAa;IA+E3B,aAAa;IACb,OAAO,CAAC,WAAW;IAQnB,qCAAqC;IACrC,OAAO,CAAC,cAAc;IAItB,iDAAiD;IACjD,OAAO,CAAC,kBAAkB;IAM1B;;OAEG;IACH,OAAO,CAAC,eAAe;IAqCvB,cAAc;IACd,OAAO,CAAC,iBAAiB;CAM1B"}
|