@honor-claw/yoyo 1.1.2 → 1.1.4-beta.1

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.
Files changed (66) hide show
  1. package/index.ts +25 -25
  2. package/package.json +5 -5
  3. package/src/apis/claw-cloud.ts +124 -124
  4. package/src/apis/helpers.ts +10 -10
  5. package/src/apis/honor-auth.ts +158 -158
  6. package/src/apis/http-client.ts +239 -239
  7. package/src/apis/index.ts +8 -8
  8. package/src/apis/types.ts +77 -73
  9. package/src/cloud-channel/channel.ts +117 -117
  10. package/src/cloud-channel/client.ts +3 -3
  11. package/src/cloud-channel/index.ts +4 -4
  12. package/src/cloud-channel/message-handler.ts +50 -42
  13. package/src/cloud-channel/session-manager.ts +14 -9
  14. package/src/cloud-channel/types.ts +115 -115
  15. package/src/commands/env/impl.ts +58 -58
  16. package/src/commands/env/index.ts +1 -1
  17. package/src/commands/index.ts +30 -30
  18. package/src/commands/login/impl.ts +30 -30
  19. package/src/commands/login/index.ts +1 -1
  20. package/src/commands/logout/index.ts +1 -1
  21. package/src/commands/status/index.ts +194 -194
  22. package/src/gateway-client/client.ts +90 -90
  23. package/src/gateway-client/device/auth.ts +57 -57
  24. package/src/gateway-client/device/builder.ts +105 -105
  25. package/src/gateway-client/device/helpers.ts +40 -40
  26. package/src/gateway-client/device/identity.ts +251 -251
  27. package/src/gateway-client/device/index.ts +40 -40
  28. package/src/gateway-client/device/types.ts +57 -57
  29. package/src/gateway-client/index.ts +5 -5
  30. package/src/gateway-client/protocol-client.ts +49 -34
  31. package/src/honor-auth/browser.ts +2 -2
  32. package/src/honor-auth/callback-server.ts +109 -109
  33. package/src/honor-auth/cloud.ts +57 -57
  34. package/src/honor-auth/config.ts +43 -43
  35. package/src/honor-auth/index.ts +3 -3
  36. package/src/honor-auth/token-manager.ts +90 -90
  37. package/src/honor-auth/types.ts +50 -50
  38. package/src/index.ts +10 -10
  39. package/src/modules/claw-configs/config-manager.ts +409 -409
  40. package/src/modules/claw-configs/hosts.ts +48 -48
  41. package/src/modules/claw-configs/index.ts +8 -8
  42. package/src/modules/claw-configs/provider.ts +394 -394
  43. package/src/modules/claw-configs/types.ts +34 -34
  44. package/src/modules/device/device-info.ts +1 -1
  45. package/src/modules/device/index.ts +3 -3
  46. package/src/modules/device/providers/base.ts +32 -32
  47. package/src/modules/device/providers/pad.ts +107 -107
  48. package/src/modules/device/providers/windows.ts +130 -130
  49. package/src/modules/device/registry.ts +48 -43
  50. package/src/modules/login/impl.ts +31 -26
  51. package/src/modules/login/index.ts +6 -6
  52. package/src/schemas.ts +23 -23
  53. package/src/services/connection/impl.ts +339 -339
  54. package/src/services/connection/status-tracker/events.ts +127 -127
  55. package/src/services/connection/status-tracker/index.ts +31 -31
  56. package/src/services/connection/status-tracker/storage.ts +133 -133
  57. package/src/services/connection/status-tracker/tracker.ts +370 -370
  58. package/src/services/connection/status-tracker/types.ts +131 -131
  59. package/src/services/connection/types.ts +20 -20
  60. package/src/types.ts +64 -64
  61. package/src/utils/id.ts +8 -8
  62. package/src/utils/jwt.ts +37 -37
  63. package/src/utils/logger.ts +20 -20
  64. package/src/utils/proxy.ts +58 -58
  65. package/src/utils/version.ts +29 -29
  66. package/src/utils/ws.ts +21 -21
package/index.ts CHANGED
@@ -1,25 +1,25 @@
1
- import { type OpenClawPluginApi } from "openclaw/plugin-sdk";
2
- import { setYoyoRuntime } from "./src/runtime.js";
3
- import { registerCommands } from "./src/commands/index.js";
4
- import { YoyoPluginConfigSchema } from "./src/schemas.js";
5
- import { createClawConnectionService } from "./src/services/connection/index.js";
6
- import { setClawLogger } from "./src/utils/logger.js";
7
-
8
- const plugin = {
9
- id: "yoyo",
10
- name: "YOYOClaw",
11
- description: "OpenClaw Honor Yoyo connection plugin",
12
- configSchema: YoyoPluginConfigSchema,
13
- register(api: OpenClawPluginApi) {
14
- setYoyoRuntime(api.runtime);
15
- setClawLogger(api.logger);
16
-
17
- // 利用服务来管理核心连接任务进行~
18
- api.registerService(createClawConnectionService(api));
19
-
20
- // 注册所有的命令行
21
- registerCommands(api);
22
- },
23
- };
24
-
25
- export default plugin;
1
+ import { type OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ import { registerCommands } from "./src/commands/index.js";
3
+ import { setYoyoRuntime } from "./src/runtime.js";
4
+ import { YoyoPluginConfigSchema } from "./src/schemas.js";
5
+ import { createClawConnectionService } from "./src/services/connection/index.js";
6
+ import { setClawLogger } from "./src/utils/logger.js";
7
+
8
+ const plugin = {
9
+ id: "yoyo",
10
+ name: "YOYOClaw",
11
+ description: "OpenClaw Honor Yoyo connection plugin",
12
+ configSchema: YoyoPluginConfigSchema,
13
+ register(api: OpenClawPluginApi) {
14
+ setYoyoRuntime(api.runtime);
15
+ setClawLogger(api.logger);
16
+
17
+ // 利用服务来管理核心连接任务进行~
18
+ api.registerService(createClawConnectionService(api));
19
+
20
+ // 注册所有的命令行
21
+ registerCommands(api);
22
+ },
23
+ };
24
+
25
+ export default plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@honor-claw/yoyo",
3
- "version": "1.1.2",
3
+ "version": "1.1.4-beta.1",
4
4
  "description": "OpenClaw Honor Yoyo connection plugin",
5
5
  "keywords": [
6
6
  "ai",
@@ -23,12 +23,12 @@
23
23
  "registry": "https://registry.npmjs.org/"
24
24
  },
25
25
  "scripts": {
26
- "test:unit": "vitest run",
27
- "test:unit:watch": "vitest",
28
- "test:coverage": "vitest run --coverage",
29
26
  "ci:check": "npx tsc --noEmit && npm run test:coverage",
30
27
  "publish:beta": "node scripts/publish.js",
31
- "publish:latest": "node scripts/publish.js --release"
28
+ "publish:latest": "node scripts/publish.js --release",
29
+ "test:coverage": "vitest run --coverage",
30
+ "test:unit": "vitest run",
31
+ "test:unit:watch": "vitest"
32
32
  },
33
33
  "dependencies": {
34
34
  "http-proxy-agent": "^8.0.0",
@@ -1,124 +1,124 @@
1
- /**
2
- * Claw Cloud API 接口封装
3
- */
4
- import type { DeviceInfo, HonorUserInfo } from "../types.js";
5
- import type { GatewayAuthConfig } from "../modules/claw-configs/types.js";
6
- import { uuid } from "../utils/id.js";
7
- import { generateAuthorization } from "../utils/jwt.js";
8
- import {
9
- HttpClient,
10
- type HttpResponse,
11
- type HttpClientOptions,
12
- } from "./http-client.js";
13
- import type {
14
- DeviceRegistryHeaders,
15
- DeviceRegistryRequest,
16
- DeviceRegistryResponse,
17
- UserLogoutHeaders,
18
- UserLogoutRequest,
19
- UserLogoutResponse,
20
- } from "./types.js";
21
-
22
- /**
23
- * Claw Cloud API 客户端
24
- */
25
- export class ClawCloudClient {
26
- private httpClient: HttpClient;
27
-
28
- constructor(baseUrl: string, options?: HttpClientOptions) {
29
- this.httpClient = new HttpClient(baseUrl, options);
30
- }
31
-
32
- /**
33
- * 设备注册
34
- * @param deviceInfo 设备信息
35
- * @param userInfo 用户信息
36
- * @param gatewayAuthConfig Gateway 认证配置(可选)
37
- * @returns 注册响应
38
- */
39
- async registerDevice(
40
- deviceInfo: DeviceInfo,
41
- userInfo: HonorUserInfo,
42
- gatewayAuthConfig?: GatewayAuthConfig
43
- ): Promise<HttpResponse<DeviceRegistryResponse>> {
44
- // 构造请求头
45
- const headers: DeviceRegistryHeaders = {
46
- "x-trace-id": uuid(),
47
- };
48
-
49
- if (userInfo.userId) {
50
- headers['x-agw-userId'] = userInfo.userId;
51
- headers['authorization'] = generateAuthorization(userInfo.userId);
52
- headers['x-udid'] = deviceInfo.deviceId;
53
- } else if (userInfo.token) {
54
- headers['x-jwt-token'] = userInfo.token;
55
- headers['x-device-id'] = deviceInfo.deviceId;
56
- }
57
-
58
- const requestBody: DeviceRegistryRequest = {
59
- businessTag: "YOYO_CLAW",
60
- role: "yoyoclaw",
61
- deviceInfo: {
62
- ...deviceInfo,
63
- manufacture: "unknown",
64
- brand: deviceInfo.brand || "unknown",
65
- bizExtInfo: gatewayAuthConfig,
66
- } as any,
67
- };
68
-
69
- return this.httpClient.post<DeviceRegistryResponse>("/v1/device/registry", {
70
- headers: headers as unknown as Record<string, string>,
71
- body: requestBody,
72
- });
73
- }
74
-
75
- /**
76
- * 用户登出
77
- * @param deviceInfo 设备信息
78
- * @param userInfo 用户信息
79
- * @returns 登出响应
80
- */
81
- async logoutDevice(
82
- deviceInfo: DeviceInfo,
83
- userInfo: HonorUserInfo
84
- ): Promise<HttpResponse<UserLogoutResponse>> {
85
- // 构造请求头
86
- const headers: UserLogoutHeaders = {
87
- "x-trace-id": uuid(),
88
- };
89
-
90
- // 如果有 userId,添加 x-agw-userId
91
- if (userInfo.userId) {
92
- headers["x-agw-userId"] = userInfo.userId;
93
- headers["x-udid"] = deviceInfo.deviceId;
94
- headers["authorization"] = generateAuthorization(userInfo.userId);
95
- } else if (userInfo.token) {
96
- headers["x-jwt-token"] = userInfo.token;
97
- headers["x-device-id"] = deviceInfo.deviceId;
98
- }
99
-
100
- const requestBody: UserLogoutRequest = {
101
- businessTag: "YOYO_CLAW",
102
- deviceInfo: {
103
- ...deviceInfo,
104
- manufacture: "unknown",
105
- brand: "unknown",
106
- } as any,
107
- };
108
-
109
- return this.httpClient.post<UserLogoutResponse>("/v1/user/logout", {
110
- headers: headers as unknown as Record<string, string>,
111
- body: requestBody,
112
- });
113
- }
114
- }
115
-
116
- /**
117
- * 创建 Claw Cloud 客户端实例
118
- */
119
- export function createClawCloudClient(
120
- baseUrl: string,
121
- options?: HttpClientOptions
122
- ): ClawCloudClient {
123
- return new ClawCloudClient(baseUrl, options);
124
- }
1
+ /**
2
+ * Claw Cloud API 接口封装
3
+ */
4
+ import type { DeviceInfo, HonorUserInfo } from "../types.js";
5
+ import type { GatewayAuthConfig } from "../modules/claw-configs/types.js";
6
+ import { uuid } from "../utils/id.js";
7
+ import { generateAuthorization } from "../utils/jwt.js";
8
+ import {
9
+ HttpClient,
10
+ type HttpResponse,
11
+ type HttpClientOptions,
12
+ } from "./http-client.js";
13
+ import type {
14
+ DeviceRegistryHeaders,
15
+ DeviceRegistryRequest,
16
+ DeviceRegistryResponse,
17
+ UserLogoutHeaders,
18
+ UserLogoutRequest,
19
+ UserLogoutResponse,
20
+ } from "./types.js";
21
+
22
+ /**
23
+ * Claw Cloud API 客户端
24
+ */
25
+ export class ClawCloudClient {
26
+ private httpClient: HttpClient;
27
+
28
+ constructor(baseUrl: string, options?: HttpClientOptions) {
29
+ this.httpClient = new HttpClient(baseUrl, options);
30
+ }
31
+
32
+ /**
33
+ * 设备注册
34
+ * @param deviceInfo 设备信息
35
+ * @param userInfo 用户信息
36
+ * @param gatewayAuthConfig Gateway 认证配置(可选)
37
+ * @returns 注册响应
38
+ */
39
+ async registerDevice(
40
+ deviceInfo: DeviceInfo,
41
+ userInfo: HonorUserInfo,
42
+ gatewayAuthConfig?: GatewayAuthConfig
43
+ ): Promise<HttpResponse<DeviceRegistryResponse>> {
44
+ // 构造请求头
45
+ const headers: DeviceRegistryHeaders = {
46
+ "x-trace-id": uuid(),
47
+ };
48
+
49
+ if (userInfo.userId) {
50
+ headers['x-agw-userId'] = userInfo.userId;
51
+ headers['authorization'] = generateAuthorization(userInfo.userId);
52
+ headers['x-udid'] = deviceInfo.deviceId;
53
+ } else if (userInfo.token) {
54
+ headers['x-jwt-token'] = userInfo.token;
55
+ headers['x-device-id'] = deviceInfo.deviceId;
56
+ }
57
+
58
+ const requestBody: DeviceRegistryRequest = {
59
+ businessTag: "YOYO_CLAW",
60
+ role: "yoyoclaw",
61
+ deviceInfo: {
62
+ ...deviceInfo,
63
+ manufacture: "unknown",
64
+ brand: deviceInfo.brand || "unknown",
65
+ bizExtInfo: gatewayAuthConfig,
66
+ },
67
+ };
68
+
69
+ return this.httpClient.post<DeviceRegistryResponse>("/v1/device/registry", {
70
+ headers: headers as unknown as Record<string, string>,
71
+ body: requestBody,
72
+ });
73
+ }
74
+
75
+ /**
76
+ * 用户登出
77
+ * @param deviceInfo 设备信息
78
+ * @param userInfo 用户信息
79
+ * @returns 登出响应
80
+ */
81
+ async logoutDevice(
82
+ deviceInfo: DeviceInfo,
83
+ userInfo: HonorUserInfo
84
+ ): Promise<HttpResponse<UserLogoutResponse>> {
85
+ // 构造请求头
86
+ const headers: UserLogoutHeaders = {
87
+ "x-trace-id": uuid(),
88
+ };
89
+
90
+ // 如果有 userId,添加 x-agw-userId
91
+ if (userInfo.userId) {
92
+ headers["x-agw-userId"] = userInfo.userId;
93
+ headers["x-udid"] = deviceInfo.deviceId;
94
+ headers["authorization"] = generateAuthorization(userInfo.userId);
95
+ } else if (userInfo.token) {
96
+ headers["x-jwt-token"] = userInfo.token;
97
+ headers["x-device-id"] = deviceInfo.deviceId;
98
+ }
99
+
100
+ const requestBody: UserLogoutRequest = {
101
+ businessTag: "YOYO_CLAW",
102
+ deviceInfo: {
103
+ ...deviceInfo,
104
+ manufacture: "unknown",
105
+ brand: "unknown",
106
+ },
107
+ };
108
+
109
+ return this.httpClient.post<UserLogoutResponse>("/v1/user/logout", {
110
+ headers: headers as unknown as Record<string, string>,
111
+ body: requestBody,
112
+ });
113
+ }
114
+ }
115
+
116
+ /**
117
+ * 创建 Claw Cloud 客户端实例
118
+ */
119
+ export function createClawCloudClient(
120
+ baseUrl: string,
121
+ options?: HttpClientOptions
122
+ ): ClawCloudClient {
123
+ return new ClawCloudClient(baseUrl, options);
124
+ }
@@ -1,10 +1,10 @@
1
- import type { HttpResponse } from './http-client.js';
2
- import type { HttpApiWrapper } from './types.js';
3
-
4
- export function isOKResponse<T>(response: HttpResponse<HttpApiWrapper<T>>) {
5
- if (response.status >= 200 && response.status < 300 && response.data.code === '1') {
6
- return true;
7
- }
8
-
9
- return false;
10
- }
1
+ import type { HttpResponse } from './http-client.js';
2
+ import type { HttpApiWrapper } from './types.js';
3
+
4
+ export function isOKResponse<T>(response: HttpResponse<HttpApiWrapper<T>>) {
5
+ if (response.status >= 200 && response.status < 300 && response.data.code === '1') {
6
+ return true;
7
+ }
8
+
9
+ return false;
10
+ }
@@ -1,158 +1,158 @@
1
- /**
2
- * 荣耀OAuth2认证客户端
3
- * 提供授权URL构建、Token获取等API能力
4
- */
5
-
6
- import { createHash, randomBytes } from 'crypto';
7
- import { HttpClient, type HttpClientOptions } from './http-client.js';
8
- import type { TokenResponse, HonorAuthConfig } from '../honor-auth/types.js';
9
- import { uuid } from '../utils/id.js';
10
- import { isOKResponse } from './helpers.js';
11
- import type { HttpApiWrapper } from './types.js';
12
- import { takeApiHost } from '../modules/claw-configs/hosts.js';
13
-
14
- /**
15
- * PKCE参数
16
- */
17
- export interface PKCEParams {
18
- codeVerifier: string;
19
- codeChallenge: string;
20
- }
21
-
22
- /**
23
- * 荣耀认证客户端
24
- */
25
- export class HonorAuthClient {
26
- private httpClient: HttpClient;
27
- private config: HonorAuthConfig;
28
-
29
- constructor(config: HonorAuthConfig) {
30
- this.config = config;
31
- // 从 HonorAuthConfig 中提取 HttpClientOptions
32
- const httpClientOptions: HttpClientOptions = {
33
- proxy: config.proxy,
34
- };
35
- this.httpClient = new HttpClient(config.authHost, httpClientOptions);
36
- }
37
-
38
- /**
39
- * 生成随机字符串
40
- */
41
- private generateRandomString(length: number): string {
42
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
43
- const bytes = randomBytes(length);
44
- let result = '';
45
- for (let i = 0; i < length; i++) {
46
- result += chars[bytes[i] % chars.length];
47
- }
48
- return result;
49
- }
50
-
51
- /**
52
- * 生成PKCE code_verifier
53
- */
54
- generateCodeVerifier(length: number = 128): string {
55
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
56
- const bytes = randomBytes(length);
57
- let result = '';
58
- for (let i = 0; i < length; i++) {
59
- result += chars[bytes[i] % chars.length];
60
- }
61
- return result;
62
- }
63
-
64
- /**
65
- * 生成PKCE code_challenge
66
- */
67
- generateCodeChallenge(verifier: string): string {
68
- const hash = createHash('sha256').update(verifier).digest();
69
- return hash.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
70
- }
71
-
72
- /**
73
- * 生成PKCE参数
74
- */
75
- generatePKCEParams(): PKCEParams {
76
- const codeVerifier = this.generateCodeVerifier();
77
- const codeChallenge = this.generateCodeChallenge(codeVerifier);
78
- return { codeVerifier, codeChallenge };
79
- }
80
-
81
- /**
82
- * 构建授权URL
83
- */
84
- buildAuthUrl(pkceParams: PKCEParams): string {
85
- const params = new URLSearchParams({
86
- client_id: this.config.clientId,
87
- redirect_uri: this.config.redirectUri,
88
- scope: this.config.scope,
89
- response_type: 'code',
90
- access_type: 'offline',
91
- state: this.generateRandomString(16),
92
- reqClientType: this.config.reqClientType,
93
- loginChannel: this.config.loginChannel,
94
- code_challenge: pkceParams.codeChallenge,
95
- code_challenge_method: 'S256',
96
- });
97
-
98
- return `${this.config.authHost}/oauth2/v3/authorize?${params.toString()}`;
99
- }
100
-
101
- /**
102
- * 使用授权码换取Token
103
- */
104
- async exchangeToken(code: string) {
105
- const hosts = takeApiHost();
106
- const tokenUrl = `https://${hosts.ics}/honorboard/auth/v1/oauth/jwtToken`;
107
-
108
- const data = {
109
- clientId: this.config.clientId,
110
- code,
111
- redirectUri: this.config.redirectUri,
112
- };
113
-
114
- try {
115
- // 构建请求头,包含灰度标签(如果有)
116
- const headers: Record<string, string> = {
117
- 'Content-Type': 'application/json',
118
- 'x-origin-udid': this.config.clientId,
119
- 'x-app-pkg': 'com.hihonor.pc.openclaw',
120
- 'x-request-nonce': uuid(),
121
- 'x-timestamp': String(Date.now()),
122
- };
123
-
124
- // 添加灰度标签
125
- if (hosts.grayTag) {
126
- headers['x-gray'] = hosts.grayTag;
127
- }
128
-
129
- const response = await this.httpClient.post<HttpApiWrapper<TokenResponse>>(tokenUrl, {
130
- body: data,
131
- headers,
132
- timeout: 15000,
133
- });
134
-
135
- if (isOKResponse(response)) {
136
- return response.data.data;
137
- }
138
-
139
- throw new Error(`failed to get token: ${response.data?.cnMessage}`);
140
- } catch (error) {
141
- throw new Error(`failed to get token: ${error instanceof Error ? error.message : String(error)}`);
142
- }
143
- }
144
-
145
- /**
146
- * 获取配置
147
- */
148
- getConfig(): HonorAuthConfig {
149
- return { ...this.config };
150
- }
151
- }
152
-
153
- /**
154
- * 创建荣耀认证客户端
155
- */
156
- export function createHonorAuthClient(config: HonorAuthConfig): HonorAuthClient {
157
- return new HonorAuthClient(config);
158
- }
1
+ /**
2
+ * 荣耀OAuth2认证客户端
3
+ * 提供授权URL构建、Token获取等API能力
4
+ */
5
+
6
+ import { createHash, randomBytes } from 'crypto';
7
+ import { HttpClient, type HttpClientOptions } from './http-client.js';
8
+ import type { TokenResponse, HonorAuthConfig } from '../honor-auth/types.js';
9
+ import { uuid } from '../utils/id.js';
10
+ import { isOKResponse } from './helpers.js';
11
+ import type { HttpApiWrapper } from './types.js';
12
+ import { takeApiHost } from '../modules/claw-configs/hosts.js';
13
+
14
+ /**
15
+ * PKCE参数
16
+ */
17
+ export interface PKCEParams {
18
+ codeVerifier: string;
19
+ codeChallenge: string;
20
+ }
21
+
22
+ /**
23
+ * 荣耀认证客户端
24
+ */
25
+ export class HonorAuthClient {
26
+ private httpClient: HttpClient;
27
+ private config: HonorAuthConfig;
28
+
29
+ constructor(config: HonorAuthConfig) {
30
+ this.config = config;
31
+ // 从 HonorAuthConfig 中提取 HttpClientOptions
32
+ const httpClientOptions: HttpClientOptions = {
33
+ proxy: config.proxy,
34
+ };
35
+ this.httpClient = new HttpClient(config.authHost, httpClientOptions);
36
+ }
37
+
38
+ /**
39
+ * 生成随机字符串
40
+ */
41
+ private generateRandomString(length: number): string {
42
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
43
+ const bytes = randomBytes(length);
44
+ let result = '';
45
+ for (let i = 0; i < length; i++) {
46
+ result += chars[bytes[i] % chars.length];
47
+ }
48
+ return result;
49
+ }
50
+
51
+ /**
52
+ * 生成PKCE code_verifier
53
+ */
54
+ generateCodeVerifier(length: number = 128): string {
55
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
56
+ const bytes = randomBytes(length);
57
+ let result = '';
58
+ for (let i = 0; i < length; i++) {
59
+ result += chars[bytes[i] % chars.length];
60
+ }
61
+ return result;
62
+ }
63
+
64
+ /**
65
+ * 生成PKCE code_challenge
66
+ */
67
+ generateCodeChallenge(verifier: string): string {
68
+ const hash = createHash('sha256').update(verifier).digest();
69
+ return hash.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
70
+ }
71
+
72
+ /**
73
+ * 生成PKCE参数
74
+ */
75
+ generatePKCEParams(): PKCEParams {
76
+ const codeVerifier = this.generateCodeVerifier();
77
+ const codeChallenge = this.generateCodeChallenge(codeVerifier);
78
+ return { codeVerifier, codeChallenge };
79
+ }
80
+
81
+ /**
82
+ * 构建授权URL
83
+ */
84
+ buildAuthUrl(pkceParams: PKCEParams): string {
85
+ const params = new URLSearchParams({
86
+ client_id: this.config.clientId,
87
+ redirect_uri: this.config.redirectUri,
88
+ scope: this.config.scope,
89
+ response_type: 'code',
90
+ access_type: 'offline',
91
+ state: this.generateRandomString(16),
92
+ reqClientType: this.config.reqClientType,
93
+ loginChannel: this.config.loginChannel,
94
+ code_challenge: pkceParams.codeChallenge,
95
+ code_challenge_method: 'S256',
96
+ });
97
+
98
+ return `${this.config.authHost}/oauth2/v3/authorize?${params.toString()}`;
99
+ }
100
+
101
+ /**
102
+ * 使用授权码换取Token
103
+ */
104
+ async exchangeToken(code: string) {
105
+ const hosts = takeApiHost();
106
+ const tokenUrl = `https://${hosts.ics}/honorboard/auth/v1/oauth/jwtToken`;
107
+
108
+ const data = {
109
+ clientId: this.config.clientId,
110
+ code,
111
+ redirectUri: this.config.redirectUri,
112
+ };
113
+
114
+ try {
115
+ // 构建请求头,包含灰度标签(如果有)
116
+ const headers: Record<string, string> = {
117
+ 'Content-Type': 'application/json',
118
+ 'x-origin-udid': this.config.clientId,
119
+ 'x-app-pkg': 'com.hihonor.pc.openclaw',
120
+ 'x-request-nonce': uuid(),
121
+ 'x-timestamp': String(Date.now()),
122
+ };
123
+
124
+ // 添加灰度标签
125
+ if (hosts.grayTag) {
126
+ headers['x-gray'] = hosts.grayTag;
127
+ }
128
+
129
+ const response = await this.httpClient.post<HttpApiWrapper<TokenResponse>>(tokenUrl, {
130
+ body: data,
131
+ headers,
132
+ timeout: 15000,
133
+ });
134
+
135
+ if (isOKResponse(response)) {
136
+ return response.data.data;
137
+ }
138
+
139
+ throw new Error(`failed to get token: ${response.data?.cnMessage}`);
140
+ } catch (error) {
141
+ throw new Error(`failed to get token: ${error instanceof Error ? error.message : String(error)}`);
142
+ }
143
+ }
144
+
145
+ /**
146
+ * 获取配置
147
+ */
148
+ getConfig(): HonorAuthConfig {
149
+ return { ...this.config };
150
+ }
151
+ }
152
+
153
+ /**
154
+ * 创建荣耀认证客户端
155
+ */
156
+ export function createHonorAuthClient(config: HonorAuthConfig): HonorAuthClient {
157
+ return new HonorAuthClient(config);
158
+ }